c64 0.1.0-alpha.1

Driver for the Commodore 64 platform
Documentation
//! HAL for the Commodore 64.

use core::marker::PhantomData;

use crate::pac::peripheral::{asp::IoConfig, ASP};

pub mod basic;
pub mod fp;
pub mod kernal;

/// Manager of the Commodore 64's memory map.
pub struct AddressSpace<HIRAM, LORAM, CHAREN> {
    asp: ASP,
    _cur: PhantomData<(HIRAM, LORAM, CHAREN)>,
}

impl AddressSpace<Kernal, Basic, Io> {
    /// Loads the default address space configuration at boot.
    pub fn at_boot(asp: ASP) -> Result<Self, ASP> {
        let p = asp.port();
        if p.kernal() && p.basic() && matches!(p.io_config(), IoConfig::Io) {
            Ok(Self {
                asp,
                _cur: PhantomData,
            })
        } else {
            Err(asp)
        }
    }
}

impl AddressSpace<Kernal, Ram, Io> {
    /// Loads the default address space configuration when running a program via the PRG
    /// Executor of the C64 Ultimate HTTP Server.
    #[cfg(feature = "ultimate")]
    pub fn at_web_executor(asp: ASP) -> Result<Self, ASP> {
        let p = asp.port();
        if p.kernal() && !p.basic() && matches!(p.io_config(), IoConfig::Io) {
            Ok(Self {
                asp,
                _cur: PhantomData,
            })
        } else {
            Err(asp)
        }
    }
}

//
// Disabling or enabling the Kernal ROM is tricky because toggling HIRAM without altering
// the other bits to compensate can change the logical state of the other overlays. We
// manage this by explicitly encoding the Kernal state transitions. The others can make
// more usage of generics due to symmetries in the constraint table.
//

impl<LORAM> AddressSpace<Kernal, LORAM, Io> {
    /// Disables the Kernal ROM overlay, exposing `$E000-$FFFF` as RAM.
    ///
    /// This also effectively calls [`Self::disable_basic`], because the BASIC ROM uses
    /// the Kernal ROM.
    pub fn disable_kernal(self) -> AddressSpace<Ram, Ram, Io> {
        let Self { asp, .. } = self;

        // SAFETY: This is a valid configuration.
        unsafe { asp.configure_memory(Some(false), Some(false), Some(IoConfig::Io)) };

        AddressSpace {
            asp,
            _cur: PhantomData,
        }
    }
}

impl<LORAM> AddressSpace<Kernal, LORAM, CharacterRom> {
    /// Disables the Kernal ROM overlay, exposing `$E000-$FFFF` as RAM.
    ///
    /// This also effectively calls [`Self::disable_basic`], because the BASIC ROM uses
    /// the Kernal ROM.
    pub fn disable_kernal(self) -> AddressSpace<Ram, Ram, CharacterRom> {
        let Self { asp, .. } = self;

        // SAFETY: This is a valid configuration.
        unsafe { asp.configure_memory(Some(false), Some(false), Some(IoConfig::CharacterRom)) };

        AddressSpace {
            asp,
            _cur: PhantomData,
        }
    }
}

impl AddressSpace<Ram, Ram, Io> {
    /// Enables the Kernal ROM overlay at `$E000-$FFFF`.
    pub fn enable_kernal(self) -> AddressSpace<Kernal, Ram, Io> {
        let Self { asp, .. } = self;

        // SAFETY: This is a valid configuration.
        unsafe { asp.configure_memory(Some(true), Some(false), Some(IoConfig::Io)) };

        AddressSpace {
            asp,
            _cur: PhantomData,
        }
    }

    /// Removes the I/O devices, exposing `$D000-$DFFF` as RAM.
    pub fn disable_io(self) -> AddressSpace<Ram, Ram, Ram> {
        let Self { asp, .. } = self;

        // SAFETY: This is a valid configuration.
        unsafe { asp.configure_memory(Some(false), Some(false), Some(IoConfig::Ram)) };

        AddressSpace {
            asp,
            _cur: PhantomData,
        }
    }
}

impl AddressSpace<Ram, Ram, CharacterRom> {
    /// Enables the Kernal ROM overlay at `$E000-$FFFF`.
    pub fn enable_kernal(self) -> AddressSpace<Kernal, Ram, CharacterRom> {
        let Self { asp, .. } = self;

        // SAFETY: This is a valid configuration.
        unsafe { asp.configure_memory(Some(true), Some(false), Some(IoConfig::CharacterRom)) };

        AddressSpace {
            asp,
            _cur: PhantomData,
        }
    }

