mos-hardware 0.4.0

Hardware register tables and support functions for 8-bit retro computers like the Commodore 64, MEGA65 and others.
Documentation
use bitflags::bitflags;
use core::error::Error;
use core::ffi::CStr;
use core::fmt;

/* automatically generated by rust-bindgen 0.63.0 */

pub const CH_HLINE: u32 = 192;
pub const CH_VLINE: u32 = 221;
pub const CH_ULCORNER: u32 = 176;
pub const CH_URCORNER: u32 = 174;
pub const CH_LLCORNER: u32 = 173;
pub const CH_LRCORNER: u32 = 189;
pub const CH_TTEE: u32 = 178;
pub const CH_BTEE: u32 = 177;
pub const CH_LTEE: u32 = 171;
pub const CH_RTEE: u32 = 179;
pub const CH_CROSS: u32 = 219;
pub const CH_CURS_UP: u32 = 145;
pub const CH_CURS_DOWN: u32 = 17;
pub const CH_CURS_LEFT: u32 = 157;
pub const CH_CURS_RIGHT: u32 = 29;
pub const CH_PI: u32 = 222;
pub const CH_HOME: u32 = 19;
pub const CH_DEL: u32 = 20;
pub const CH_INS: u32 = 148;
pub const CH_ENTER: u32 = 13;
pub const CH_STOP: u32 = 3;
pub const CH_LIRA: u32 = 92;
pub const CH_ESC: u32 = 27;
pub const CH_FONT_LOWER: u32 = 14;
pub const CH_FONT_UPPER: u32 = 142;
pub const CBM_A_RO: u32 = 1;
pub const CBM_A_WO: u32 = 2;
pub const CBM_A_RW: u32 = 3;
pub const CBM_READ: u32 = 0;
pub const CBM_WRITE: u32 = 1;
pub const CBM_SEQ: u32 = 2;

#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct max_align_t {
    pub __clang_max_align_nonce1: ::core::ffi::c_longlong,
    pub __clang_max_align_nonce2: f64,
}

#[repr(C)]
#[derive(Debug, Default, Copy, Clone)]
pub struct cbm_dirent {
    pub name: [::core::ffi::c_char; 17usize],
    pub size: ::core::ffi::c_uint,
    pub type_: ::core::ffi::c_uchar,
    pub access: ::core::ffi::c_uchar,
}

extern "C" {
    pub fn cbm_k_acptr() -> ::core::ffi::c_uchar;
}
extern "C" {
    pub fn cbm_k_basin() -> ::core::ffi::c_uchar;
}
extern "C" {
    pub fn cbm_k_bsout(C: ::core::ffi::c_uchar);
}
extern "C" {
    pub fn cbm_k_chkin(FN: ::core::ffi::c_uchar) -> ::core::ffi::c_uchar;
}
extern "C" {
    pub fn cbm_k_chrin() -> ::core::ffi::c_uchar;
}
extern "C" {
    pub fn cbm_k_chrout(C: ::core::ffi::c_uchar);
}
extern "C" {
    pub fn cbm_k_ciout(C: ::core::ffi::c_uchar);
}
extern "C" {
    pub fn cbm_k_ckout(FN: ::core::ffi::c_uchar) -> ::core::ffi::c_uchar;
}
extern "C" {
    pub fn cbm_k_clall();
}
extern "C" {
    pub fn cbm_k_close(FN: ::core::ffi::c_uchar);
}
extern "C" {
    pub fn cbm_k_clrch();
}
extern "C" {
    pub fn cbm_k_getin() -> ::core::ffi::c_uchar;
}
extern "C" {
    pub fn cbm_k_iobase() -> ::core::ffi::c_uint;
}
extern "C" {
    pub fn cbm_k_listen(dev: ::core::ffi::c_uchar);
}
extern "C" {
    pub fn cbm_k_load(flag: ::core::ffi::c_uchar, addr: ::core::ffi::c_uint)
        -> ::core::ffi::c_uint;
}
extern "C" {
    pub fn cbm_k_open() -> ::core::ffi::c_uchar;
}
extern "C" {
    pub fn cbm_k_readst() -> ::core::ffi::c_uchar;
}
extern "C" {
    pub fn cbm_k_save(start: ::core::ffi::c_uint, end: ::core::ffi::c_uint)
        -> ::core::ffi::c_uchar;
}
extern "C" {
    pub fn cbm_k_scnkey();
}
extern "C" {
    pub fn cbm_k_second(addr: ::core::ffi::c_uchar);
}
extern "C" {
    pub fn cbm_k_setlfs(
        LFN: ::core::ffi::c_uchar,
        DEV: ::core::ffi::c_uchar,
        SA: ::core::ffi::c_uchar,
    );
}
extern "C" {
    pub fn cbm_k_setnam(Name: *const ::core::ffi::c_uchar);
}
extern "C" {
    pub fn cbm_k_settim(timer: ::core::ffi::c_ulong);
}
extern "C" {
    pub fn cbm_k_talk(dev: ::core::ffi::c_uchar);
}
extern "C" {
    pub fn cbm_k_tksa(addr: ::core::ffi::c_uchar);
}
extern "C" {
    pub fn cbm_k_udtim();
}
extern "C" {
    pub fn cbm_k_unlsn();
}
extern "C" {
    pub fn cbm_k_untlk();
}

