From 2604f47bec44ecea8ddf61dae858f1fb268cdb48 Mon Sep 17 00:00:00 2001 From: husky Date: Thu, 20 Jul 2023 04:36:59 -0700 Subject: [PATCH] initial implementations for call and call-method --- .gitignore | 5 + Cargo.toml | 8 ++ examples/client_ppc.rs | 16 +++ src/arch/mod.rs | 20 +++ src/lib.rs | 268 +++++++++++++++++++++++++++++++++++++++++ src/ntstr.rs | 94 +++++++++++++++ src/tests.rs | 167 +++++++++++++++++++++++++ 7 files changed, 578 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.toml create mode 100644 examples/client_ppc.rs create mode 100644 src/arch/mod.rs create mode 100644 src/lib.rs create mode 100644 src/ntstr.rs create mode 100644 src/tests.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8398f69 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +/target +/Cargo.lock + +# as its usage is encouraged for testing architecture-specific code, do not include the .cargo directory +/.cargo \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..5990672 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "ofw" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/examples/client_ppc.rs b/examples/client_ppc.rs new file mode 100644 index 0000000..29248ed --- /dev/null +++ b/examples/client_ppc.rs @@ -0,0 +1,16 @@ +#![no_std] +#![no_main] + +use ofw::arch::EntryFunction; +use ofw::{call, service_result, ntstr, OFW_TRUE}; + +#[no_mangle] +#[link_section = ".text"] +extern "C" fn _start(_r3: u32, _r4: u32, entry: EntryFunction) -> isize { + let mut results = service_result!(1,1); + call!(entry, results, ntstr!("test"), 1, 1, ntstr!("test")); + if results.success { + let _missing: bool = results.rets[0] == OFW_TRUE; + } + 0 +} \ No newline at end of file diff --git a/src/arch/mod.rs b/src/arch/mod.rs new file mode 100644 index 0000000..ed2466b --- /dev/null +++ b/src/arch/mod.rs @@ -0,0 +1,20 @@ +// # Note to developers: +// architecture-specific code may be tested via the usage of a filein the following +// path: `/.cargo/config`. (where `/` is the root of the project) +// the file should contain the following: +// ``` +// [build] +// # for powerpc +// target = "powerpc-unknown-linux-gnu" +// ``` + +use crate::Args; + +/// # PowerPC +#[cfg(target_arch = "powerpc")] +pub type EntryFunction = extern "C" fn (*mut Args) -> i32; + +/// # x86_64 +/// note: x86_64 does not have an OpenFirmware implementation, this is for testing purposes only +#[cfg(target_arch = "x86_64")] +pub type EntryFunction = extern "C" fn (*mut Args) -> i32; \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..5c5efc8 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,268 @@ +#![no_std] + +/// # OFW_TRUE +/// a constant representing the OpenFirmware "true" value +pub const OFW_TRUE: i32 = -1; + +/// # OFW_FALSE +/// a constant representing the OpenFirmware "false" value +pub const OFW_FALSE: i32 = 0; + +/// # OFWBoolean +/// a type representing the OpenFirmware boolean type +/// can be either OFW_TRUE or OFW_FALSE +pub type OFWBoolean = i32; + +/// # PHandle +/// handle to a "package" in OpenFirmware +/// usually used to access properties +pub type PHandle = i32; + +/// # IHandle +/// handle to a package instance in OpenFirmware +/// usually used to call methods from a package +pub type IHandle = i32; + +use crate::arch::EntryFunction; +use crate::ntstr::NTSTR; + +/// # ntstr +/// module for handling null-terminated strings +pub mod ntstr; + +/// # arch +/// module for architecture-specific code +pub mod arch; + + +#[cfg(test)] +mod tests; + +/// # Args +/// a base structure for all calls to the OpenFirmware entry function, +/// shouldn't be used directly but rather used as a base for other "service" structures +/// an example "service struct" is as follows +/// ``` +/// use ofw::{Args, OFWBoolean}; +/// use ofw::ntstr::NTSTR; +/// /// # test +/// /// nargs: 1 +/// /// nrets: 1 +/// /// takes a service name string, and returns OFW_TRUE if the service is missing or OFW_FALSE if it is present +/// #[repr(C)] +/// pub struct TestService { +/// pub args: Args, +/// pub service: NTSTR, +/// pub missing: OFWBoolean, +/// } +/// ``` +#[repr(C)] +pub struct Args { + pub service: NTSTR, + // ieee 1275, 2.4.1 General conventions: "All numbers on the stack are signed integers, _32 bits[...]" + pub nrets: i32, + pub nargs: i32, + // inheriting structs will continue from here +} + +/// # ServiceResult +/// a generic structure for accessing the results of a service call +#[allow(dead_code)] +pub struct ServiceResult<'a> { + pub success: bool, + buf: &'static mut [i32], + pub rets: &'a [i32], +} + +/// # call! +/// a macro for calling OpenFirmware services +/// ## example +/// ``` +/// use ofw::{call, service_result, ServiceResult}; +/// use ofw::arch::EntryFunction; +/// let entry_fn: EntryFunction = unimplemented!("get the entry function from somewhere"); +/// // <# of args>, <# of rets> +/// let mut results: ServiceResult = service_result!(1, 1); +/// // , , <# of args>, <# of rets>, +/// call!(entry_fn, results, ntstr!("test"), 1, 1, ntstr!("test")); +/// if results.success { +/// let test_result: i32 = results.rets[0]; +/// } +/// ``` +#[macro_export] +macro_rules! call { + ($entry_fn:expr, $results:expr, $service:expr, $nargs:expr, $nrets:expr, $($args:expr),*) => { + #[allow(unused_assignments)] + { + let mut i = 0; + $( + $results.buf[i] = $args.into(); + i += 1; + )* + let success: bool = $crate::ofw_call($entry_fn, $service, $nargs, $nrets, $results.buf); + $results.success = success; + $results.rets = &$results.buf[$nargs as usize..]; + }}; +} + +/// # call_method! +/// a macro for calling package methods +/// ## example +/// ``` +/// use ofw::arch::EntryFunction; +/// use ofw::{call_method, method_service_result, ServiceResult}; +/// +/// let entry_fn: EntryFunction = unimplemented!("get the entry function from somewhere"); +/// let package_instance: i32 = unimplemented!("get the package instance from somewhere"); +/// // note the usage of the `method_service_result!` macro instead of `service_result!` +/// // this is required as the `call_method!` macro requires a +/// // slightly different ServiceResult struct configuration +/// // <# of args>, <# of rets> +/// let mut results: ServiceResult = method_service_result!(1, 1); +/// // , , , <# of args>, <# of rets>, +/// call_method!(entry_fn, results, package_instance, ntstr!("seek"), 2, 1, 0, 0); +/// if results.success { +/// let seek_result: i32 = results.rets[0]; +/// } +/// ``` +#[macro_export] +macro_rules! call_method { + ($entry_fn:expr, $package_instance:expr, $results:expr, $service:expr, $nargs:expr, $nrets:expr, $($args:expr),*) => { + #[allow(unused_assignments)] + { + let mut i = 0; + $( + $results.buf[i] = $args.into(); + i += 1; + )* + let success: bool = $crate::ofw_call_method($entry_fn, $package_instance, $service, $nargs, $nrets, $results.buf); + $results.success = success && $results.buf[$nargs as usize] != 0; + $results.rets = &$results.buf[$nargs as usize + 1..]; + }}; +} + +/// # service_result! +/// a macro for creating a ServiceResult struct, used with the `call!` macro +#[macro_export] +macro_rules! service_result { + ($nargs:expr, $nrets:expr) => { unsafe { + static mut BUF: [i32; $nargs + $nrets] = [0; $nargs + $nrets]; + let rets: &[i32] = &[]; + let results = $crate::ServiceResult { + success: false, + buf: &mut BUF, + rets, + }; + results + }}; +} + +/// # method_service_result! +/// a macro for creating a ServiceResult struct, used with the `call_method!` macro +#[macro_export] +macro_rules! method_service_result { + ($nargs:expr, $nrets:expr) => { unsafe { + static mut BUF: [i32; $nargs + $nrets + 1] = [0; $nargs + $nrets + 1]; + let rets: &[i32] = &[]; + let results = $crate::ServiceResult { + success: false, + buf: &mut BUF, + rets, + }; + results + }}; +} + +/// # ofw_call +/// a function to call any OpenFirmware-provided service +/// usage of the macro `call!` is preferred, but this function can be used directly +/// ## example +/// ``` +/// use ofw::arch::EntryFunction; +/// use ofw::{ntstr, ofw_call}; +/// let entry_fn: EntryFunction = unimplemented!("get the entry function from somewhere"); +/// let mut args: [i32; 2] = [ntstr!("test") as i32, 0]; +/// let was_service_successfully_called: bool = ofw_call(entry_fn, ntstr!("test"), 1, 1, &mut args); +/// let test_result: i32 = args[1]; +/// ``` +pub fn ofw_call(entry_fn: EntryFunction, service: NTSTR, num_args: i32, num_rets: i32, args_and_rets: &mut [i32]) -> bool { + #[repr(C)] + struct GenericArgs { + args: Args, + buffer: *mut i32, + } + let mut generic_args = GenericArgs { + args: Args { + service, + nrets: num_rets, + nargs: num_args, + }, + buffer: args_and_rets.as_mut_ptr(), + }; + + let result: bool; + + #[cfg(target_arch = "powerpc")] + { + result = entry_fn(&mut generic_args as *mut _ as *mut Args) == OFW_TRUE; + } + #[cfg(target_arch = "x86_64")] + { + result = entry_fn(&mut generic_args as *mut _ as *mut Args) == OFW_TRUE; + } + + result +} + +/// # ofw_call_method +/// a function to call a method provided by a package instance +/// usage of the `call_method!` macro is preferred, but this function can be used directly +/// ## example +/// ``` +/// // we will be calling the "seek" method on an imaginary block device +/// // the seek method takes 2 arguments, and returns 1 value +/// // addr_hi, addr_lo -- okay? +/// // however, we must add another cell in order to account for the catch-result +/// // (see ieee 1275, 6.3.2.2 Device tree) +/// use ofw::{ntstr, ofw_call_method}; +/// // addr_hi, addr_lo, catch-result, okay? +/// let mut args: [i32; 4] = [0, 0, 0, 0]; +/// // note that num_args and num_rets are not the same as the length of args, this is intentional +/// let was_service_successfully_called: bool = ofw_call_method(entry_fn, instance, ntstr!("seek"), 2, 1, &mut args); +/// if was_service_successfully_called && args[2] != 0 { +/// let seek_result: i32 = args[3]; +/// } +/// ``` +pub fn ofw_call_method(entry_fn: EntryFunction, ihandle: IHandle, method: NTSTR, num_args: i32, num_rets: i32, args_and_rets: &mut [i32]) -> bool { + #[repr(C)] + struct CallMethodArgs { + args: Args, + method: NTSTR, + ihandle: IHandle, + buffer: *mut i32, + } + + let mut call_method_args = CallMethodArgs { + args: Args { + service: ntstr!("call-method"), + nrets: num_rets + 1, + nargs: num_args + 2, + }, + method, + ihandle, + buffer: args_and_rets.as_mut_ptr(), + }; + + let result: bool; + + #[cfg(target_arch = "powerpc")] + { + result = entry_fn(&mut call_method_args as *mut _ as *mut Args) == OFW_TRUE; + } + #[cfg(target_arch = "x86_64")] + { + result = entry_fn(&mut call_method_args as *mut _ as *mut Args) == OFW_TRUE; + } + + result +} \ No newline at end of file diff --git a/src/ntstr.rs b/src/ntstr.rs new file mode 100644 index 0000000..314e344 --- /dev/null +++ b/src/ntstr.rs @@ -0,0 +1,94 @@ +use core::fmt::{Debug, Display, Formatter, Write}; + +/// # ntstr! +/// macro for creating a null-terminated string +/// ## example +/// ``` +/// use ofw::ntstr; +/// let ntstr = ntstr!("Hello, world!"); +/// ``` +/// becomes +/// ``` +/// let ntstr = ofw::ntstr::NTSTR::new(b"Hello, world!\0".as_ptr()); +/// ``` +#[macro_export] +macro_rules! ntstr { + ($str:expr) => { + $crate::ntstr::NTSTR::new(concat!($str, "\0").as_ptr()) + }; +} + +/// # NTSTR +/// a null-terminated string, defined as a pointer to an array of u8 characters ending with a null byte +/// not mutable +#[repr(C)] +pub struct NTSTR { + ptr: *const u8, +} + +impl From for NTSTR { + fn from(ptr: usize) -> Self { + Self { ptr: ptr as *const u8 } + } +} + +impl From<*const u8> for NTSTR { + fn from(ptr: *const u8) -> Self { + Self { ptr } + } +} + +#[allow(clippy::from_over_into)] +impl Into for NTSTR { + fn into(self) -> i32 { + self.ptr as i32 + } +} + +impl NTSTR { + pub const fn new(ptr: *const u8) -> Self { + Self { ptr } + } + + fn print(&self, f: &mut Formatter<'_>) -> core::fmt::Result { + let mut i = 0; + loop { + let a = unsafe { *self.ptr.add(i) }; + if a == 0 { + break; + } + f.write_char(a as char)?; + i += 1; + } + Ok(()) + } +} + +impl PartialEq for NTSTR { + fn eq(&self, other: &Self) -> bool { + let mut i = 0; + loop { + let a = unsafe { *self.ptr.add(i) }; + let b = unsafe { *other.ptr.add(i) }; + if a != b { + return false; + } + if a == 0 { + return true; + } + i += 1; + } + } +} + +impl Debug for NTSTR { + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { + self.print(f) + } +} + +impl Display for NTSTR { + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { + self.print(f) + } +} \ No newline at end of file diff --git a/src/tests.rs b/src/tests.rs new file mode 100644 index 0000000..54387c2 --- /dev/null +++ b/src/tests.rs @@ -0,0 +1,167 @@ +use crate::arch::EntryFunction; +use crate::{Args, call, call_method, IHandle, method_service_result, ntstr, OFW_FALSE, OFW_TRUE, service_result}; +use crate::ntstr::NTSTR; + +#[test] +fn ntstr_macro() { + let a = ntstr!("Hello, world!"); + let b = NTSTR::new(b"Hello, world!\0".as_ptr()); + assert_eq!(a, b); +} + +#[test] +fn call_ofw() { + pub extern "C" fn test_entry_function(args: *mut Args) -> i32 { + #[repr(C)] + struct GenericArgs { + args: Args, + buffer: *mut i32, + } + let args = unsafe { &mut *(args as *mut GenericArgs) }; + assert_eq!(args.args.service, ntstr!("test")); + assert_eq!(args.args.nargs, 1); + assert_eq!(args.args.nrets, 1); + // 1 offset from args in buffer + unsafe { *(args.buffer.offset(1)) = OFW_FALSE }; // set "missing" to false + OFW_TRUE + } + + let entry_fn: EntryFunction = test_entry_function; + let mut results = service_result!(1,1); + call!(entry_fn, results, ntstr!("test"), 1, 1, ntstr!("test")); + assert!(results.success); + assert_eq!(results.rets[0], OFW_FALSE); +} + +#[test] +fn call_ofw_missing() { + pub extern "C" fn test_entry_function(args: *mut Args) -> i32 { + #[repr(C)] + struct GenericArgs { + args: Args, + buffer: *mut i32, + } + let args = unsafe { &mut *(args as *mut GenericArgs) }; + assert_eq!(args.args.service, ntstr!("test")); + assert_eq!(args.args.nargs, 1); + assert_eq!(args.args.nrets, 1); + // 1 offset from args in buffer + unsafe { *(args.buffer.offset(1)) = OFW_TRUE }; // set "missing" to true + OFW_TRUE + } + + let entry_fn: EntryFunction = test_entry_function; + let mut results = service_result!(1,1); + call!(entry_fn, results, ntstr!("test"), 1, 1, ntstr!("aioshjfoahwfi")); + assert!(results.success); + assert_eq!(results.rets[0], OFW_TRUE); +} + +#[test] +fn call_ofw_failure() { + pub extern "C" fn test_entry_function(args: *mut Args) -> i32 { + #[repr(C)] + struct GenericArgs { + args: Args, + buffer: *mut i32, + } + let args = unsafe { &mut *(args as *mut GenericArgs) }; + assert_eq!(args.args.service, ntstr!("this-method-does-not-exist")); + assert_eq!(args.args.nargs, 1); + assert_eq!(args.args.nrets, 1); + OFW_FALSE + } + + let entry_fn: EntryFunction = test_entry_function; + let mut results = service_result!(1,1); + call!(entry_fn, results, ntstr!("this-method-does-not-exist"), 1, 1, ntstr!("aioshjfoahwfi")); + assert!(!results.success); +} + +#[test] +fn call_method_ofw() { + pub extern "C" fn test_entry_function(args: *mut Args) -> i32 { + #[repr(C)] + struct CallMethodArgs { + args: Args, + method: NTSTR, + ihandle: IHandle, + buffer: *mut i32, + } + let args = unsafe { &mut *(args as *mut CallMethodArgs) }; + assert_eq!(args.args.service, ntstr!("call-method")); + assert_eq!(args.method, ntstr!("seek")); + assert_eq!(args.ihandle, 0); + assert_eq!(args.args.nargs, 2 + 2); + assert_eq!(args.args.nrets, 1 + 1); + // 2 offset from args in buffer + unsafe { *(args.buffer.offset(2)) = 1 }; // set catch-result to non-zero + unsafe { *(args.buffer.offset(2 + 1)) = OFW_TRUE }; // set okay? to true + OFW_TRUE + } + + let entry_fn: EntryFunction = test_entry_function; + let ih: IHandle = 0; + let mut results = method_service_result!(2,1); + call_method!(entry_fn, ih, results, ntstr!("seek"), 2, 1, 0, 0); + assert!(results.success); + assert_eq!(results.rets[0], OFW_TRUE); +} + +#[test] +fn call_method_ofw_seek_failure() { + pub extern "C" fn test_entry_function(args: *mut Args) -> i32 { + #[repr(C)] + struct CallMethodArgs { + args: Args, + method: NTSTR, + ihandle: IHandle, + buffer: *mut i32, + } + let args = unsafe { &mut *(args as *mut CallMethodArgs) }; + assert_eq!(args.args.service, ntstr!("call-method")); + assert_eq!(args.method, ntstr!("seek")); + assert_eq!(args.ihandle, 0); + assert_eq!(args.args.nargs, 2 + 2); + assert_eq!(args.args.nrets, 1 + 1); + // 2 offset from args in buffer + unsafe { *(args.buffer.offset(2)) = 1 }; // set catch-result to non-zero + unsafe { *(args.buffer.offset(2 + 1)) = OFW_FALSE }; // set okay? to false + OFW_TRUE + } + + let entry_fn: EntryFunction = test_entry_function; + let ih: IHandle = 0; + let mut results = method_service_result!(2,1); + call_method!(entry_fn, ih, results, ntstr!("seek"), 2, 1, 0, 0); + assert!(results.success); + assert_eq!(results.rets[0], OFW_FALSE); +} + +#[test] +fn call_method_ofw_failure() { + pub extern "C" fn test_entry_function(args: *mut Args) -> i32 { + #[repr(C)] + struct CallMethodArgs { + args: Args, + method: NTSTR, + ihandle: IHandle, + buffer: *mut i32, + } + let args = unsafe { &mut *(args as *mut CallMethodArgs) }; + assert_eq!(args.args.service, ntstr!("call-method")); + assert_eq!(args.method, ntstr!("asiofhawoptgh")); + assert_eq!(args.ihandle, 0); + assert_eq!(args.args.nargs, 2 + 2); + assert_eq!(args.args.nrets, 1 + 1); + // 2 offset from args in buffer + unsafe { *(args.buffer.offset(2)) = 0 }; // set catch-result to 0 (caught error, package doesn't have this method) + OFW_TRUE + } + + let entry_fn: EntryFunction = test_entry_function; + let ih: IHandle = 0; + let mut results = method_service_result!(2,1); + call_method!(entry_fn, ih, results, ntstr!("asiofhawoptgh"), 2, 1, 0, 0); + assert!(!results.success); +} \ No newline at end of file