    /// Removes the character ROM, exposing `$D000-$DFFF` as RAM.
    pub fn disable_character_rom(self) -> AddressSpace<Ram, Ram, Ram> {
        let Self { asp, .. } = self;

        // SAFETY: This is a valid configuration.
        unsafe { asp.configure_memory(Some(false), Some(false), Some(IoConfig::Ram)) };

        AddressSpace {
            asp,
            _cur: PhantomData,
        }
    }
}

impl<CHAREN> AddressSpace<Kernal, Basic, CHAREN> {
    /// Disables the BASIC ROM overlay, exposing `$A000-$BFFF` as RAM.
    pub fn disable_basic(self) -> AddressSpace<Kernal, Ram, CHAREN> {
        let Self { asp, .. } = self;

        // SAFETY: We know HIRAM is enabled, so disabling LORAM won't affect CHAREN.
        unsafe { asp.configure_memory(None, Some(false), None) };

        AddressSpace {
            asp,
            _cur: PhantomData,
        }
    }
}

impl<CHAREN> AddressSpace<Kernal, Ram, CHAREN> {
    /// Enables the BASIC ROM overlay at `$A000-$BFFF`.
    pub fn enable_basic(self) -> AddressSpace<Kernal, Basic, CHAREN> {
        let Self { asp, .. } = self;

        // SAFETY: We know HIRAM is enabled, so enabling LORAM won't affect CHAREN.
        unsafe { asp.configure_memory(None, Some(true), None) };

        AddressSpace {
            asp,
            _cur: PhantomData,
        }
    }
}

impl<HIRAM, LORAM> AddressSpace<HIRAM, LORAM, Io> {
    /// Removes the I/O devices, and exposes the character ROM at `$D000-$DFFF`.
    pub fn switch_to_character_rom(self) -> AddressSpace<HIRAM, LORAM, CharacterRom> {
        let Self { asp, .. } = self;

        // SAFETY: Every valid I/O configuration has a matching valid Character ROM
        // configuration.
        unsafe { asp.configure_memory(None, None, Some(IoConfig::CharacterRom)) };

        AddressSpace {
            asp,
            _cur: PhantomData,
        }
    }
}

impl<HIRAM, LORAM> AddressSpace<HIRAM, LORAM, CharacterRom> {
    /// Removes the character ROM, and exposes the I/O devices at `$D000-$DFFF`.
    pub fn switch_to_io(self) -> AddressSpace<HIRAM, LORAM, Io> {
        let Self { asp, .. } = self;

        // SAFETY: Every valid Character ROM configuration has a matching valid I/O
        // configuration.
        unsafe { asp.configure_memory(None, None, Some(IoConfig::Io)) };

        AddressSpace {
            asp,
            _cur: PhantomData,
        }
    }
}

impl AddressSpace<Ram, Ram, Ram> {
    /// Exposes the I/O devices at `$D000-$DFFF`.
    pub fn enable_io(self) -> AddressSpace<Ram, Ram, Io> {
        let Self { asp, .. } = self;

        // SAFETY: This is a valid configuration.
        unsafe { asp.configure_memory(Some(false), Some(false), Some(IoConfig::Io)) };

        AddressSpace {
            asp,
            _cur: PhantomData,
        }
    }

    /// Enables the character ROM overlay at `$D000-$DFFF`.
    pub fn enable_character_rom(self) -> AddressSpace<Ram, Ram, CharacterRom> {
        let Self { asp, .. } = self;

        // SAFETY: This is a valid configuration.
        unsafe { asp.configure_memory(Some(false), Some(false), Some(IoConfig::CharacterRom)) };

        AddressSpace {
            asp,
            _cur: PhantomData,
        }
    }
}

/// Type marker for the Kernal ROM being mapped into memory.
pub struct Kernal;
/// Type marker for the BASIC ROM being mapped into memory.
pub struct Basic;
/// Type marker for a particular region of memory being RAM.
pub struct Ram;
/// Type marker for I/O devices being mapped into memory.
pub struct Io;
/// Type marker for the character ROM being mapped into memory.
pub struct CharacterRom;