bitflags! {
    /// Status bits for tape and serial I/O as returned by
    /// the READST kernal routine.
    pub struct StatusFlags: u8 {
        /// Serial write time out
        const WRITE_TIME_OUT = 0b0000_0001; // bit 0
        /// Serial read time out
        const READ_TIME_OUT  = 0b0000_0010; // bit 1
        /// Tape short block
        const SHORT_BLOCK = 0b0000_0100; // bit 2
        /// Tape long block
        const LONG_BLOCK = 0b0000_1000; // bit 3
        /// Unrecoverable tape read error or serial verify error
        const READ_ERROR = 0b0001_0000; // bit 4
        /// Tape checksum error
        const CHECKSUM_ERROR = 0b0010_0000; // bit 5
        /// End of file (tape) or identity (serial)
        const END_OF_IDENTITY = 0b0100_0000; // bit 6
        /// End of tape or device not present
        const DEVICE_NOT_PRESENT = 0b1000_0000; // bit 7
    }
}

#[derive(Debug)]
pub enum FileError {
    TooManyFiles,        // 1
    FileOpen,            // 2
    FileNotOpen,         // 3
    FileNotFound,        // 4
    DeviceNotPresent,    // 5
    NotInputFile,        // 6
    NotOutputFile,       // 7
    MissingFileName,     // 8
    IllegalDeviceNumber, // 9
    StopKeyPushed,       // 10
    IOError,             // 11
    Other(u8),
}

impl FileError {
    pub const fn new(code: u8) -> Self {
        match code {
            1 => Self::TooManyFiles,
            2 => Self::FileOpen,
            3 => Self::FileNotOpen,
            4 => Self::FileNotFound,
            5 => Self::DeviceNotPresent,
            6 => Self::NotInputFile,
            7 => Self::NotOutputFile,
            8 => Self::MissingFileName,
            9 => Self::IllegalDeviceNumber,
            10 => Self::StopKeyPushed,
            11 => Self::IOError,
            _ => Self::Other(code),
        }
    }

    pub const fn value(&self) -> u8 {
        match &self {
            Self::TooManyFiles => 1,
            Self::FileOpen => 2,
            Self::FileNotOpen => 3,
            Self::FileNotFound => 4,
            Self::DeviceNotPresent => 5,
            Self::NotInputFile => 6,
            Self::NotOutputFile => 7,
            Self::MissingFileName => 8,
            Self::IllegalDeviceNumber => 9,
            Self::StopKeyPushed => 10,
            Self::IOError => 11,
            Self::Other(value) => *value,
        }
    }
}

