use crate::emulator::{HostCall, OpenMode};
use crate::soc::Width;
pub(super) mod operation {
pub(super) const OPEN: u32 = 0x01;
pub(super) const CLOSE: u32 = 0x02;
pub(super) const WRITEC: u32 = 0x03;
pub(super) const WRITE0: u32 = 0x04;
pub(super) const WRITE: u32 = 0x05;
pub(super) const READ: u32 = 0x06;
pub(super) const ISTTY: u32 = 0x09;
pub(super) const SEEK: u32 = 0x0A;
pub(super) const FLEN: u32 = 0x0C;
pub(super) const CLOCK: u32 = 0x10;
pub(super) const TIME: u32 = 0x11;
pub(super) const ERRNO: u32 = 0x13;
pub(super) const EXIT: u32 = 0x18;
pub(super) const EXIT_EXTENDED: u32 = 0x20;
pub(in super::super) const ELAPSED: u32 = 0x30;
pub(super) const TICKFREQ: u32 = 0x31;
}
const APPLICATION_EXIT: u64 = 0x2_0026;
const FAILED: u32 = 1;
const CONSOLE: &[u8] = b":tt";
const CONSOLE_IN: u64 = 0;
const CONSOLE_OUT: u64 = 1;
const LONGEST_STRING: u64 = 64 << 10;
pub(super) fn decode(
operation: u32,
parameter: u32,
read: &mut dyn FnMut(u64, Width) -> Option<u64>,
) -> Option<HostCall> {
let block = u64::from(parameter);
let mut field = |index: u64| read(block.checked_add(4 * index)?, Width::W32);
Some(match operation {
operation::OPEN => {
let (path, mode, length) = (field(0)?, field(1)?, field(2)?);
let mode = open_mode(mode)?;
if is_console(path, length, read) {
let handle = if matches!(mode, OpenMode::Read | OpenMode::ReadWrite) {
CONSOLE_IN
} else {
CONSOLE_OUT
};
HostCall::OpenConsole { handle }
} else {
HostCall::Open { path, length, mode }
}
}
operation::CLOSE => HostCall::Close { handle: field(0)? },
operation::WRITEC => HostCall::Write {
handle: CONSOLE_OUT,
address: block,
length: 1,
},
operation::WRITE0 => HostCall::Write {
handle: CONSOLE_OUT,
address: block,
length: string_length(block, read)?,
},
operation::WRITE => HostCall::Write {
handle: field(0)?,
address: field(1)?,
length: field(2)?,
},
operation::READ => HostCall::Read {
handle: field(0)?,
address: field(1)?,
length: field(2)?,
},
operation::ISTTY => HostCall::IsTty { handle: field(0)? },
operation::SEEK => HostCall::Seek {
handle: field(0)?,
position: field(1)?,
},
operation::FLEN => HostCall::Length { handle: field(0)? },
operation::CLOCK => HostCall::Clock,
operation::TIME => HostCall::Time,
operation::ERRNO => HostCall::Errno,
operation::EXIT => HostCall::Exit {
code: if block == APPLICATION_EXIT { 0 } else { FAILED },
},
operation::EXIT_EXTENDED => HostCall::Exit {
code: if field(0)? == APPLICATION_EXIT {
u32::try_from(field(1)?).ok()?
} else {
FAILED
},
},
operation::ELAPSED => HostCall::Elapsed { block },
operation::TICKFREQ => HostCall::TickFrequency,
_ => return None,
})
}
fn open_mode(mode: u64) -> Option<OpenMode> {
Some(match mode {
0 | 1 => OpenMode::Read,
2 | 3 => OpenMode::ReadWrite,
4 | 5 => OpenMode::Write,
6 | 7 => OpenMode::WriteRead,
8 | 9 => OpenMode::Append,
10 | 11 => OpenMode::AppendRead,
_ => return None,
})
}
fn is_console(path: u64, length: u64, read: &mut dyn FnMut(u64, Width) -> Option<u64>) -> bool {
length == CONSOLE.len() as u64
&& (path..)
.zip(CONSOLE)
.all(|(at, &byte)| read(at, Width::W8) == Some(u64::from(byte)))
}
fn string_length(address: u64, read: &mut dyn FnMut(u64, Width) -> Option<u64>) -> Option<u64> {
(0..LONGEST_STRING).find_map(
|offset| match read(address.checked_add(offset)?, Width::W8) {
Some(0) => Some(Some(offset)),
Some(_) => None,
None => Some(None),
},
)?
}
#[cfg(test)]
mod tests {
use super::*;
use crate::laid::Laid;
fn memory(words: &[(u64, u32)], bytes: &[(u64, &[u8])]) -> Laid {
let mut memory = Laid::default();
for &(at, word) in words {
memory.word(at, word);
}
for &(at, string) in bytes {
memory.bytes(at, string);
}
memory
}
fn reader(memory: &Laid) -> impl Fn(u64, Width) -> Option<u64> + '_ {
|address, width| memory.read(address, width)
}
const BLOCK: u32 = 0x2000_0100;
const NAME: u32 = 0x2000_0200;
#[test]
fn each_operation_decodes_as_the_specification_gives_it() {
let laid = memory(
&[
(u64::from(BLOCK), NAME),
(u64::from(BLOCK) + 4, 6),
(u64::from(BLOCK) + 8, 4),
],
&[(u64::from(NAME), b"data\0hello\0")],
);
let mut read = reader(&laid);
let mut call = |operation, parameter| decode(operation, parameter, &mut read);
let path = u64::from(NAME);
assert_eq!(
call(0x01, BLOCK),
Some(HostCall::Open {
path,
length: 4,
mode: OpenMode::WriteRead
})
);
assert_eq!(call(0x02, BLOCK), Some(HostCall::Close { handle: path }));
assert_eq!(
call(0x03, NAME),
Some(HostCall::Write {
handle: 1,
address: path,
length: 1
})
);
assert_eq!(
call(0x04, NAME + 5),
Some(HostCall::Write {
handle: 1,
address: path + 5,
length: 5
}),
"SYS_WRITE0 up to its NUL"
);
let fields = HostCall::Write {
handle: path,
address: 6,
length: 4,
};
assert_eq!(call(0x05, BLOCK), Some(fields));
assert_eq!(
call(0x06, BLOCK),
Some(HostCall::Read {
handle: path,
address: 6,
length: 4
})
);
assert_eq!(call(0x09, BLOCK), Some(HostCall::IsTty { handle: path }));
assert_eq!(
call(0x0A, BLOCK),
Some(HostCall::Seek {
handle: path,
position: 6
})
);
assert_eq!(call(0x0C, BLOCK), Some(HostCall::Length { handle: path }));
assert_eq!(call(0x10, 0), Some(HostCall::Clock));
assert_eq!(call(0x11, 0), Some(HostCall::Time));
assert_eq!(call(0x13, 0), Some(HostCall::Errno));
assert_eq!(
call(0x30, BLOCK),
Some(HostCall::Elapsed {
block: u64::from(BLOCK)
})
);
assert_eq!(call(0x31, 0), Some(HostCall::TickFrequency));
assert_eq!(call(0x18, 0x2_0026), Some(HostCall::Exit { code: 0 }));
assert_eq!(call(0x18, 0x2_0023), Some(HostCall::Exit { code: 1 }));
for unserved in [0x07, 0x08, 0x0D, 0x0E, 0x0F, 0x12, 0x15, 0x16, 0x32, 0x100] {
assert_eq!(call(unserved, BLOCK), None, "{unserved:#x}");
}
assert_eq!(call(0x05, 0x3000_0000), None, "a block outside memory");
assert_eq!(
call(0x04, NAME + 11),
None,
"a string with no NUL in memory"
);
}
#[test]
fn an_extended_exit_carries_its_status() {
let exit = |reason, status| {
let laid = memory(
&[(u64::from(BLOCK), reason), (u64::from(BLOCK) + 4, status)],
&[],
);
decode(0x20, BLOCK, &mut reader(&laid))
};
assert_eq!(exit(0x2_0026, 3), Some(HostCall::Exit { code: 3 }));
assert_eq!(exit(0x2_0024, 3), Some(HostCall::Exit { code: 1 }));
}
#[test]
fn open_takes_each_mode_and_the_console_by_its_name() {
let open = |mode, name: &[u8]| {
let length = u32::try_from(name.len()).unwrap();
let laid = memory(
&[
(u64::from(BLOCK), NAME),
(u64::from(BLOCK) + 4, mode),
(u64::from(BLOCK) + 8, length),
],
&[(u64::from(NAME), name)],
);
decode(0x01, BLOCK, &mut reader(&laid))
};
let modes = [
OpenMode::Read,
OpenMode::ReadWrite,
OpenMode::Write,
OpenMode::WriteRead,
OpenMode::Append,
OpenMode::AppendRead,
];
for (mode, expected) in (0..12).zip(modes.iter().flat_map(|&mode| [mode, mode])) {
assert_eq!(
open(mode, b"log"),
Some(HostCall::Open {
path: u64::from(NAME),
length: 3,
mode: expected
}),
"mode {mode}"
);
}
assert_eq!(open(12, b"log"), None);
assert_eq!(open(0, b":tt"), Some(HostCall::OpenConsole { handle: 0 }));
assert_eq!(open(4, b":tt"), Some(HostCall::OpenConsole { handle: 1 }));
assert_eq!(open(8, b":tt"), Some(HostCall::OpenConsole { handle: 1 }));
assert!(matches!(open(0, b":tty"), Some(HostCall::Open { .. })));
}
}