use std::{num::NonZeroU32, time::SystemTime};
use crate::{CoreInterface, Error, MemoryInterface, RegisterValue};
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum SemihostingCommand {
ExitSuccess,
ExitError(ExitErrorDetails),
GetCommandLine(GetCommandLineRequest),
Open(OpenRequest),
Close(CloseRequest),
WriteConsole(WriteConsoleRequest),
Write(WriteRequest),
Read(ReadRequest),
Seek(SeekRequest),
FileLength(FileLengthRequest),
Remove(RemoveRequest),
Rename(RenameRequest),
Time(TimeRequest),
Errno(ErrnoRequest),
Unknown(UnknownCommandDetails),
}
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
pub struct ExitErrorDetails {
pub reason: u32,
pub exit_status: Option<u32>,
pub subcode: Option<u32>,
}
impl std::fmt::Display for ExitErrorDetails {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
const REASON_APPLICATION_EXIT: u32 = 0x20026;
const EXIT_SUCCESS: u32 = 0;
const EXIT_ABORTED: u32 = 134;
match self.reason {
REASON_APPLICATION_EXIT => match self.exit_status {
Some(EXIT_SUCCESS) => write!(f, "success")?,
Some(EXIT_ABORTED) => write!(f, "exit_status: aborted")?,
Some(other) => write!(f, "exit_status: {other:#x}")?,
None => write!(f, "exit_status: unknown")?,
},
reason => {
write!(f, "reason: {reason:#x}")?;
if let Some(exit_status) = self.exit_status {
write!(f, ", exit_status: {exit_status}")?;
}
}
}
if let Some(subcode) = self.subcode {
write!(f, ", subcode: {subcode:#x}")?;
}
Ok(())
}
}
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
pub struct UnknownCommandDetails {
pub operation: u32,
pub parameter: u32,
}
impl UnknownCommandDetails {
pub fn get_buffer(&self, core: &mut dyn CoreInterface) -> Result<Buffer, Error> {
Buffer::from_block_at(core, self.parameter)
}
pub fn write_status(&self, core: &mut dyn CoreInterface, status: i32) -> Result<(), Error> {
write_status(core, status)
}
}
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
pub struct GetCommandLineRequest(Buffer);
impl GetCommandLineRequest {
pub fn write_command_line_to_target(
&self,
core: &mut dyn CoreInterface,
cmdline: &str,
) -> Result<(), Error> {
let mut buf = cmdline.to_owned().into_bytes();
buf.push(0);
self.0.write(core, &buf)?;
write_status(core, 0)?;
Ok(())
}
}
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
pub struct OpenRequest {
path: ZeroTerminatedString,
mode: &'static str,
}
impl OpenRequest {
pub fn path(&self, core: &mut dyn CoreInterface) -> Result<String, Error> {
self.path.read(core)
}
pub fn mode(&self) -> &'static str {
self.mode
}
pub fn respond_with_handle(
&self,
core: &mut dyn CoreInterface,
handle: NonZeroU32,
) -> Result<(), Error> {
write_status(core, handle.get() as i32)
}
}
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
pub struct CloseRequest {
handle: u32,
}
impl CloseRequest {
pub fn file_handle(&self) -> u32 {
self.handle
}
pub fn success(&self, core: &mut dyn CoreInterface) -> Result<(), Error> {
write_status(core, 0)
}
}
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
pub struct WriteConsoleRequest(pub(crate) ZeroTerminatedString);
impl WriteConsoleRequest {
pub fn read(&self, core: &mut crate::Core<'_>) -> Result<String, Error> {
self.0.read(core)
}
}
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
pub struct WriteRequest {
handle: u32,
bytes: u32,
len: u32,
}
impl WriteRequest {
pub fn file_handle(&self) -> u32 {
self.handle
}
pub fn read(&self, core: &mut crate::Core<'_>) -> Result<Vec<u8>, Error> {
let mut buf = vec![0u8; self.len as usize];
core.read(self.bytes as u64, &mut buf)?;
Ok(buf)
}
pub fn write_status(&self, core: &mut dyn CoreInterface, status: i32) -> Result<(), Error> {
write_status(core, status)
}
}
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
pub struct ReadRequest {
handle: u32,
bytes: u32,
len: u32,
}
impl ReadRequest {
pub fn file_handle(&self) -> u32 {
self.handle
}
pub fn bytes_to_read(&self) -> u32 {
self.len
}
pub fn write_buffer_to_target(
&self,
core: &mut crate::Core<'_>,
buf: &[u8],
) -> Result<(), Error> {
assert!(buf.len() <= self.len as usize);
if !buf.is_empty() {
core.write(self.bytes as u64, buf)?;
}
let status = match buf.len() {
0 => self.len as i32,
len if len == self.len as usize => 0,
len => len as i32,
};
write_status(core, status)
}
}
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
pub struct SeekRequest {
handle: u32,
pos: u32,
}
impl SeekRequest {
pub fn file_handle(&self) -> u32 {
self.handle
}
pub fn position(&self) -> u32 {
self.pos
}
pub fn success(&self, core: &mut dyn CoreInterface) -> Result<(), Error> {
write_status(core, 0)
}
}
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
pub struct FileLengthRequest {
handle: u32,
}
impl FileLengthRequest {
pub fn file_handle(&self) -> u32 {
self.handle
}
pub fn write_length(&self, core: &mut dyn CoreInterface, len: i32) -> Result<(), Error> {
write_status(core, len)
}
}
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
pub struct RemoveRequest {
path: ZeroTerminatedString,
}
impl RemoveRequest {
pub fn path(&self, core: &mut dyn CoreInterface) -> Result<String, Error> {
self.path.read(core)
}
pub fn success(&self, core: &mut dyn CoreInterface) -> Result<(), Error> {
write_status(core, 0)
}
}
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
pub struct RenameRequest {
from_path: ZeroTerminatedString,
to_path: ZeroTerminatedString,
}
impl RenameRequest {
pub fn from_path(&self, core: &mut dyn CoreInterface) -> Result<String, Error> {
self.from_path.read(core)
}
pub fn to_path(&self, core: &mut dyn CoreInterface) -> Result<String, Error> {
self.to_path.read(core)
}
pub fn success(&self, core: &mut dyn CoreInterface) -> Result<(), Error> {
write_status(core, 0)
}
}
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
pub struct TimeRequest {}
impl TimeRequest {
pub fn write_time(&self, core: &mut dyn CoreInterface, value: u32) -> Result<(), Error> {
write_status(core, value as i32)
}
pub fn write_current_time(&self, core: &mut dyn CoreInterface) -> Result<(), Error> {
let duration = SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.expect("Failed to get system time");
self.write_time(core, duration.as_secs() as u32)
}
}
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
pub struct ErrnoRequest {}
impl ErrnoRequest {
pub fn write_errno(&self, core: &mut dyn CoreInterface, errno: i32) -> Result<(), Error> {
write_status(core, errno)
}
}
fn write_status(core: &mut dyn CoreInterface, value: i32) -> Result<(), crate::Error> {
let reg = core.registers().get_argument_register(0).unwrap();
core.write_core_reg(reg.into(), RegisterValue::U32(value as u32))?;
Ok(())
}
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
pub struct Buffer {
buffer_location: u32, address: u32, len: u32, }
impl Buffer {
pub fn from_block_at(
core: &mut dyn CoreInterface,
block_addr: u32,
) -> Result<Self, crate::Error> {
let mut block: [u32; 2] = [0, 0];
core.read_32(block_addr as u64, &mut block)?;
Ok(Self {
buffer_location: block_addr,
address: block[0],
len: block[1],
})
}
pub fn read(&self, core: &mut dyn CoreInterface) -> Result<Vec<u8>, Error> {
let mut buf = vec![0u8; self.len as usize];
core.read(self.address as u64, &mut buf[..])?;
Ok(buf)
}
pub fn write(&self, core: &mut dyn CoreInterface, buf: &[u8]) -> Result<(), Error> {
if buf.len() > self.len as usize {
return Err(Error::Other("buffer not large enough".to_string()));
}
if buf.last() != Some(&0) {
return Err(Error::Other("last byte of buffer must be 0".to_string()));
}
core.write_8(self.address as u64, buf)?;
let block: [u32; 2] = [self.address, (buf.len() - 1) as u32];
core.write_32(self.buffer_location as u64, &block)?;
Ok(())
}
}
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
pub(crate) struct ZeroTerminatedString {
pub address: u32,
pub length: Option<u32>,
}
impl ZeroTerminatedString {
pub fn read(&self, core: &mut dyn CoreInterface) -> Result<String, Error> {
let mut bytes = Vec::new();
if let Some(len) = self.length {
bytes = vec![0; len as usize];
core.read(self.address as u64, &mut bytes)?;
} else {
let mut buf = [0; 128];
let mut from = self.address as u64;
loop {
core.read(from, &mut buf)?;
if let Some(end) = buf.iter().position(|&x| x == 0) {
bytes.extend_from_slice(&buf[..end]);
break;
}
bytes.extend_from_slice(&buf);
from += buf.len() as u64;
}
}
Ok(String::from_utf8_lossy(&bytes).to_string())
}
}
pub fn decode_semihosting_syscall(
core: &mut dyn CoreInterface,
) -> Result<SemihostingCommand, Error> {
let operation: u32 = core
.read_core_reg(core.registers().get_argument_register(0).unwrap().id())?
.try_into()?;
let parameter: u32 = core
.read_core_reg(core.registers().get_argument_register(1).unwrap().id())?
.try_into()?;
tracing::debug!("Semihosting found r0={operation:#x} r1={parameter:#x}");
const SYS_GET_CMDLINE: u32 = 0x15;
const SYS_EXIT: u32 = 0x18;
const SYS_EXIT_EXTENDED: u32 = 0x20;
const SYS_EXIT_ADP_STOPPED_APPLICATIONEXIT: u32 = 0x20026;
const SYS_OPEN: u32 = 0x01;
const SYS_CLOSE: u32 = 0x02;
const SYS_WRITEC: u32 = 0x03;
const SYS_WRITE0: u32 = 0x04;
const SYS_WRITE: u32 = 0x05;
const SYS_READ: u32 = 0x06;
const SYS_SEEK: u32 = 0x0a;
const SYS_FLEN: u32 = 0x0c;
const SYS_REMOVE: u32 = 0x0e;
const SYS_RENAME: u32 = 0x0f;
const SYS_TIME: u32 = 0x11;
const SYS_ERRNO: u32 = 0x13;
Ok(match (operation, parameter) {
(SYS_EXIT, SYS_EXIT_ADP_STOPPED_APPLICATIONEXIT) => SemihostingCommand::ExitSuccess,
(SYS_EXIT, reason) => SemihostingCommand::ExitError(ExitErrorDetails {
reason,
exit_status: None,
subcode: None,
}),
(SYS_EXIT_EXTENDED, block_address) => {
let mut buf = [0u32; 2];
core.read_32(block_address as u64, &mut buf)?;
let reason = buf[0];
let subcode = buf[1];
match (reason, subcode) {
(SYS_EXIT_ADP_STOPPED_APPLICATIONEXIT, 0) => SemihostingCommand::ExitSuccess,
(SYS_EXIT_ADP_STOPPED_APPLICATIONEXIT, exit_status) => {
SemihostingCommand::ExitError(ExitErrorDetails {
reason,
exit_status: Some(exit_status),
subcode: None,
})
}
(reason, subcode) => SemihostingCommand::ExitError(ExitErrorDetails {
reason,
exit_status: None,
subcode: Some(subcode),
}),
}
}
(SYS_GET_CMDLINE, block_address) => {
write_status(core, -1)?;
SemihostingCommand::GetCommandLine(GetCommandLineRequest(Buffer::from_block_at(
core,
block_address,
)?))
}
(SYS_OPEN, pointer) => {
let [string, mode, str_len] = param(core, pointer)?;
write_status(core, -1)?;
SemihostingCommand::Open(OpenRequest {
path: ZeroTerminatedString {
address: string,
length: Some(str_len),
},
mode: match mode {
0 => "r",
1 => "rb",
2 => "r+",
3 => "r+b",
4 => "w",
5 => "wb",
6 => "w+",
7 => "w+b",
8 => "a",
9 => "ab",
10 => "a+",
11 => "a+b",
_ => "unknown",
},
})
}
(SYS_CLOSE, pointer) => {
let [handle] = param(core, pointer)?;
write_status(core, -1)?;
SemihostingCommand::Close(CloseRequest { handle })
}
(SYS_WRITEC, pointer) => {
SemihostingCommand::WriteConsole(WriteConsoleRequest(ZeroTerminatedString {
address: pointer,
length: Some(1),
}))
}
(SYS_WRITE0, pointer) => {
SemihostingCommand::WriteConsole(WriteConsoleRequest(ZeroTerminatedString {
address: pointer,
length: None,
}))
}
(SYS_WRITE, pointer) => {
let [handle, bytes, len] = param(core, pointer)?;
write_status(core, -1)?;
SemihostingCommand::Write(WriteRequest { handle, bytes, len })
}
(SYS_READ, pointer) => {
let [handle, bytes, len] = param(core, pointer)?;
write_status(core, -1)?;
SemihostingCommand::Read(ReadRequest { handle, bytes, len })
}
(SYS_SEEK, pointer) => {
let [handle, pos] = param(core, pointer)?;
write_status(core, -1)?;
SemihostingCommand::Seek(SeekRequest { handle, pos })
}
(SYS_FLEN, pointer) => {
let [handle] = param(core, pointer)?;
write_status(core, -1)?;
SemihostingCommand::FileLength(FileLengthRequest { handle })
}
(SYS_REMOVE, pointer) => {
let [path, len] = param(core, pointer)?;
write_status(core, -1)?;
SemihostingCommand::Remove(RemoveRequest {
path: ZeroTerminatedString {
address: path,
length: Some(len),
},
})
}
(SYS_RENAME, pointer) => {
let [from_path, from_len, to_path, to_len] = param(core, pointer)?;
write_status(core, -1)?;
SemihostingCommand::Rename(RenameRequest {
from_path: ZeroTerminatedString {
address: from_path,
length: Some(from_len),
},
to_path: ZeroTerminatedString {
address: to_path,
length: Some(to_len),
},
})
}
(SYS_TIME, 0) => SemihostingCommand::Time(TimeRequest {}),
(SYS_ERRNO, 0) => SemihostingCommand::Errno(ErrnoRequest {}),
_ => {
write_status(core, -1)?;
tracing::debug!(
"Unknown semihosting operation={operation:04x} parameter={parameter:04x}"
);
SemihostingCommand::Unknown(UnknownCommandDetails {
operation,
parameter,
})
}
})
}
fn param<const N: usize>(
core: &mut dyn CoreInterface,
pointer: u32,
) -> Result<[u32; N], crate::Error> {
let mut buf = [0; N];
core.read_32(pointer as u64, &mut buf)?;
Ok(buf)
}