impl From<u8> for FileError {
    fn from(error_code: u8) -> Self {
        FileError::new(error_code)
    }
}

impl From<&FileError> for u8 {
    fn from(error: &FileError) -> Self {
        error.value()
    }
}

impl Error for FileError {}

impl fmt::Display for FileError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "FILE ERROR: {}", u8::from(self))
    }
}

/// Loads `filename` from `device` into `address` or to the file load address is `address == None`
/// Returns number of loaded bytes.
fn _cbm_load(filename: &CStr, device: u8, load_address: Option<u16>) -> u16 {
    // logical file numner, lfn, is set to 0; but, it's not needed for loading
    // (BASIC V2 sets it to the value of the SA for LOAD).
    let lfn = 0u8;
    let (address, secondary_address) = match load_address {
        Some(address) => (address, 0u8),
        None => (0, 1), // use file load address (first two bytes)
    };
    unsafe {
        cbm_k_setlfs(lfn, device, secondary_address);
        cbm_k_setnam(filename.to_bytes_with_nul().as_ptr());
        cbm_k_load(lfn, address) - address
    }
}

/// CBM devices
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Device {
    Keyboard,
    Tape,
    RC232,
    CRT,
    Printer,
    Plotter,
    Drive8,
    Drive9,
    Other(u8),
}

impl Device {
    pub const fn value(&self) -> u8 {
        match *self {
            Device::Keyboard => 0,
            Device::Tape => 1,
            Device::RC232 => 2,
            Device::CRT => 3,
            Device::Printer => 4,
            Device::Plotter => 5,
            Device::Drive8 => 8,
            Device::Drive9 => 9,
            Device::Other(number) => number,
        }
    }
}

impl From<u8> for Device {
    fn from(value: u8) -> Self {
        Device::Other(value)
    }
}

impl From<Device> for u8 {
    fn from(device: Device) -> Self {
        device.value()
    }
}

/// Opens a CBM file
///
/// # Warning
/// This is under constuction
#[derive(Debug, PartialEq, Eq)]
pub struct File {
    logical_file_number: u8,
}

impl File {
    /// Attempts to open a file in read-only mode.
    pub fn open(
        filename: &CStr,
        device: Device,
        logical_file_number: u8,
    ) -> Result<Self, FileError> {
        unsafe {
            cbm_k_setlfs(logical_file_number, device.value(), 15);
            cbm_k_setnam(filename.to_bytes_with_nul().as_ptr());
        }
        match unsafe { cbm_k_open() } {
            0 => Ok(File {
                logical_file_number,
            }),
            _ => Err(FileError::IOError),
        }
    }
}

impl Drop for File {
    fn drop(&mut self) {
        unsafe { cbm_k_close(self.logical_file_number) };
    }
}

impl genio::Read for File {
    type ReadError = FileError;

    fn read(&mut self, buf: &mut [u8]) -> Result<usize, Self::ReadError> {
        // if we can't change to the input channel #lfn then return an error
        if unsafe { cbm_k_chkin(self.logical_file_number) } != 0 {
            return Err(FileError::IOError);
        }
        let mut bytes_read = 0;
        while (bytes_read < buf.len()) && (unsafe { cbm_k_readst() } == 0) {
            let byte = unsafe { cbm_k_basin() };
            // the kernal routine BASIN sets ST to EOF if the end of file
            // is reached the first time, then we have store tmp.
            // every subsequent call returns EOF (bit 6) and READ ERROR in ST, then
            // we have to exit the loop here immediatly.
            if (unsafe { cbm_k_readst() } & 0b10111111) == 0 {
                break;
            }
            buf[bytes_read] = byte;
            bytes_read += 1;
        }
        Ok(bytes_read)
    }
}