diff --git a/Cargo.lock b/Cargo.lock index 581004888..d3ced2942 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -335,6 +335,7 @@ name = "bao-console" version = "0.1.0" dependencies = [ "cram-hal-service", + "cramium-api", "cramium-hal", "log", "num-derive 0.4.2", @@ -352,6 +353,8 @@ name = "bao-video" version = "0.1.0" dependencies = [ "cram-hal-service", + "cramium-api", + "cramium-emu", "cramium-hal", "libm 0.2.8", "locales", @@ -971,6 +974,7 @@ name = "cram-hal-service" version = "0.1.0" dependencies = [ "bitfield", + "cramium-api", "cramium-hal", "log", "num-derive 0.4.2", @@ -1017,12 +1021,43 @@ dependencies = [ "xous-api-ticktimer", ] +[[package]] +name = "cramium-api" +version = "0.1.0" +dependencies = [ + "num-derive 0.4.2", + "num-traits", + "rkyv 0.8.8", + "xous 0.9.64 (registry+https://github.com/rust-lang/crates.io-index)", + "xous-api-names", + "xous-ipc 0.10.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "cramium-emu" +version = "0.1.0" +dependencies = [ + "cramium-api", + "log", + "minifb", + "num-derive 0.4.2", + "num-traits", + "rand 0.8.5", + "rand_core 0.6.4", + "rkyv 0.8.8", + "ux-api", + "xous 0.9.64 (registry+https://github.com/rust-lang/crates.io-index)", + "xous-api-names", + "xous-ipc 0.10.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "cramium-hal" version = "0.1.0" dependencies = [ "bitfield", "bitflags 1.3.2", + "cramium-api", "either", "log", "num-derive 0.4.2", @@ -3017,6 +3052,7 @@ dependencies = [ "armv7", "atsama5d27", "com_rs 0.1.0 (git+https://github.com/betrusted-io/com_rs?branch=main)", + "cramium-api", "cramium-hal", "crc 1.8.1", "curve25519-dalek-loader", @@ -3195,7 +3231,9 @@ name = "modals" version = "0.1.0" dependencies = [ "bit_field", + "blitstr2", "cram-hal-service", + "cramium-emu", "cramium-hal", "gam", "locales", @@ -6889,6 +6927,7 @@ name = "xous-swapper" version = "0.1.0" dependencies = [ "aes-gcm-siv", + "cramium-api", "cramium-hal", "loader", "log", diff --git a/Cargo.toml b/Cargo.toml index 92906270c..a1c1bca3b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -114,6 +114,8 @@ members = [ "services/usb-cramium", "services/bao-console", "libs/ux-api", + "services/cramium-emu", + "libs/cramium-api", ] resolver = "2" diff --git a/README-baochip.md b/README-baochip.md new file mode 100644 index 000000000..bf7a7d7b6 --- /dev/null +++ b/README-baochip.md @@ -0,0 +1,96 @@ +# "Baochip" / "Cramium" Series API Organization & Glossary + +## API Organization + +The Baochip platform has the following API structure. Note that +`cramium` is the original code name for the target chip, which after +two years of extensive code development migrated to the `baochip` +brand name. At the moment the misnaming stands, due to the amount +of code that carries the legacy name. + +`libs/cramium-api`: contains hardware-abstracted API code to the hardware +layers (traits, some constants, enums, etc.). For example, "here is +an IO trait that lets you configure and set GPIO pins", which could +then be implemented in hardware or emulation. The APIs aren't entirely +generic across all SoCs because they are tweaked to accommodate +quirks of the cramium SoC. + +`libs/cramium-hal`: contains board-specific driver codes that do not +require persistent services to be started. Possibly misnamed because +it also includes not just cramium-chip items, but also e.g. peripherals +to the cramium SoC, such as the PMIC and camera. + +`services/cramium-hal-service`: contains the `main` process that manages +shared resources, such as UDMA, IO pins, IFRAM. These drivers cannot +be delegated to a `lib` crate because there can be only one instance +of these resources, and instead we have to dynamically allocate +access to these through IPC messaging. + +`services/cramium-emu`: hosted mode emulation of things in +cramium-hal-service. + +`services/bao-video`: contains a `main` process that integrates +the camera and OLED driver. This means that all graphic drawing +primitives also interface with this crate. These are condensed +into a single process space to speed up execution, and kept +separate from cramium-hal-services because we want the video +services to not be blocked by, for example, a thread that is +handling I2C things. + +## Glossary + +The history of names in this SoC are complicated because the drivers +were developed in parallel with the legal entity that makes the chip +being formed and funded. Thus the name of the company and chip don't +even match compared to the final product. Here is a glossary of terms +you may encounter in this project. + +`daric`: Code name for the MPW (multi-project-wafer) test SoC. It was +also supposed to be a product name but apparently this was already +taken and thus while lots of code uses the name, it can't be used +as a product name. + +`cramium`: Putative name for a company that was supposed to be started +to sell what was the `daric` SoC, and was assumed to be the brand +name for the chip. As of writing the entity may still not exist. + +`crossbar`: The parent company that was supposed to spin out `cramium`. +Most of the OSS SoC code has its copyright assigned to this organization; +so, this is probably the most relevant legal entity even though its +name doesn't appear in marketing materials. + +`baochip`: The name of a new company formed to market an OSS- and Xous-focused +variant of the `daric` chip. This is the relevant legal organization in terms +of purchasing chips and systems related to this code base; thus from a user +perspective this is the brand used in Xous. + +`baochip zero`: Actual brand name of the chip. Internally code named `nto` (which +stands for "Next Tape Out" - look, I don't come up with half of these names, +I just use them). + +`cramium-soc`: The name of the Xous target that is actually the `baochip zero`. +It is also used as a target for "pure SoC" simulations; i.e., verilog RTL +simulations where the peripherals are entirely virtual test benches. + +`cramium-fpga`: A placeholder target that is meant to be a down-sized +FPGA implementation of the SoC. This will likely be an Artix-7 100T +implementation, targeting the Digilent Arty A7. + +`baosec`: Internal code name of a board that contains the `baochip zero` with +a USB security token form factor. This is likewise the name of the xtask target +to build images for this board. Contains a camera, display, storage, USB +and buttons. `board-baosec` is the flag for the board target, and `loader-baosec` +is an analogous flag but for no-std environments. `hosted-baosec` likewise +is for `baosec` but running on an x86 host (for UX development). + +`baosec-emu`: xtask target for hosted mode emulation for `baosec`. `cramium-emu` +contains hosted mode shims for the `baosec` target. `cramium-emu` mis-named and +should probably be renamed to `baosec-emu`. + +`baosor`: Internal code name of a board that contains the `baochip zero` with +the Precursor form factor. `board-basor` is the flag for the board target, +and `loader-baosor` is an analogous flag but for no-std environments. + +`dabao`: Internal codename of a breakout board for the `baochip zero` that +contains nothing more than the chip, a power regulator and a USB connector. + diff --git a/README.md b/README.md index ba7598ef8..fc534e510 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,8 @@ Core files for the Xous microkernel operating system. You might find this [wiki](https://github.com/betrusted-io/betrusted-wiki/wiki) handy, as well as the [Xous Book](https://betrusted.io/xous-book/). +Users targeting Baochip platforms should also familiarize with the [Boachip API structure and glossary](./README-baochip.md) as the platform has several targets and gone through numerous name changes. + This repository contains everything necessary to build the Xous kernel from source. It consists of the following projects: @@ -23,6 +25,15 @@ from source. It consists of the following projects: - Some system packages are needed, which can be installed with `sudo apt install libssl-dev libxkbcommon-dev` or similar - If you receive an error about `feature resolver is required`, try installing a newer version of `rustc` and `cargo` via [rustup](https://rustup.rs) +## Building Documentation +A flag of `--feature doc-deps` must be passed when running `cargo doc`, like this: + +`cargo doc --no-deps --feature doc-deps` + +This flag is required because Xous requires a target board to be specified in +all build configurations. `doc-deps` specifies a set of dummy dependencies +that satisfy board requirements for the purpose of building documentation. + ## Local-vs-crates.io Verification By default the `xtask` resolver runs a check to confirm that your local files match the ones referenced in `crates.io`. For a handful of core crates, the @@ -59,13 +70,13 @@ will begin scrolling in your terminal. ### Hosted Mode UI navigation -| Precursor | Host | -| --------- | ---- | -| D-pad middle button | Home | -| D-pad up | up arrow | -| D-pad down | down arrow | -| D-pad left | left arrow | -| D-pad right | right arrow | +| Precursor | Host | +| ------------------- | ----------- | +| D-pad middle button | Home | +| D-pad up | up arrow | +| D-pad down | down arrow | +| D-pad left | left arrow | +| D-pad right | right arrow | ## Quickstart using an emulator diff --git a/RELEASE-v0.9.md b/RELEASE-v0.9.md index 833037231..174506d95 100644 --- a/RELEASE-v0.9.md +++ b/RELEASE-v0.9.md @@ -506,7 +506,13 @@ perform the Xous firmware upgrade. This requires running manual update commands, - Number of pages to allocate could be automated inside the `xous-ipc` crate, but this will be delegated to a future time with a new API. - Most applications were forward-ported, except for `app-loader` which has already bit-rotted for other reasons and may be deprecated because we can use "swap" space to effectively do app loading (to be made available in future hardware revs) - Serialization is a bit easier now with the new `rkyv`, we don't have to track a `pos` explicitly; all of the archival metadata is now stuck at the end of the archive, so all you need to know is the final length of the serialized record and you're done. - +- Refactor `blitstr2` to be in its own `libs` crate, allowing it to be re-used across multiple configurations +- Clean up the board vs soc abstraction. There are still places that don't adhere to this distinction, but: + - A `soc` flag specifies dependencies that are generic to a system-on-chip (SoC). For example, the locations of registers, or the extents of memory regions contained in the `soc`. + - A `board` flag specifies dependencies that are specific to a board. For example, the resolution of displays, pin mappings to peripherals, and sizes of external memory. Typically, a board assumes a `soc`, so both a `board-*` and `*-soc` set of flags are required to fully specify a build. + - Precursor (the first target for Xous) did not hold to this abstraction and conflated the two, so it is a special case in the build system. + - "Hosted" mode emulations are considered to be a `board` target; the `soc` is assumed to be the host (linux, windows, etc.) +- Since all builds require a `board` specifier, documentation now requires a `doc-deps` flag to be passed. This effectively specifies a set of dummy board dependencies so that the documentation can build. Here is the recommended command line for building docs: `cargo doc --no-deps --feature doc-deps` ## Roadmap - Lots of testing and bug fixes diff --git a/kernel/Cargo.toml b/kernel/Cargo.toml index 7074208b1..12e3510e0 100644 --- a/kernel/Cargo.toml +++ b/kernel/Cargo.toml @@ -44,12 +44,17 @@ critical-section = "1.1.1" unexpected_cfgs = { level = "warn", check-cfg = ['cfg(ci)', 'cfg(baremetal)'] } [features] -cramium-soc = ["utralib/cramium-soc", "cramium-hal", "rand_chacha", "raw-trng"] +cramium-soc = [ + "utralib/cramium-soc", + "cramium-hal/cramium-soc", + "rand_chacha", + "raw-trng", +] verilator-only = [] cramium-fpga = ["utralib/cramium-fpga", "rand_chacha"] -board-baosec = ["cramium-hal/board-baosec"] -board-baosor = ["cramium-hal/board-baosor"] -board-dabao = ["cramium-hal/board-dabao"] +board-baosec = [] +board-baosor = [] +board-dabao = [] atsama5d27 = ["utralib/atsama5d27"] precursor = ["utralib/precursor"] diff --git a/libs/blitstr2/src/platform/doc.rs b/libs/blitstr2/src/platform/doc.rs new file mode 100644 index 000000000..7a019cd0f --- /dev/null +++ b/libs/blitstr2/src/platform/doc.rs @@ -0,0 +1,6 @@ +// Dummy values to allow cargo doc to build +pub const LINES: isize = 536; +pub const WIDTH: isize = 336; +pub const WORDS_PER_LINE: usize = 11; +pub type FrBuf = [u32; WORDS_PER_LINE * LINES as usize]; +pub const FB_SIZE: usize = WORDS_PER_LINE * LINES as usize; // 44 bytes by 536 lines diff --git a/libs/blitstr2/src/platform/mod.rs b/libs/blitstr2/src/platform/mod.rs index 18f9c17c6..c1010b569 100644 --- a/libs/blitstr2/src/platform/mod.rs +++ b/libs/blitstr2/src/platform/mod.rs @@ -9,3 +9,22 @@ pub use baosec::*; mod precursor; #[cfg(any(feature = "hosted", feature = "renode", feature = "precursor"))] pub use precursor::*; + +// Dummy configuration to allow cargo doc to run - this has no board specified +#[cfg(any( + all( + not(any(feature = "board-baosec", feature = "hosted-baosec")), + not(any(feature = "hosted", feature = "renode", feature = "precursor")) + ), + doc +))] +mod doc; + +#[cfg(any( + all( + not(any(feature = "board-baosec", feature = "hosted-baosec")), + not(any(feature = "hosted", feature = "renode", feature = "precursor")) + ), + doc +))] +pub use doc::*; diff --git a/libs/cramium-api/Cargo.toml b/libs/cramium-api/Cargo.toml new file mode 100644 index 000000000..b4a5c86e3 --- /dev/null +++ b/libs/cramium-api/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "cramium-api" +version = "0.1.0" +edition = "2021" + +[dependencies] +xous = "0.9.64" +xous-ipc = { version = "0.10.4", optional = true } +xous-names = { package = "xous-api-names", version = "0.9.65", optional = true } +num-derive = { version = "0.4.2", default-features = false } +num-traits = { version = "0.2.14", default-features = false } +rkyv = { version = "0.8.8", default-features = false, features = [ + "std", + "alloc", +], optional = true } + +[features] +std = ["derive-rkyv", "xous-names", "xous-ipc"] +derive-rkyv = ["rkyv"] +default = [] diff --git a/libs/cramium-api/src/i2c.rs b/libs/cramium-api/src/i2c.rs new file mode 100644 index 000000000..dc6498e4c --- /dev/null +++ b/libs/cramium-api/src/i2c.rs @@ -0,0 +1,50 @@ +pub trait I2cApi { + fn i2c_write(&mut self, dev: u8, adr: u8, data: &[u8]) -> Result; + + /// initiate an i2c read. The read buffer is passed during the await. + fn i2c_read( + &mut self, + dev: u8, + adr: u8, + buf: &mut [u8], + repeated_start: bool, + ) -> Result; +} + +#[cfg_attr(feature = "derive-rkyv", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))] +#[derive(Debug, PartialEq, Eq)] +pub enum I2cTransactionType { + Write, + Read, + ReadRepeatedStart, +} + +#[cfg_attr(feature = "derive-rkyv", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))] +#[derive(Debug)] +pub enum I2cResult { + /// For the outbound message holder + Pending, + /// Returns # of bytes read or written if successful + Ack(usize), + /// An error occurred. + Nack, +} +#[cfg_attr(feature = "derive-rkyv", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))] +#[cfg(feature = "std")] +pub struct I2cTransaction { + pub i2c_type: I2cTransactionType, + pub device: u8, + pub address: u8, + pub data: Vec, + pub result: I2cResult, +} + +#[cfg_attr(feature = "derive-rkyv", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))] +#[cfg(feature = "std")] +pub struct I2cTransactions { + pub transactions: Vec, +} +#[cfg(feature = "std")] +impl From> for I2cTransactions { + fn from(value: Vec) -> Self { Self { transactions: value } } +} diff --git a/services/cram-hal-service/src/iox_lib.rs b/libs/cramium-api/src/iox.rs similarity index 55% rename from services/cram-hal-service/src/iox_lib.rs rename to libs/cramium-api/src/iox.rs index 48389e289..7669b03a2 100644 --- a/services/cram-hal-service/src/iox_lib.rs +++ b/libs/cramium-api/src/iox.rs @@ -1,22 +1,139 @@ -use core::sync::atomic::Ordering; +#[cfg(feature = "std")] +use core::sync::atomic::{AtomicU32, Ordering}; +#[cfg(feature = "std")] +static REFCOUNT: AtomicU32 = AtomicU32::new(0); -use cramium_hal::iox::{ - IoGpio, IoIrq, IoSetup, IoxDir, IoxDriveStrength, IoxEnable, IoxFunction, IoxPort, IoxValue, -}; -use num_traits::*; +#[cfg(feature = "std")] +use super::*; -use crate::{ - Opcode, SERVER_NAME_CRAM_HAL, - api::{IoxConfigMessage, IoxIrqRegistration}, -}; +pub const OP_CONFIGURE_IOX: usize = 4; +pub const OP_SET_GPIO_BANK: usize = 5; +pub const OP_GET_GPIO_BANK: usize = 6; +pub const OP_CONFIGURE_IOX_IRQ: usize = 11; +#[cfg_attr(feature = "derive-rkyv", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))] +#[derive(Debug, Copy, Clone, num_derive::FromPrimitive, num_derive::ToPrimitive)] +#[repr(u32)] +pub enum IoxPort { + PA = 0, + PB = 1, + PC = 2, + PD = 3, + PE = 4, + PF = 5, +} + +#[cfg_attr(feature = "derive-rkyv", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))] +#[derive(Debug)] +#[repr(u32)] +pub enum IoxFunction { + Gpio = 0b00, + AF1 = 0b01, + AF2 = 0b10, + AF3 = 0b11, +} + +#[cfg_attr(feature = "derive-rkyv", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))] +#[derive(Debug)] +#[repr(u32)] +pub enum IoxDriveStrength { + Drive2mA = 0b00, + Drive4mA = 0b01, + Drive8mA = 0b10, + Drive12mA = 0b11, +} + +#[cfg_attr(feature = "derive-rkyv", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))] +#[derive(Debug)] +#[repr(u32)] +pub enum IoxDir { + Input = 0, + Output = 1, +} + +#[cfg_attr(feature = "derive-rkyv", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))] +#[derive(Debug)] +#[repr(u32)] +pub enum IoxEnable { + Disable = 0, + Enable = 1, +} + +#[cfg_attr(feature = "derive-rkyv", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))] +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[repr(u32)] +pub enum IoxValue { + Low = 0, + High = 1, +} +/// The From trait for IoxValue takes any non-zero value and interprets it as "high". +impl From for IoxValue { + fn from(value: u32) -> Self { if value == 0 { IoxValue::Low } else { IoxValue::High } } +} + +/// Use a trait that will allow us to share code between both `std` and `no-std` implementations +pub trait IoSetup { + fn setup_pin( + &self, + port: IoxPort, + pin: u8, + direction: Option, + function: Option, + schmitt_trigger: Option, + pullup: Option, + slow_slew: Option, + strength: Option, + ); +} + +/// Traits for accessing GPIOs after the port has been set up. +pub trait IoGpio { + fn set_gpio_pin_value(&self, port: IoxPort, pin: u8, value: IoxValue); + fn get_gpio_pin_value(&self, port: IoxPort, pin: u8) -> IoxValue; + fn set_gpio_pin_dir(&self, port: IoxPort, pin: u8, dir: IoxDir); +} + +pub trait IoIrq { + /// This hooks a given port/pin to generate a message to the server specified + /// with `server` and the opcode number `usize` when an IRQ is detected on the port/pin. + /// The active state of the IRQ is defined by `active`; the transition edge from inactive + /// to active is when the event is generated. + fn set_irq_pin(&self, port: IoxPort, pin: u8, active: IoxValue, server: &str, opcode: usize); +} + +#[cfg_attr(feature = "derive-rkyv", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))] +#[derive(Debug)] +pub struct IoxConfigMessage { + pub port: IoxPort, + pub pin: u8, + pub direction: Option, + pub function: Option, + pub schmitt_trigger: Option, + pub pullup: Option, + pub slow_slew: Option, + pub strength: Option, +} + +#[cfg_attr(feature = "derive-rkyv", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))] +#[derive(Debug)] +#[cfg(feature = "std")] +pub struct IoxIrqRegistration { + pub server: String, + pub opcode: usize, + pub port: IoxPort, + pub pin: u8, + pub active: IoxValue, +} + +#[cfg(feature = "std")] pub struct IoxHal { conn: xous::CID, } +#[cfg(feature = "std")] impl IoxHal { pub fn new() -> Self { - crate::REFCOUNT.fetch_add(1, Ordering::Relaxed); + REFCOUNT.fetch_add(1, Ordering::Relaxed); let xns = xous_names::XousNames::new().unwrap(); let conn = xns.request_connection(SERVER_NAME_CRAM_HAL).expect("Couldn't connect to Cramium HAL server"); @@ -27,7 +144,7 @@ impl IoxHal { xous::send_message( self.conn, xous::Message::new_blocking_scalar( - Opcode::SetGpioBank.to_usize().unwrap(), + OP_SET_GPIO_BANK, port as usize, // values to set (value as usize) << (pin as usize), @@ -43,7 +160,7 @@ impl IoxHal { xous::send_message( self.conn, xous::Message::new_blocking_scalar( - Opcode::SetGpioBank.to_usize().unwrap(), + OP_SET_GPIO_BANK, port as usize, // values to set value as usize, @@ -58,13 +175,7 @@ impl IoxHal { pub fn get_gpio_pin_value(&self, port: IoxPort, pin: u8) -> IoxValue { match xous::send_message( self.conn, - xous::Message::new_blocking_scalar( - Opcode::GetGpioBank.to_usize().unwrap(), - port as usize, - 0, - 0, - 0, - ), + xous::Message::new_blocking_scalar(OP_GET_GPIO_BANK, port as usize, 0, 0, 0), ) { Ok(xous::Result::Scalar5(_, value, _, _, _)) => { if value & (1 << pin as usize) != 0 { @@ -80,13 +191,7 @@ impl IoxHal { pub fn get_gpio_bank_value(&self, port: IoxPort) -> u32 { match xous::send_message( self.conn, - xous::Message::new_blocking_scalar( - Opcode::GetGpioBank.to_usize().unwrap(), - port as usize, - 0, - 0, - 0, - ), + xous::Message::new_blocking_scalar(OP_GET_GPIO_BANK, port as usize, 0, 0, 0), ) { Ok(xous::Result::Scalar5(_, value, _, _, _)) => value as u32, _ => panic!("Internal Error: Couldn't get GPIO pin value"), @@ -126,6 +231,7 @@ impl IoxHal { } } +#[cfg(feature = "std")] impl IoSetup for IoxHal { fn setup_pin( &self, @@ -141,10 +247,11 @@ impl IoSetup for IoxHal { let msg = IoxConfigMessage { port, pin, direction, function, schmitt_trigger, pullup, slow_slew, strength }; let buf = xous_ipc::Buffer::into_buf(msg).unwrap(); - buf.lend(self.conn, Opcode::ConfigureIox.to_u32().unwrap()).expect("Couldn't set up IO"); + buf.lend(self.conn, OP_CONFIGURE_IOX as u32).expect("Couldn't set up IO"); } } +#[cfg(feature = "std")] impl IoGpio for IoxHal { fn get_gpio_pin_value(&self, port: IoxPort, pin: u8) -> IoxValue { self.get_gpio_pin_value(port, pin) } @@ -160,7 +267,7 @@ impl IoGpio for IoxHal { strength: None, }; let buf = xous_ipc::Buffer::into_buf(msg).unwrap(); - buf.lend(self.conn, Opcode::ConfigureIox.to_u32().unwrap()).expect("Couldn't set up IO"); + buf.lend(self.conn, OP_CONFIGURE_IOX as u32).expect("Couldn't set up IO"); } fn set_gpio_pin_value(&self, port: IoxPort, pin: u8, value: IoxValue) { @@ -168,11 +275,12 @@ impl IoGpio for IoxHal { } } +#[cfg(feature = "std")] impl Drop for IoxHal { fn drop(&mut self) { // de-allocate myself. It's unsafe because we are responsible to make sure nobody else is using the // connection. - if crate::REFCOUNT.fetch_sub(1, Ordering::Relaxed) == 1 { + if REFCOUNT.fetch_sub(1, Ordering::Relaxed) == 1 { unsafe { xous::disconnect(self.conn).unwrap(); } @@ -180,10 +288,11 @@ impl Drop for IoxHal { } } +#[cfg(feature = "std")] impl IoIrq for IoxHal { fn set_irq_pin(&self, port: IoxPort, pin: u8, active: IoxValue, server: &str, opcode: usize) { let msg = IoxIrqRegistration { server: server.to_owned(), opcode, port, pin, active }; let buf = xous_ipc::Buffer::into_buf(msg).unwrap(); - buf.lend(self.conn, Opcode::ConfigureIoxIrq.to_u32().unwrap()).expect("Couldn't set up IRQ"); + buf.lend(self.conn, OP_CONFIGURE_IOX_IRQ as u32).expect("Couldn't set up IRQ"); } } diff --git a/services/cram-hal-service/src/keyboard.rs b/libs/cramium-api/src/keyboard.rs similarity index 53% rename from services/cram-hal-service/src/keyboard.rs rename to libs/cramium-api/src/keyboard.rs index 593268a7e..1f8eaf97b 100644 --- a/services/cram-hal-service/src/keyboard.rs +++ b/libs/cramium-api/src/keyboard.rs @@ -1,9 +1,120 @@ +use core::fmt::Display; + +#[derive(Debug, Default, Copy, Clone)] +#[allow(dead_code)] +pub struct ScanCode { + /// base key value + pub key: Option, + /// tap blue shift key, then key + pub shift: Option, + /// hold blue shift key, then key + pub hold: Option, + /// hold orange shift key, then key + pub alt: Option, +} + +/// Maintainer note: there is a "BackupKeyboardLayout" serializer inside +/// root-keys/api.rs that needs to be updated when this changes. +#[derive(Debug, Copy, Clone, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)] +pub enum KeyMap { + Qwerty, + Azerty, + Qwertz, + Dvorak, + Braille, + Undefined, +} +impl From for KeyMap { + fn from(code: usize) -> Self { + match code { + 0 => KeyMap::Qwerty, + 1 => KeyMap::Azerty, + 2 => KeyMap::Qwertz, + 3 => KeyMap::Dvorak, + 4 => KeyMap::Braille, + _ => KeyMap::Qwerty, + } + } +} +impl From for usize { + fn from(map: KeyMap) -> usize { + match map { + // note: these indicese correspond to the position on the keyboard menu + KeyMap::Qwerty => 0, + KeyMap::Azerty => 1, + KeyMap::Qwertz => 2, + KeyMap::Dvorak => 3, + KeyMap::Braille => 4, + KeyMap::Undefined => 255, + } + } +} + +impl Display for KeyMap { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + Self::Azerty => write!(f, "AZERTY"), + Self::Qwerty => write!(f, "QWERTY"), + Self::Qwertz => write!(f, "QWERTZ"), + Self::Dvorak => write!(f, "Dvorak"), + Self::Braille => write!(f, "Braille"), + Self::Undefined => write!(f, "Undefined"), + } + } +} + +// Opcodes are pinned down to allow for unsafe FFI extraction of key hits +#[derive(Debug, num_derive::FromPrimitive, num_derive::ToPrimitive)] +pub enum KeyboardOpcode { + /// set which keyboard mapping is present + SelectKeyMap = 0, //(KeyMap), + GetKeyMap = 1, + + /// request for ScanCodes + RegisterListener = 2, + + /// request for updates for *when* keyboard is pressed + RegisterKeyObserver = 12, + + /// set repeat delay, rate; both in ms + SetRepeat = 4, //(u32, u32), + + /// set chording interval (how long to wait for all keydowns to happen before interpreting as a chord), + /// in ms (for braille keyboards) + SetChordInterval = 5, //(u32), + + /// used by host mode emulation and debug UART to inject keys + InjectKey = 6, //(char), + + /// used by the interrupt handler to transfer results to the main loop + HandlerTrigger = 7, + + /// a blocking key listener - blocks until a key is hit + BlockingKeyListener = 9, +} + +// this structure is used to register a keyboard listener. +#[cfg(feature = "std")] +#[derive(Debug, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize, Clone)] +pub struct KeyboardRegistration { + pub server_name: String, + pub listener_op_id: usize, +} + +#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, Hash)] +pub struct RowCol { + pub r: u8, + pub c: u8, +} +impl RowCol { + #[allow(dead_code)] + pub fn new(r: u8, c: u8) -> RowCol { RowCol { r, c } } +} + use num_traits::*; use xous::{Message, send_message}; use xous_ipc::Buffer; -use crate::api::keyboard::*; - #[derive(Debug)] pub struct Keyboard { conn: xous::CID, @@ -11,12 +122,12 @@ pub struct Keyboard { impl Keyboard { pub fn new(xns: &xous_names::XousNames) -> Result { REFCOUNT.fetch_add(1, Ordering::Relaxed); - let conn = - xns.request_connection_blocking(crate::api::SERVER_NAME_KBD).expect("Can't connect to KBD"); + let conn = xns.request_connection_blocking(crate::SERVER_NAME_KBD).expect("Can't connect to KBD"); Ok(Keyboard { conn }) } /// Listeners get passed the full content of the key press on each key hit. + #[cfg(feature = "std")] pub fn register_listener(&self, server_name: &str, action_opcode: usize) { let kr = KeyboardRegistration { server_name: String::from(server_name), listener_op_id: action_opcode }; @@ -28,6 +139,7 @@ impl Keyboard { /// Observers get notified if a key is hit, but the actual keypress is always null. /// This is useful for hardware services that need to do e.g. screen wakeup on key hit /// but has no need to know the user's actually keystroke contents. + #[cfg(feature = "std")] pub fn register_observer(&self, server_name: &str, action_opcode: usize) { let kr = KeyboardRegistration { server_name: String::from(server_name), listener_op_id: action_opcode }; @@ -62,6 +174,7 @@ impl Keyboard { /// Blocks until a key is hit. Does not block the keyboard server, just the caller. /// Returns a `Vec::`, as the user can press more than one key at a time. /// The specific order of a simultaneous key hit event is not defined. + #[cfg(feature = "std")] pub fn get_keys_blocking(&self) -> Vec { match send_message( self.conn, diff --git a/libs/cramium-api/src/lib.rs b/libs/cramium-api/src/lib.rs new file mode 100644 index 000000000..ef1f4295a --- /dev/null +++ b/libs/cramium-api/src/lib.rs @@ -0,0 +1,52 @@ +#![cfg_attr(not(feature = "std"), no_std)] + +pub mod udma; +pub use udma::*; +// A bunch of these are gated on "std" because they include APIs that +// aren't available in e.g. kernel or loader. +pub mod iox; +pub use iox::*; +pub mod i2c; +pub use i2c::*; +#[cfg(feature = "std")] +pub mod keyboard; + +/// Constants used by both emulation and hardware implementations +pub const PERCLK: u32 = 100_000_000; +pub const SERVER_NAME_KBD: &str = "_Matrix keyboard driver_"; +/// Do not change this constant, it is hard-coded into libraries in order to break +/// circular dependencies on the IFRAM block. +pub const SERVER_NAME_CRAM_HAL: &str = "_Cramium-SoC HAL_"; + +pub mod camera { + #[derive(Clone, Copy)] + pub enum Resolution { + Res480x272, + Res640x480, + Res320x240, + Res160x120, + Res256x256, + } + + #[derive(Clone, Copy)] + #[repr(u32)] + pub enum Format { + Rgb565 = 0, + Rgb555 = 1, + Rgb444 = 2, + BypassLe = 4, + BypassBe = 5, + } + + impl Into<(usize, usize)> for Resolution { + fn into(self) -> (usize, usize) { + match self { + Resolution::Res160x120 => (160, 120), + Resolution::Res320x240 => (320, 240), + Resolution::Res480x272 => (480, 272), + Resolution::Res640x480 => (640, 480), + Resolution::Res256x256 => (256, 256), + } + } + } +} diff --git a/libs/cramium-api/src/udma.rs b/libs/cramium-api/src/udma.rs new file mode 100644 index 000000000..7fe785ffc --- /dev/null +++ b/libs/cramium-api/src/udma.rs @@ -0,0 +1,246 @@ +/// Constrain potential types for UDMA words to only what is representable and valid +/// for the UDMA subsystem. +pub trait UdmaWidths {} +impl UdmaWidths for i8 {} +impl UdmaWidths for u8 {} +impl UdmaWidths for i16 {} +impl UdmaWidths for u16 {} +impl UdmaWidths for i32 {} +impl UdmaWidths for u32 {} + +#[derive(Debug, Clone, Copy)] +pub enum I2cChannel { + Channel0, + Channel1, + Channel2, + Channel3, +} + +#[derive(Debug, Clone, Copy)] +pub enum SpimChannel { + Channel0, + Channel1, + Channel2, + Channel3, +} + +#[repr(u32)] +#[derive(Copy, Clone, num_derive::FromPrimitive)] +pub enum PeriphId { + Uart0 = 1 << 0, + Uart1 = 1 << 1, + Uart2 = 1 << 2, + Uart3 = 1 << 3, + Spim0 = 1 << 4, + Spim1 = 1 << 5, + Spim2 = 1 << 6, + Spim3 = 1 << 7, + I2c0 = 1 << 8, + I2c1 = 1 << 9, + I2c2 = 1 << 10, + I2c3 = 1 << 11, + Sdio = 1 << 12, + I2s = 1 << 13, + Cam = 1 << 14, + Filter = 1 << 15, + Scif = 1 << 16, + Spis0 = 1 << 17, + Spis1 = 1 << 18, + Adc = 1 << 19, +} +impl Into for PeriphId { + fn into(self) -> u32 { self as u32 } +} + +impl From for PeriphId { + fn from(value: SpimChannel) -> Self { + match value { + SpimChannel::Channel0 => PeriphId::Spim0, + SpimChannel::Channel1 => PeriphId::Spim1, + SpimChannel::Channel2 => PeriphId::Spim2, + SpimChannel::Channel3 => PeriphId::Spim3, + } + } +} + +impl From for PeriphId { + fn from(value: I2cChannel) -> Self { + match value { + I2cChannel::Channel0 => PeriphId::I2c0, + I2cChannel::Channel1 => PeriphId::I2c1, + I2cChannel::Channel2 => PeriphId::I2c2, + I2cChannel::Channel3 => PeriphId::I2c3, + } + } +} + +#[repr(u32)] +#[derive(Copy, Clone)] +pub enum PeriphEventId { + Uart0 = 0, + Uart1 = 4, + Uart2 = 8, + Uart3 = 12, + Spim0 = 16, + Spim1 = 20, + Spim2 = 24, + Spim3 = 28, + I2c0 = 32, + I2c1 = 36, + I2c2 = 40, + I2c3 = 44, + Sdio = 48, + I2s = 52, + Cam = 56, + Adc = 57, // note exception to ordering here + Filter = 60, + Scif = 64, + Spis0 = 68, + Spis1 = 72, +} +impl From for PeriphEventId { + fn from(id: PeriphId) -> Self { + match id { + PeriphId::Uart0 => PeriphEventId::Uart0, + PeriphId::Uart1 => PeriphEventId::Uart1, + PeriphId::Uart2 => PeriphEventId::Uart2, + PeriphId::Uart3 => PeriphEventId::Uart3, + PeriphId::Spim0 => PeriphEventId::Spim0, + PeriphId::Spim1 => PeriphEventId::Spim1, + PeriphId::Spim2 => PeriphEventId::Spim2, + PeriphId::Spim3 => PeriphEventId::Spim3, + PeriphId::I2c0 => PeriphEventId::I2c0, + PeriphId::I2c1 => PeriphEventId::I2c1, + PeriphId::I2c2 => PeriphEventId::I2c2, + PeriphId::I2c3 => PeriphEventId::I2c3, + PeriphId::Sdio => PeriphEventId::Sdio, + PeriphId::I2s => PeriphEventId::I2s, + PeriphId::Cam => PeriphEventId::Cam, + PeriphId::Filter => PeriphEventId::Filter, + PeriphId::Scif => PeriphEventId::Scif, + PeriphId::Spis0 => PeriphEventId::Spis0, + PeriphId::Spis1 => PeriphEventId::Spis1, + PeriphId::Adc => PeriphEventId::Adc, + } + } +} +#[repr(u32)] +#[derive(Copy, Clone)] +pub enum EventUartOffset { + Rx = 0, + Tx = 1, + RxChar = 2, + Err = 3, +} +#[repr(u32)] +#[derive(Copy, Clone)] +pub enum EventSpimOffset { + Rx = 0, + Tx = 1, + Cmd = 2, + Eot = 3, +} +#[repr(u32)] +#[derive(Copy, Clone)] +pub enum EventI2cOffset { + Rx = 0, + Tx = 1, + Cmd = 2, + Eot = 3, +} +#[repr(u32)] +#[derive(Copy, Clone)] +pub enum EventSdioOffset { + Rx = 0, + Tx = 1, + Eot = 2, + Err = 3, +} +#[repr(u32)] +#[derive(Copy, Clone)] +pub enum EventI2sOffset { + Rx = 0, + Tx = 1, +} +#[repr(u32)] +#[derive(Copy, Clone)] +pub enum EventCamOffset { + Rx = 0, +} +#[repr(u32)] +#[derive(Copy, Clone)] +pub enum EventAdcOffset { + Rx = 0, +} +#[repr(u32)] +#[derive(Copy, Clone)] +pub enum EventFilterOffset { + Eot = 0, + Active = 1, +} +#[repr(u32)] +#[derive(Copy, Clone)] + +pub enum EventScifOffset { + Rx = 0, + Tx = 1, + RxChar = 2, + Err = 3, +} +#[repr(u32)] +#[derive(Copy, Clone)] +pub enum EventSpisOffset { + Rx = 0, + Tx = 1, + Eot = 2, +} +#[derive(Copy, Clone)] +pub enum PeriphEventType { + Uart(EventUartOffset), + Spim(EventSpimOffset), + I2c(EventI2cOffset), + Sdio(EventSdioOffset), + I2s(EventI2sOffset), + Cam(EventCamOffset), + Adc(EventAdcOffset), + Filter(EventFilterOffset), + Scif(EventScifOffset), + Spis(EventSpisOffset), +} +impl Into for PeriphEventType { + fn into(self) -> u32 { + match self { + PeriphEventType::Uart(t) => t as u32, + PeriphEventType::Spim(t) => t as u32, + PeriphEventType::I2c(t) => t as u32, + PeriphEventType::Sdio(t) => t as u32, + PeriphEventType::I2s(t) => t as u32, + PeriphEventType::Cam(t) => t as u32, + PeriphEventType::Adc(t) => t as u32, + PeriphEventType::Filter(t) => t as u32, + PeriphEventType::Scif(t) => t as u32, + PeriphEventType::Spis(t) => t as u32, + } + } +} + +#[repr(u32)] +#[derive(Debug, Copy, Clone, num_derive::FromPrimitive)] +pub enum EventChannel { + Channel0 = 0, + Channel1 = 8, + Channel2 = 16, + Channel3 = 24, +} + +/// Use a trait that will allow us to share code between both `std` and `no-std` implementations +pub trait UdmaGlobalConfig { + fn clock(&self, peripheral: PeriphId, enable: bool); + unsafe fn udma_event_map( + &self, + peripheral: PeriphId, + event_type: PeriphEventType, + to_channel: EventChannel, + ); + fn reset(&self, peripheral: PeriphId); +} diff --git a/libs/cramium-hal/Cargo.toml b/libs/cramium-hal/Cargo.toml index 9bbd01156..aa789ae73 100644 --- a/libs/cramium-hal/Cargo.toml +++ b/libs/cramium-hal/Cargo.toml @@ -9,9 +9,7 @@ edition = "2021" xous-api-names = { version = "0.9.65", optional = true } xous-api-ticktimer = { version = "0.9.63", optional = true } log = { version = "0.4.14", optional = true } -utralib = { version = "0.1.25", default-features = false, features = [ - "cramium-soc", -] } +utralib = { version = "0.1.25", default-features = false, optional = true } bitflags = "1.2.1" rkyv = { version = "0.8.8", default-features = false, features = [ "std", @@ -23,7 +21,8 @@ riscv = { version = "0.5.6", package = "xous-riscv" } usb-device = { version = "0.2.8", features = ["log"], optional = true } bitfield = "0.13.2" either = { version = "1.9.0", default-features = false } -ux-api = { path = "../ux-api", default-features = false } +ux-api = { path = "../ux-api", default-features = false, optional = true } +cramium-api = { path = "../cramium-api", default-features = false } # [target.'cfg(target_os = "xous")'.dependencies] xous = { version = "0.9.64", features = ["v2p"] } @@ -32,18 +31,27 @@ xous = { version = "0.9.64", features = ["v2p"] } camera-ov2640 = [] display-sh1107 = [] axp2101 = [] +cramium-soc = ["utralib/cramium-soc"] board-baosec = [ "camera-ov2640", "display-sh1107", "axp2101", + "utralib/cramium-soc", "ux-api/board-baosec", ] # USB form factor token +loader-baosec = ["camera-ov2640", "display-sh1107", "axp2101", "ux-api"] hosted-baosec = [] # emulated hardware on x86 platform -board-baosor = ["camera-ov2640", "axp2101"] # Precursor form factor -board-dabao = [] # Dev board form factor -verilator-only = [] -mpw = [] -hdl-test = [] +board-baosor = [ + "camera-ov2640", + "axp2101", + "utralib/cramium-soc", +] # Precursor form factor +loader-baosor = [] +board-dabao = ["utralib/cramium-soc"] # Dev board form factor +loader-dabao = [] +verilator-only = ["utralib/cramium-soc"] +mpw = ["utralib/cramium-soc"] +hdl-test = ["utralib/cramium-soc"] verbose-debug = [] debug-print-usb = [] @@ -58,6 +66,7 @@ std = [ "usb-device", "xous-api-ticktimer", "ux-api/std", + "cramium-api/std", ] derive-rkyv = ["rkyv"] default = [] diff --git a/libs/cramium-hal/src/axp2101.rs b/libs/cramium-hal/src/axp2101.rs index 32fea0ef8..fd041bde8 100644 --- a/libs/cramium-hal/src/axp2101.rs +++ b/libs/cramium-hal/src/axp2101.rs @@ -1,4 +1,4 @@ -use crate::udma::i2c; +use cramium_api::*; pub const AXP2101_DEV: u8 = 0x34; @@ -136,7 +136,7 @@ pub struct Axp2101 { } impl Axp2101 { - pub fn new(i2c: &mut dyn i2c::I2cApi) -> Result { + pub fn new(i2c: &mut dyn I2cApi) -> Result { let mut s = Axp2101 { dcdc_ena: [false; 4], fast_ramp: false, @@ -149,7 +149,7 @@ impl Axp2101 { Ok(s) } - pub fn update(&mut self, i2c: &mut dyn i2c::I2cApi) -> Result<(), xous::Error> { + pub fn update(&mut self, i2c: &mut dyn I2cApi) -> Result<(), xous::Error> { let mut buf = [0u8; 0xb0]; i2c.i2c_read(AXP2101_DEV, 0x0, &mut buf, false)?; @@ -175,7 +175,7 @@ impl Axp2101 { pub fn set_dcdc( &mut self, - i2c: &mut dyn i2c::I2cApi, + i2c: &mut dyn I2cApi, setting: Option<(f32, bool)>, which: WhichDcDc, ) -> Result<(), xous::Error> { @@ -197,7 +197,7 @@ impl Axp2101 { pub fn set_ldo( &mut self, - i2c: &mut dyn i2c::I2cApi, + i2c: &mut dyn I2cApi, setting: Option, which: WhichLdo, ) -> Result<(), xous::Error> { @@ -253,7 +253,7 @@ impl Axp2101 { /// This will clear all other IRQ sources except VBUS IRQ /// If we need to take more IRQ sources then this API will need to be refactored. - pub fn setup_vbus_irq(&mut self, i2c: &mut dyn i2c::I2cApi, mode: VbusIrq) -> Result<(), xous::Error> { + pub fn setup_vbus_irq(&mut self, i2c: &mut dyn I2cApi, mode: VbusIrq) -> Result<(), xous::Error> { let data = match mode { VbusIrq::None => 0u8, VbusIrq::Insert => VBUS_INSERT_MASK, @@ -270,14 +270,14 @@ impl Axp2101 { i2c.i2c_write(AXP2101_DEV, REG_IRQ_STATUS0, &status).map(|_| ()) } - pub fn get_vbus_irq_status(&self, i2c: &mut dyn i2c::I2cApi) -> Result { + pub fn get_vbus_irq_status(&self, i2c: &mut dyn I2cApi) -> Result { let mut buf = [0u8]; i2c.i2c_read(AXP2101_DEV, REG_IRQ_STATUS1, &mut buf, false)?; Ok(VbusIrq::from(buf[0])) } /// This will clear all pending IRQs, regardless of the setup - pub fn clear_vbus_irq_pending(&mut self, i2c: &mut dyn i2c::I2cApi) -> Result<(), xous::Error> { + pub fn clear_vbus_irq_pending(&mut self, i2c: &mut dyn I2cApi) -> Result<(), xous::Error> { let data = VBUS_INSERT_MASK | VBUS_REMOVE_MASK; i2c.i2c_write(AXP2101_DEV, REG_IRQ_STATUS1, &[data]).map(|_| ()) } diff --git a/libs/cramium-hal/src/board/baosec.rs b/libs/cramium-hal/src/board/baosec.rs index c226c80d3..2a73e3314 100644 --- a/libs/cramium-hal/src/board/baosec.rs +++ b/libs/cramium-hal/src/board/baosec.rs @@ -1,7 +1,5 @@ // Constants that define pin locations, RAM offsets, etc. for the BaoSec board -use crate::iox; -use crate::iox::*; -use crate::iox::{IoIrq, IoSetup}; +use cramium_api::*; pub const I2C_AXP2101_ADR: u8 = 0x34; pub const I2C_TUSB320_ADR: u8 = 0x47; @@ -44,62 +42,62 @@ pub const IFRAM1_RESERVED_PAGE_RANGE: [usize; 2] = [31 - CAM_IFRAM_LEN_PAGES, 31 /// Setup pins for the baosec display /// Returns a spi channel object and descriptor for the C/D + CS pins as a (port, c/d pin, cs pin) tuple -pub fn setup_display_pins(iox: &dyn IoSetup) -> (crate::udma::SpimChannel, iox::IoxPort, u8, u8) { +pub fn setup_display_pins(iox: &dyn IoSetup) -> (SpimChannel, IoxPort, u8, u8) { const SPI_CS_PIN: u8 = 3; const SPI_CLK_PIN: u8 = 0; const SPI_DAT_PIN: u8 = 1; const SPI_CD_PIN: u8 = 2; - const SPI_PORT: iox::IoxPort = iox::IoxPort::PC; + const SPI_PORT: IoxPort = IoxPort::PC; // SPIM_CLK_B[2] iox.setup_pin( SPI_PORT, SPI_CLK_PIN, - Some(iox::IoxDir::Output), - Some(iox::IoxFunction::AF2), + Some(IoxDir::Output), + Some(IoxFunction::AF2), None, None, - Some(iox::IoxEnable::Enable), - Some(iox::IoxDriveStrength::Drive2mA), + Some(IoxEnable::Enable), + Some(IoxDriveStrength::Drive2mA), ); // SPIM_SD0_B[2] iox.setup_pin( SPI_PORT, SPI_DAT_PIN, - Some(iox::IoxDir::Output), - Some(iox::IoxFunction::AF2), + Some(IoxDir::Output), + Some(IoxFunction::AF2), None, None, - Some(iox::IoxEnable::Enable), - Some(iox::IoxDriveStrength::Drive2mA), + Some(IoxEnable::Enable), + Some(IoxDriveStrength::Drive2mA), ); // SPIM_CSN0_B[2] iox.setup_pin( SPI_PORT, SPI_CS_PIN, - Some(iox::IoxDir::Output), - Some(iox::IoxFunction::AF2), + Some(IoxDir::Output), + Some(IoxFunction::AF2), None, - Some(iox::IoxEnable::Enable), - Some(iox::IoxEnable::Enable), - Some(iox::IoxDriveStrength::Drive2mA), + Some(IoxEnable::Enable), + Some(IoxEnable::Enable), + Some(IoxDriveStrength::Drive2mA), ); // C/D pin is a gpio direct-drive iox.setup_pin( SPI_PORT, SPI_CD_PIN, - Some(iox::IoxDir::Output), + Some(IoxDir::Output), Some(IoxFunction::Gpio), None, None, Some(IoxEnable::Enable), - Some(iox::IoxDriveStrength::Drive2mA), + Some(IoxDriveStrength::Drive2mA), ); // using bank SPIM_B[2] - (crate::udma::SpimChannel::Channel2, SPI_PORT, SPI_CD_PIN, SPI_CS_PIN) + (SpimChannel::Channel2, SPI_PORT, SPI_CD_PIN, SPI_CS_PIN) } -pub fn setup_memory_pins(iox: &dyn IoSetup) -> crate::udma::SpimChannel { +pub fn setup_memory_pins(iox: &dyn IoSetup) -> SpimChannel { // JPC7_13 // SPIM_CLK_A[1] iox.setup_pin( @@ -147,11 +145,11 @@ pub fn setup_memory_pins(iox: &dyn IoSetup) -> crate::udma::SpimChannel { Some(IoxEnable::Enable), Some(IoxDriveStrength::Drive2mA), ); - crate::udma::SpimChannel::Channel1 + SpimChannel::Channel1 } /// This also sets up I2C-adjacent interrupt inputs as well -pub fn setup_i2c_pins(iox: &dyn IoSetup) -> crate::udma::I2cChannel { +pub fn setup_i2c_pins(iox: &dyn IoSetup) -> I2cChannel { // I2C_SCL_B[0] iox.setup_pin( IoxPort::PB, @@ -185,7 +183,7 @@ pub fn setup_i2c_pins(iox: &dyn IoSetup) -> crate::udma::I2cChannel { None, None, ); - crate::udma::I2cChannel::Channel0 + I2cChannel::Channel0 } /// returns the power-down port and pin number diff --git a/libs/cramium-hal/src/board/mod.rs b/libs/cramium-hal/src/board/mod.rs index d4f813907..7532733c1 100644 --- a/libs/cramium-hal/src/board/mod.rs +++ b/libs/cramium-hal/src/board/mod.rs @@ -1,12 +1,12 @@ -#[cfg(feature = "board-baosec")] +#[cfg(any(feature = "board-baosec", feature = "loader-baosec"))] pub mod baosec; -#[cfg(feature = "board-baosec")] +#[cfg(any(feature = "board-baosec", feature = "loader-baosec"))] pub use baosec::*; -#[cfg(feature = "board-baosor")] +#[cfg(any(feature = "board-baosor", feature = "loader-baosor"))] pub mod baosor; -#[cfg(feature = "board-baosor")] +#[cfg(any(feature = "board-baosor", feature = "loader-baosor"))] pub use baosor::*; -#[cfg(feature = "board-dabao")] +#[cfg(any(feature = "board-dabao", feature = "loader-dabao"))] pub mod dabao; -#[cfg(feature = "board-dabao")] +#[cfg(any(feature = "board-dabao", feature = "loader-dabao"))] pub use dabao::*; diff --git a/libs/cramium-hal/src/ifram.rs b/libs/cramium-hal/src/ifram.rs index bfd83654b..34996122a 100644 --- a/libs/cramium-hal/src/ifram.rs +++ b/libs/cramium-hal/src/ifram.rs @@ -1,3 +1,4 @@ +use cramium_api::*; #[cfg(feature = "std")] use xous::Result; use xous::{MemoryRange, Message, send_message}; @@ -18,16 +19,6 @@ pub struct IframRange { pub(crate) conn: Option, } -/// Constrain potential types for UDMA words to only what is representable and valid -/// for the UDMA subsystem. -pub trait UdmaWidths {} -impl UdmaWidths for i8 {} -impl UdmaWidths for u8 {} -impl UdmaWidths for i16 {} -impl UdmaWidths for u16 {} -impl UdmaWidths for i32 {} -impl UdmaWidths for u32 {} - impl IframRange { /// Request `length` bytes of memory in an optional Bank /// diff --git a/libs/cramium-hal/src/iox.rs b/libs/cramium-hal/src/iox.rs index 0f681a966..5a2bff7a4 100644 --- a/libs/cramium-hal/src/iox.rs +++ b/libs/cramium-hal/src/iox.rs @@ -1,3 +1,4 @@ +use cramium_api::*; use utralib::generated::utra::iox; use crate::SharedCsr; @@ -18,97 +19,6 @@ macro_rules! set_pin_in_bank { } }}; } - -#[cfg_attr(feature = "derive-rkyv", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))] -#[derive(Debug, Copy, Clone, num_derive::FromPrimitive, num_derive::ToPrimitive)] -#[repr(u32)] -pub enum IoxPort { - PA = 0, - PB = 1, - PC = 2, - PD = 3, - PE = 4, - PF = 5, -} - -#[cfg_attr(feature = "derive-rkyv", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))] -#[derive(Debug)] -#[repr(u32)] -pub enum IoxFunction { - Gpio = 0b00, - AF1 = 0b01, - AF2 = 0b10, - AF3 = 0b11, -} - -#[cfg_attr(feature = "derive-rkyv", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))] -#[derive(Debug)] -#[repr(u32)] -pub enum IoxDriveStrength { - Drive2mA = 0b00, - Drive4mA = 0b01, - Drive8mA = 0b10, - Drive12mA = 0b11, -} - -#[cfg_attr(feature = "derive-rkyv", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))] -#[derive(Debug)] -#[repr(u32)] -pub enum IoxDir { - Input = 0, - Output = 1, -} - -#[cfg_attr(feature = "derive-rkyv", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))] -#[derive(Debug)] -#[repr(u32)] -pub enum IoxEnable { - Disable = 0, - Enable = 1, -} - -#[cfg_attr(feature = "derive-rkyv", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))] -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -#[repr(u32)] -pub enum IoxValue { - Low = 0, - High = 1, -} -/// The From trait for IoxValue takes any non-zero value and interprets it as "high". -impl From for IoxValue { - fn from(value: u32) -> Self { if value == 0 { IoxValue::Low } else { IoxValue::High } } -} - -/// Use a trait that will allow us to share code between both `std` and `no-std` implementations -pub trait IoSetup { - fn setup_pin( - &self, - port: IoxPort, - pin: u8, - direction: Option, - function: Option, - schmitt_trigger: Option, - pullup: Option, - slow_slew: Option, - strength: Option, - ); -} - -/// Traits for accessing GPIOs after the port has been set up. -pub trait IoGpio { - fn set_gpio_pin_value(&self, port: IoxPort, pin: u8, value: IoxValue); - fn get_gpio_pin_value(&self, port: IoxPort, pin: u8) -> IoxValue; - fn set_gpio_pin_dir(&self, port: IoxPort, pin: u8, dir: IoxDir); -} - -pub trait IoIrq { - /// This hooks a given port/pin to generate a message to the server specified - /// with `server` and the opcode number `usize` when an IRQ is detected on the port/pin. - /// The active state of the IRQ is defined by `active`; the transition edge from inactive - /// to active is when the event is generated. - fn set_irq_pin(&self, port: IoxPort, pin: u8, active: IoxValue, server: &str, opcode: usize); -} - pub struct Iox { pub csr: SharedCsr, } diff --git a/libs/cramium-hal/src/ov2640/ov2640.rs b/libs/cramium-hal/src/ov2640/ov2640.rs index 5bf82ddc1..f58d599a4 100644 --- a/libs/cramium-hal/src/ov2640/ov2640.rs +++ b/libs/cramium-hal/src/ov2640/ov2640.rs @@ -1,11 +1,13 @@ +use cramium_api::camera::*; +use cramium_api::*; use utralib::CSR; use utralib::utra; use utralib::utra::udma_camera::REG_CAM_CFG_GLOB; use super::constants::*; +use crate::ifram::IframRange; use crate::udma::Udma; use crate::udma::*; -use crate::{ifram::IframRange, ifram::UdmaWidths, udma::I2cApi}; pub const OV2640_DEV: u8 = 0x30; @@ -16,37 +18,6 @@ pub const CFG_FORMAT: utralib::Field = utralib::Field::new(3, 8, REG_CAM_CFG_GLO pub const CFG_SHIFT: utralib::Field = utralib::Field::new(4, 11, REG_CAM_CFG_GLOB); pub const CFG_GLOB_EN: utralib::Field = utralib::Field::new(1, 31, REG_CAM_CFG_GLOB); -#[derive(Clone, Copy)] -pub enum Resolution { - Res480x272, - Res640x480, - Res320x240, - Res160x120, - Res256x256, -} - -#[derive(Clone, Copy)] -#[repr(u32)] -pub enum Format { - Rgb565 = 0, - Rgb555 = 1, - Rgb444 = 2, - BypassLe = 4, - BypassBe = 5, -} - -impl Into<(usize, usize)> for Resolution { - fn into(self) -> (usize, usize) { - match self { - Resolution::Res160x120 => (160, 120), - Resolution::Res320x240 => (320, 240), - Resolution::Res480x272 => (480, 272), - Resolution::Res640x480 => (640, 480), - Resolution::Res256x256 => (256, 256), - } - } -} - pub struct Ov2640 { csr: CSR, ifram: Option, diff --git a/libs/cramium-hal/src/sh1107.rs b/libs/cramium-hal/src/sh1107.rs index b9a0f99ad..4070cba17 100644 --- a/libs/cramium-hal/src/sh1107.rs +++ b/libs/cramium-hal/src/sh1107.rs @@ -1,16 +1,28 @@ // `Command` vendored from https://github.com/ithinuel/sh1107-rs/tree/main +use cramium_api::*; use ux_api::minigfx::{ColorNative, FrameBuffer, Point}; -use crate::{ - ifram::IframRange, - iox::{IoGpio, IoSetup, IoxPort}, - udma::{PeriphId, Spim, SpimClkPha, SpimClkPol, SpimCs, UdmaGlobalConfig}, -}; +use crate::ifram::IframRange; +use crate::udma::{Spim, SpimClkPha, SpimClkPol, SpimCs}; pub const COLUMN: isize = 128; pub const ROW: isize = 128; pub const PAGE: u8 = ROW as u8 / 8; +pub struct MainThreadToken(()); +impl MainThreadToken { + pub fn new() -> Self { MainThreadToken(()) } +} +pub enum Never {} + +/// Shim for hosted mode compatibility +#[inline] +pub fn claim_main_thread(f: impl FnOnce(MainThreadToken) -> Never + Send + 'static) -> ! { + // Just call the closure - this backend will work on any thread + #[allow(unreachable_code)] // false positive + match f(MainThreadToken(())) {} +} + #[derive(Clone, Copy, PartialEq, Eq, Debug)] pub struct MonoColor(ColorNative); #[derive(Clone, Copy, PartialEq, Eq, Debug)] @@ -180,7 +192,12 @@ pub struct Oled128x128<'a> { } impl<'a> Oled128x128<'a> { - pub fn new(perclk_freq: u32, iox: &'a T, udma_global: &'a dyn UdmaGlobalConfig) -> Self + pub fn new( + _main_thread_token: MainThreadToken, + perclk_freq: u32, + iox: &'a T, + udma_global: &'a dyn UdmaGlobalConfig, + ) -> Self where T: IoSetup + IoGpio, { @@ -249,11 +266,9 @@ impl<'a> Oled128x128<'a> { } } - fn set_data(&self) { self.iox.set_gpio_pin_value(self.cd_port, self.cd_pin, crate::iox::IoxValue::High); } + fn set_data(&self) { self.iox.set_gpio_pin_value(self.cd_port, self.cd_pin, IoxValue::High); } - fn set_command(&self) { - self.iox.set_gpio_pin_value(self.cd_port, self.cd_pin, crate::iox::IoxValue::Low); - } + fn set_command(&self) { self.iox.set_gpio_pin_value(self.cd_port, self.cd_pin, IoxValue::Low); } pub fn buffer_swap(&mut self) { self.active_buffer = self.active_buffer.swap(); } diff --git a/libs/cramium-hal/src/udma/i2c.rs b/libs/cramium-hal/src/udma/i2c.rs index 40169d71d..264c3f3a5 100644 --- a/libs/cramium-hal/src/udma/i2c.rs +++ b/libs/cramium-hal/src/udma/i2c.rs @@ -1,3 +1,4 @@ +use cramium_api::*; use utralib::*; use crate::ifram::IframRange; @@ -53,13 +54,6 @@ impl Into for I2cCmd { } } } -#[derive(Debug, Clone, Copy)] -pub enum I2cChannel { - Channel0, - Channel1, - Channel2, - Channel3, -} enum I2cPending { Idle, @@ -70,19 +64,6 @@ impl I2cPending { fn take(&mut self) -> I2cPending { core::mem::replace(self, I2cPending::Idle) } } -pub trait I2cApi { - fn i2c_write(&mut self, dev: u8, adr: u8, data: &[u8]) -> Result; - - /// initiate an i2c read. The read buffer is passed during the await. - fn i2c_read( - &mut self, - dev: u8, - adr: u8, - buf: &mut [u8], - repeated_start: bool, - ) -> Result; -} - const MAX_I2C_TXLEN: usize = 512; const MAX_I2C_RXLEN: usize = 512; const MAX_I2C_CMDLEN: usize = 512; diff --git a/libs/cramium-hal/src/udma/mod.rs b/libs/cramium-hal/src/udma/mod.rs index 4f1d0fe30..7185e7c6c 100644 --- a/libs/cramium-hal/src/udma/mod.rs +++ b/libs/cramium-hal/src/udma/mod.rs @@ -4,6 +4,7 @@ pub mod uart; use core::mem::size_of; +use cramium_api::*; pub use i2c::*; pub use spim::*; pub use uart::*; @@ -33,226 +34,7 @@ enum GlobalReg { impl Into for GlobalReg { fn into(self) -> usize { self as usize } } -#[repr(u32)] -#[derive(Copy, Clone, num_derive::FromPrimitive)] -pub enum PeriphId { - Uart0 = 1 << 0, - Uart1 = 1 << 1, - Uart2 = 1 << 2, - Uart3 = 1 << 3, - Spim0 = 1 << 4, - Spim1 = 1 << 5, - Spim2 = 1 << 6, - Spim3 = 1 << 7, - I2c0 = 1 << 8, - I2c1 = 1 << 9, - I2c2 = 1 << 10, - I2c3 = 1 << 11, - Sdio = 1 << 12, - I2s = 1 << 13, - Cam = 1 << 14, - Filter = 1 << 15, - Scif = 1 << 16, - Spis0 = 1 << 17, - Spis1 = 1 << 18, - Adc = 1 << 19, -} -impl Into for PeriphId { - fn into(self) -> u32 { self as u32 } -} - -impl From for PeriphId { - fn from(value: SpimChannel) -> Self { - match value { - SpimChannel::Channel0 => PeriphId::Spim0, - SpimChannel::Channel1 => PeriphId::Spim1, - SpimChannel::Channel2 => PeriphId::Spim2, - SpimChannel::Channel3 => PeriphId::Spim3, - } - } -} -impl From for PeriphId { - fn from(value: I2cChannel) -> Self { - match value { - I2cChannel::Channel0 => PeriphId::I2c0, - I2cChannel::Channel1 => PeriphId::I2c1, - I2cChannel::Channel2 => PeriphId::I2c2, - I2cChannel::Channel3 => PeriphId::I2c3, - } - } -} - -#[repr(u32)] -#[derive(Copy, Clone)] -pub enum PeriphEventId { - Uart0 = 0, - Uart1 = 4, - Uart2 = 8, - Uart3 = 12, - Spim0 = 16, - Spim1 = 20, - Spim2 = 24, - Spim3 = 28, - I2c0 = 32, - I2c1 = 36, - I2c2 = 40, - I2c3 = 44, - Sdio = 48, - I2s = 52, - Cam = 56, - Adc = 57, // note exception to ordering here - Filter = 60, - Scif = 64, - Spis0 = 68, - Spis1 = 72, -} -impl From for PeriphEventId { - fn from(id: PeriphId) -> Self { - match id { - PeriphId::Uart0 => PeriphEventId::Uart0, - PeriphId::Uart1 => PeriphEventId::Uart1, - PeriphId::Uart2 => PeriphEventId::Uart2, - PeriphId::Uart3 => PeriphEventId::Uart3, - PeriphId::Spim0 => PeriphEventId::Spim0, - PeriphId::Spim1 => PeriphEventId::Spim1, - PeriphId::Spim2 => PeriphEventId::Spim2, - PeriphId::Spim3 => PeriphEventId::Spim3, - PeriphId::I2c0 => PeriphEventId::I2c0, - PeriphId::I2c1 => PeriphEventId::I2c1, - PeriphId::I2c2 => PeriphEventId::I2c2, - PeriphId::I2c3 => PeriphEventId::I2c3, - PeriphId::Sdio => PeriphEventId::Sdio, - PeriphId::I2s => PeriphEventId::I2s, - PeriphId::Cam => PeriphEventId::Cam, - PeriphId::Filter => PeriphEventId::Filter, - PeriphId::Scif => PeriphEventId::Scif, - PeriphId::Spis0 => PeriphEventId::Spis0, - PeriphId::Spis1 => PeriphEventId::Spis1, - PeriphId::Adc => PeriphEventId::Adc, - } - } -} -#[repr(u32)] -#[derive(Copy, Clone)] -pub enum EventUartOffset { - Rx = 0, - Tx = 1, - RxChar = 2, - Err = 3, -} -#[repr(u32)] -#[derive(Copy, Clone)] -pub enum EventSpimOffset { - Rx = 0, - Tx = 1, - Cmd = 2, - Eot = 3, -} -#[repr(u32)] -#[derive(Copy, Clone)] -pub enum EventI2cOffset { - Rx = 0, - Tx = 1, - Cmd = 2, - Eot = 3, -} -#[repr(u32)] -#[derive(Copy, Clone)] -pub enum EventSdioOffset { - Rx = 0, - Tx = 1, - Eot = 2, - Err = 3, -} -#[repr(u32)] -#[derive(Copy, Clone)] -pub enum EventI2sOffset { - Rx = 0, - Tx = 1, -} -#[repr(u32)] -#[derive(Copy, Clone)] -pub enum EventCamOffset { - Rx = 0, -} -#[repr(u32)] -#[derive(Copy, Clone)] -pub enum EventAdcOffset { - Rx = 0, -} -#[repr(u32)] -#[derive(Copy, Clone)] -pub enum EventFilterOffset { - Eot = 0, - Active = 1, -} -#[repr(u32)] -#[derive(Copy, Clone)] - -pub enum EventScifOffset { - Rx = 0, - Tx = 1, - RxChar = 2, - Err = 3, -} -#[repr(u32)] -#[derive(Copy, Clone)] -pub enum EventSpisOffset { - Rx = 0, - Tx = 1, - Eot = 2, -} -#[derive(Copy, Clone)] -pub enum PeriphEventType { - Uart(EventUartOffset), - Spim(EventSpimOffset), - I2c(EventI2cOffset), - Sdio(EventSdioOffset), - I2s(EventI2sOffset), - Cam(EventCamOffset), - Adc(EventAdcOffset), - Filter(EventFilterOffset), - Scif(EventScifOffset), - Spis(EventSpisOffset), -} -impl Into for PeriphEventType { - fn into(self) -> u32 { - match self { - PeriphEventType::Uart(t) => t as u32, - PeriphEventType::Spim(t) => t as u32, - PeriphEventType::I2c(t) => t as u32, - PeriphEventType::Sdio(t) => t as u32, - PeriphEventType::I2s(t) => t as u32, - PeriphEventType::Cam(t) => t as u32, - PeriphEventType::Adc(t) => t as u32, - PeriphEventType::Filter(t) => t as u32, - PeriphEventType::Scif(t) => t as u32, - PeriphEventType::Spis(t) => t as u32, - } - } -} - -/// Use a trait that will allow us to share code between both `std` and `no-std` implementations -pub trait UdmaGlobalConfig { - fn clock(&self, peripheral: PeriphId, enable: bool); - unsafe fn udma_event_map( - &self, - peripheral: PeriphId, - event_type: PeriphEventType, - to_channel: EventChannel, - ); - fn reset(&self, peripheral: PeriphId); -} - -#[repr(u32)] -#[derive(Debug, Copy, Clone, num_derive::FromPrimitive)] -pub enum EventChannel { - Channel0 = 0, - Channel1 = 8, - Channel2 = 16, - Channel3 = 24, -} pub struct GlobalConfig { csr: SharedCsr, } diff --git a/libs/cramium-hal/src/udma/spim.rs b/libs/cramium-hal/src/udma/spim.rs index f3dec412e..90ffb75a8 100644 --- a/libs/cramium-hal/src/udma/spim.rs +++ b/libs/cramium-hal/src/udma/spim.rs @@ -1,6 +1,7 @@ +use cramium_api::*; use utralib::*; -use crate::ifram::{IframRange, UdmaWidths}; +use crate::ifram::IframRange; use crate::udma::*; pub const FLASH_PAGE_LEN: usize = 256; @@ -165,13 +166,6 @@ impl Into for SpimCmd { } } -#[derive(Debug, Clone, Copy)] -pub enum SpimChannel { - Channel0, - Channel1, - Channel2, - Channel3, -} #[derive(Debug)] pub struct Spim { csr: CSR, diff --git a/libs/ux-api/Cargo.toml b/libs/ux-api/Cargo.toml index 14163a07d..6e13a5769 100644 --- a/libs/ux-api/Cargo.toml +++ b/libs/ux-api/Cargo.toml @@ -5,7 +5,7 @@ edition = "2021" [dependencies] xous = "0.9.64" -xous-ipc = "0.10.4" +xous-ipc = { version = "0.10.4", optional = true } log = "0.4.14" num-derive = { version = "0.4.2", default-features = false } num-traits = { version = "0.2.14", default-features = false } @@ -18,13 +18,15 @@ enum_dispatch = "0.3.7" blitstr2 = { path = "../blitstr2", optional = true } # used in notifications -locales = { path = "../../locales" } -qrcode = { version = "0.12", default-features = false } +locales = { path = "../../locales", optional = true } +qrcode = { version = "0.12", default-features = false, optional = true } # used in bip39 dialogue -hex = { version = "0.4.3", default-features = false, features = ["alloc"] } -sha2 = { version = "0.10.8" } -digest = "0.9.0" +hex = { version = "0.4.3", default-features = false, features = [ + "alloc", +], optional = true } +sha2 = { version = "0.10.8", optional = true } +digest = { version = "0.9.0", optional = true } [features] # various feature gates - recommended to be selective about these because @@ -50,7 +52,9 @@ default-widgets = [ # board definition is used to define things like the size of the screen. board-baosec = ["blitstr2/board-baosec"] +loader-baosec = [] board-baosor = [] +loader-baosor = [] hosted-baosec = ["blitstr2/hosted-baosec"] # Used by font codegen routine @@ -58,6 +62,7 @@ hosted = ["blitstr2/hosted"] renode = ["blitstr2/renode"] cramium-soc = [] precursor = ["blitstr2/precursor"] +derive-rkyv = ["rkyv"] -std = ["rkyv"] +std = ["derive-rkyv", "locales", "qrcode", "xous-ipc", "hex", "sha2", "digest"] default = ["std", "default-widgets"] diff --git a/libs/ux-api/src/lib.rs b/libs/ux-api/src/lib.rs index 5b31f1fdb..e8077a058 100644 --- a/libs/ux-api/src/lib.rs +++ b/libs/ux-api/src/lib.rs @@ -1,11 +1,13 @@ #![cfg_attr(not(feature = "std"), no_std)] -pub mod minigfx; -pub mod widgets; -mod wordwrap; - +#[cfg(feature = "std")] pub mod cursor; mod fontmap; +pub mod minigfx; pub mod platform; - +#[cfg(feature = "std")] +pub mod widgets; +#[cfg(feature = "std")] +mod wordwrap; +#[cfg(feature = "std")] pub const SYSTEM_STYLE: blitstr2::GlyphStyle = blitstr2::GlyphStyle::Tall; diff --git a/libs/ux-api/src/minigfx/circle.rs b/libs/ux-api/src/minigfx/circle.rs index 14b6e9826..e30500bcc 100644 --- a/libs/ux-api/src/minigfx/circle.rs +++ b/libs/ux-api/src/minigfx/circle.rs @@ -1,6 +1,7 @@ use crate::minigfx::*; -#[derive(Debug, Clone, Copy, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)] +#[cfg_attr(feature = "derive-rkyv", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))] +#[derive(Debug, Clone, Copy)] pub struct Circle { pub center: Point, pub radius: isize, diff --git a/libs/ux-api/src/minigfx/cursor.rs b/libs/ux-api/src/minigfx/cursor.rs index bd445eff1..b9751f4b0 100644 --- a/libs/ux-api/src/minigfx/cursor.rs +++ b/libs/ux-api/src/minigfx/cursor.rs @@ -10,7 +10,8 @@ use crate::minigfx::Point; /// Cursor specifies a drawing position along a line of text. Lines of text can /// be different heights. Line_height is for keeping track of the tallest /// character that has been drawn so far on the current line. -#[derive(Copy, Clone, Debug, PartialEq, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)] +#[cfg_attr(feature = "derive-rkyv", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))] +#[derive(Copy, Clone, Debug, PartialEq)] pub struct Cursor { pub pt: Point, pub line_height: usize, diff --git a/libs/ux-api/src/minigfx/mod.rs b/libs/ux-api/src/minigfx/mod.rs index b790cd655..880791ba6 100644 --- a/libs/ux-api/src/minigfx/mod.rs +++ b/libs/ux-api/src/minigfx/mod.rs @@ -8,12 +8,19 @@ pub mod style; pub use style::*; pub mod circle; pub use circle::*; + +#[cfg(feature = "std")] pub mod textview; +#[cfg(feature = "std")] pub use textview::*; +#[cfg(feature = "std")] pub mod cursor; +#[cfg(feature = "std")] pub use cursor::*; +#[cfg(feature = "std")] pub(crate) mod op; +#[cfg(feature = "std")] use blitstr2::GlyphSprite; /// Abstract trait for a FrameBuffer. Slower than native manipulation @@ -35,6 +42,7 @@ pub trait FrameBuffer { /// defined by its `bb` record. The intention is that this abstract representation can be passed directly to /// a rasterizer for rendering. #[derive(Debug)] +#[cfg(feature = "std")] pub(crate) struct TypesetWord { /// glyph data to directly render the word pub gs: Vec, diff --git a/libs/ux-api/src/minigfx/point.rs b/libs/ux-api/src/minigfx/point.rs index f73ad6d6f..703598e1f 100644 --- a/libs/ux-api/src/minigfx/point.rs +++ b/libs/ux-api/src/minigfx/point.rs @@ -1,6 +1,7 @@ use core::ops::{Add, AddAssign, Div, DivAssign, Index, Mul, MulAssign, Neg, Sub, SubAssign}; -#[derive(Copy, Clone, PartialEq, Eq, Debug, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)] +#[cfg_attr(feature = "derive-rkyv", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))] +#[derive(Copy, Clone, PartialEq, Eq, Debug)] pub struct Point { pub x: isize, pub y: isize, diff --git a/libs/ux-api/src/minigfx/rect.rs b/libs/ux-api/src/minigfx/rect.rs index e99456029..e1f238434 100644 --- a/libs/ux-api/src/minigfx/rect.rs +++ b/libs/ux-api/src/minigfx/rect.rs @@ -2,7 +2,8 @@ use core::cmp::{max, min}; use crate::minigfx::*; -#[derive(Debug, Clone, Copy, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)] +#[cfg_attr(feature = "derive-rkyv", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))] +#[derive(Debug, Clone, Copy)] pub struct Rectangle { pub tl: Point, pub br: Point, @@ -193,7 +194,8 @@ impl Rectangle { } //////////////////////// Rounded Rectangle -#[derive(Debug, Clone, Copy, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)] +#[cfg_attr(feature = "derive-rkyv", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))] +#[derive(Debug, Clone, Copy)] pub struct RoundedRectangle { pub border: Rectangle, // drawstyle is inherited from the Rectangle pub radius: isize, diff --git a/libs/ux-api/src/minigfx/style.rs b/libs/ux-api/src/minigfx/style.rs index 50b12608e..f30441d7e 100644 --- a/libs/ux-api/src/minigfx/style.rs +++ b/libs/ux-api/src/minigfx/style.rs @@ -1,7 +1,8 @@ use crate::minigfx::Point; /// Type wrapper for native colors -#[derive(Clone, Copy, PartialEq, Eq, Debug, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)] +#[cfg_attr(feature = "derive-rkyv", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))] +#[derive(Clone, Copy, PartialEq, Eq, Debug)] pub struct ColorNative(pub usize); impl From for ColorNative { fn from(value: usize) -> Self { Self { 0: value } } @@ -11,7 +12,8 @@ impl Into for ColorNative { } /// Style properties for an object -#[derive(Debug, Copy, Clone, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)] +#[cfg_attr(feature = "derive-rkyv", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))] +#[derive(Debug, Copy, Clone)] pub struct DrawStyle { /// Fill colour of the object pub fill_color: Option, @@ -40,7 +42,8 @@ impl Default for DrawStyle { } } -#[derive(Debug, Copy, Clone, PartialEq, Eq, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)] +#[cfg_attr(feature = "derive-rkyv", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))] +#[derive(Debug, Copy, Clone, PartialEq, Eq)] pub enum PixelColor { Dark, Light, diff --git a/libs/ux-api/src/minigfx/textview.rs b/libs/ux-api/src/minigfx/textview.rs index 4644d7506..ff1c3ed88 100644 --- a/libs/ux-api/src/minigfx/textview.rs +++ b/libs/ux-api/src/minigfx/textview.rs @@ -1,12 +1,12 @@ use core::ops::Add; -use String; use blitstr2::GlyphStyle; use super::*; /// coordinates are local to the canvas, not absolute to the screen -#[derive(Debug, Copy, Clone, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)] +#[cfg_attr(feature = "derive-rkyv", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))] +#[derive(Debug, Copy, Clone)] pub enum TextBounds { // fixed width and height in a rectangle BoundingBox(Rectangle), @@ -46,7 +46,8 @@ impl TextBounds { } } -#[derive(Debug, Copy, Clone, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize, PartialEq)] +#[cfg_attr(feature = "derive-rkyv", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))] +#[derive(Debug, Copy, Clone)] // operations that may be requested of a TextView when sent to GAM pub enum TextOp { Nop, @@ -76,7 +77,8 @@ impl From for TextOp { pub const TEXTVIEW_LEN: usize = 3072; pub const TEXTVIEW_DEFAULT_STYLE: GlyphStyle = GlyphStyle::Regular; -#[derive(Clone, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)] +#[cfg_attr(feature = "derive-rkyv", derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize))] +#[derive(Clone)] pub struct TextView { // this is the operation as specified for the GAM. Note this is different from the "op" when sent to // graphics-server only the GAM should be sending TextViews to the graphics-server, and a different diff --git a/libs/ux-api/src/platform/mod.rs b/libs/ux-api/src/platform/mod.rs index fe8015bc5..25c2bd068 100644 --- a/libs/ux-api/src/platform/mod.rs +++ b/libs/ux-api/src/platform/mod.rs @@ -1,9 +1,9 @@ -#[cfg(any(feature = "board-baosec", feature = "hosted-baosec"))] +#[cfg(any(feature = "board-baosec", feature = "hosted-baosec", feature = "loader-baosec"))] mod baosec; -#[cfg(any(feature = "board-baosec", feature = "hosted-baosec"))] +#[cfg(any(feature = "board-baosec", feature = "hosted-baosec", feature = "loader-baosec"))] pub use baosec::*; -#[cfg(feature = "board-baosor")] +#[cfg(any(feature = "board-baosor", feature = "loader-baosor"))] mod baosor; -#[cfg(feature = "board-baosor")] +#[cfg(any(feature = "board-baosor", feature = "loader-baosor"))] pub use baosor::*; diff --git a/libs/ux-api/src/widgets/modal.rs b/libs/ux-api/src/widgets/modal.rs index 96a08b2c2..9623e68d9 100644 --- a/libs/ux-api/src/widgets/modal.rs +++ b/libs/ux-api/src/widgets/modal.rs @@ -28,7 +28,6 @@ pub struct Modal<'a> { pub inverted: bool, pub style: GlyphStyle, pub helper_data: Option>, - pub name: String, // optimize draw time top_dirty: bool, @@ -39,7 +38,6 @@ pub struct Modal<'a> { impl<'a> Modal<'a> { pub fn new( - name: &str, action: ActionType, top_text: Option<&str>, bot_text: Option<&str>, @@ -73,7 +71,6 @@ impl<'a> Modal<'a> { inverted, style, helper_data: None, - name: String::from(name), top_dirty: true, bot_dirty: true, top_memoized_height: None, @@ -230,7 +227,9 @@ fn layout(modal: &mut Modal, top_text: Option<&str>, bot_text: Option<&str>, sty // compute height of action item log::trace!("step 1 total_height: {}", total_height); - total_height += modal.action.height(modal.line_height, modal.margin, &modal); + total_height += + modal.action.height(modal.line_height.try_into().unwrap(), modal.margin.try_into().unwrap(), &modal) + as isize; total_height += modal.margin; // compute height of bot_text, if any diff --git a/loader/Cargo.toml b/loader/Cargo.toml index 858e6817a..5339175c6 100644 --- a/loader/Cargo.toml +++ b/loader/Cargo.toml @@ -18,6 +18,7 @@ armv7 = { git = "https://github.com/Foundation-Devices/armv7.git", branch = "upd atsama5d27 = { git = "https://github.com/Foundation-Devices/atsama5d27.git", branch = "master", optional = true } sha2-loader = { path = "./sha2-loader", default-features = false, optional = true } cramium-hal = { path = "../libs/cramium-hal", optional = true, default-features = false } +cramium-api = { path = "../libs/cramium-api", optional = true, default-features = false } ux-api = { path = "../libs/ux-api", optional = true, default-features = false } xous-pl230 = { path = "../libs/xous-pl230", optional = true, features = [ "tests", @@ -80,14 +81,14 @@ renode = [ cramium-soc = [ "utralib/cramium-soc", "debug-print", - "cramium-hal", - "ux-api", + "cramium-hal/cramium-soc", + "cramium-api", "rand_chacha", "sram-margin", # "boot-delay", ] board-baosec = [ - "cramium-hal/board-baosec", + "cramium-hal/loader-baosec", "updates", "usb", "libm", @@ -96,10 +97,10 @@ board-baosec = [ "ed25519-dalek", "sha2", "digest", - "ux-api/board-baosec", + "ux-api/loader-baosec", ] -board-baosor = ["cramium-hal/board-baosor"] -board-dabao = ["cramium-hal/board-dabao"] +board-baosor = ["cramium-hal/loader-baosor"] +board-dabao = ["cramium-hal/loader-dabao"] cramium-fpga = ["utralib/cramium-fpga", "debug-print"] atsama5d27 = ["utralib/atsama5d27", "armv7", "dep:atsama5d27"] diff --git a/loader/src/platform/cramium/cramium.rs b/loader/src/platform/cramium/cramium.rs index 130a724c6..a77b9eb80 100644 --- a/loader/src/platform/cramium/cramium.rs +++ b/loader/src/platform/cramium/cramium.rs @@ -1,13 +1,14 @@ -#[cfg(any(feature = "board-baosec", feature = "board-baosor"))] -use cramium_hal::axp2101::Axp2101; #[cfg(not(feature = "verilator-only"))] -use cramium_hal::iox::{Iox, IoxDir, IoxEnable, IoxFunction, IoxPort}; +use cramium_api::{ + udma::{PeriphId, UdmaGlobalConfig}, + *, +}; +#[cfg(not(feature = "verilator-only"))] +use cramium_hal::iox::Iox; #[cfg(feature = "cramium-soc")] use cramium_hal::udma; #[cfg(any(feature = "board-baosec", feature = "board-baosor"))] -use cramium_hal::udma::PeriphId; -#[cfg(any(feature = "board-baosec", feature = "board-baosor"))] -use cramium_hal::udma::UdmaGlobalConfig; +use cramium_hal::{axp2101::Axp2101, udma::GlobalConfig}; use utralib::generated::*; #[cfg(feature = "qr")] @@ -240,17 +241,17 @@ pub fn early_init() -> u32 { // Set up the UDMA_UART block to the correct baud rate and enable status #[allow(unused_mut)] // some configs require mut - let mut udma_global = udma::GlobalConfig::new(utra::udma_ctrl::HW_UDMA_CTRL_BASE as *mut u32); - udma_global.clock_on(udma::PeriphId::Uart1); + let mut udma_global = GlobalConfig::new(utra::udma_ctrl::HW_UDMA_CTRL_BASE as *mut u32); + udma_global.clock_on(PeriphId::Uart1); udma_global.map_event( - udma::PeriphId::Uart1, - udma::PeriphEventType::Uart(udma::EventUartOffset::Rx), - udma::EventChannel::Channel0, + PeriphId::Uart1, + PeriphEventType::Uart(EventUartOffset::Rx), + EventChannel::Channel0, ); udma_global.map_event( - udma::PeriphId::Uart1, - udma::PeriphEventType::Uart(udma::EventUartOffset::Tx), - udma::EventChannel::Channel1, + PeriphId::Uart1, + PeriphEventType::Uart(EventUartOffset::Tx), + EventChannel::Channel1, ); let baudrate: u32 = 115200; @@ -372,7 +373,12 @@ pub fn early_init() -> u32 { // show the boot logo use ux_api::minigfx::FrameBuffer; - let mut sh1107 = cramium_hal::sh1107::Oled128x128::new(perclk, &mut iox, &mut udma_global); + let mut sh1107 = cramium_hal::sh1107::Oled128x128::new( + cramium_hal::sh1107::MainThreadToken::new(), + perclk, + &mut iox, + &mut udma_global, + ); sh1107.init(); crate::platform::cramium::bootlogo::show_logo(&mut sh1107); sh1107.buffer_swap(); diff --git a/loader/src/platform/cramium/swap.rs b/loader/src/platform/cramium/swap.rs index f21fd1a30..374deb0df 100644 --- a/loader/src/platform/cramium/swap.rs +++ b/loader/src/platform/cramium/swap.rs @@ -1,11 +1,13 @@ use core::mem::size_of; use aes_gcm_siv::{AeadInPlace, Aes256GcmSiv, KeyInit, Nonce, Tag}; +use cramium_api::udma::*; +use cramium_api::*; use cramium_hal::board::{APP_UART_IFRAM_ADDR, SPIM_FLASH_IFRAM_ADDR, SPIM_RAM_IFRAM_ADDR}; use cramium_hal::ifram::IframRange; -use cramium_hal::iox::*; +use cramium_hal::iox::Iox; use cramium_hal::sce; -use cramium_hal::udma::*; +use cramium_hal::udma::{GlobalConfig, Spim, SpimClkPha, SpimClkPol, SpimCs}; use rand_chacha::ChaCha8Rng; use rand_chacha::rand_core::RngCore; use rand_chacha::rand_core::SeedableRng; @@ -396,16 +398,16 @@ pub fn userspace_maps(cfg: &mut BootConfig) { // Set up the UDMA_UART block to the correct baud rate and enable status let udma_global = cramium_hal::udma::GlobalConfig::new(utralib::utra::udma_ctrl::HW_UDMA_CTRL_BASE as *mut u32); - udma_global.clock_on(cramium_hal::udma::PeriphId::Uart0); + udma_global.clock_on(PeriphId::Uart0); udma_global.map_event( - cramium_hal::udma::PeriphId::Uart0, - cramium_hal::udma::PeriphEventType::Uart(cramium_hal::udma::EventUartOffset::Rx), - cramium_hal::udma::EventChannel::Channel2, + PeriphId::Uart0, + PeriphEventType::Uart(EventUartOffset::Rx), + EventChannel::Channel2, ); udma_global.map_event( - cramium_hal::udma::PeriphId::Uart0, - cramium_hal::udma::PeriphEventType::Uart(cramium_hal::udma::EventUartOffset::Tx), - cramium_hal::udma::EventChannel::Channel3, + PeriphId::Uart0, + PeriphEventType::Uart(EventUartOffset::Tx), + EventChannel::Channel3, ); let baudrate: u32 = 115200; diff --git a/loader/src/platform/cramium/update.rs b/loader/src/platform/cramium/update.rs index ad1c1ce51..29f164941 100644 --- a/loader/src/platform/cramium/update.rs +++ b/loader/src/platform/cramium/update.rs @@ -1,9 +1,10 @@ use core::convert::TryInto; use core::fmt::Write; +use cramium_api::*; use cramium_hal::board::SPIM_FLASH_IFRAM_ADDR; use cramium_hal::ifram::IframRange; -use cramium_hal::iox::{IoGpio, IoSetup, Iox, IoxPort, IoxValue}; +use cramium_hal::iox::Iox; use cramium_hal::mbox::{ MBOX_PROTOCOL_REV, Mbox, MboxError, MboxToCm7Pkt, PAYLOAD_LEN_WORDS, RERAM_PAGE_SIZE_BYTES, ToCm7Op, ToRvOp, @@ -108,7 +109,12 @@ pub fn process_update(perclk: u32) { let mut udma_global = udma::GlobalConfig::new(utra::udma_ctrl::HW_UDMA_CTRL_BASE as *mut u32); let iox_kbd = iox.clone(); - let mut sh1107 = cramium_hal::sh1107::Oled128x128::new(perclk, &mut iox, &mut udma_global); + let mut sh1107 = cramium_hal::sh1107::Oled128x128::new( + cramium_hal::sh1107::MainThreadToken::new(), + perclk, + &mut iox, + &mut udma_global, + ); gfx::msg(&mut sh1107, " START to boot", Point::new(0, 16), Mono::White.into(), Mono::Black.into()); gfx::msg(&mut sh1107, " SELECT to update", Point::new(0, 0), Mono::White.into(), Mono::Black.into()); diff --git a/services/bao-console/Cargo.toml b/services/bao-console/Cargo.toml index ff4a4954e..0a6af484b 100644 --- a/services/bao-console/Cargo.toml +++ b/services/bao-console/Cargo.toml @@ -13,6 +13,7 @@ log = "0.4.14" num-derive = { version = "0.4.2", default-features = false } num-traits = { version = "0.2.14", default-features = false } cramium-hal = { path = "../../libs/cramium-hal", features = ["std"] } +cramium-api = { path = "../../libs/cramium-api" } cram-hal-service = { path = "../cram-hal-service" } usb-cramium = { path = "../usb-cramium" } diff --git a/services/bao-console/src/shell.rs b/services/bao-console/src/shell.rs index 09a2f7831..d32b08df1 100644 --- a/services/bao-console/src/shell.rs +++ b/services/bao-console/src/shell.rs @@ -1,4 +1,4 @@ -use cram_hal_service::keyboard; +use cramium_api::*; use xous::msg_scalar_unpack; pub fn start_shell() { diff --git a/services/bao-video/Cargo.toml b/services/bao-video/Cargo.toml index 019f252fc..044c629b5 100644 --- a/services/bao-video/Cargo.toml +++ b/services/bao-video/Cargo.toml @@ -17,6 +17,8 @@ cramium-hal = { path = "../../libs/cramium-hal", features = [ ], optional = true } cram-hal-service = { path = "../cram-hal-service", optional = true } ux-api = { path = "../../libs/ux-api" } +cramium-emu = { path = "../cramium-emu", optional = true } +cramium-api = { path = "../../libs/cramium-api" } # QR decoding libm = { version = "0.2.8" } @@ -29,9 +31,12 @@ locales = { path = "../../locales" } # This crate is baosec specific; these flags switch between hosted mode emulation # (for UI development) and actual hardware targets. In UI development all camera data # is "faked" -board-baosec = ["cram-hal-service"] -hosted-baosec = [] +board-baosec = ["cram-hal-service", "cramium-hal"] +hosted-baosec = ["cramium-emu"] cramium-soc = ["utralib/cramium-soc"] decongest-udma = [] -default = ["decongest-udma"] +default = [ + "decongest-udma", + # "hosted-baosec", +] # hosted-baosec should be removed for commit, this is only here to make vscode happy diff --git a/services/bao-video/src/api.rs b/services/bao-video/src/api.rs index 4a040ec2b..9cabff441 100644 --- a/services/bao-video/src/api.rs +++ b/services/bao-video/src/api.rs @@ -3,3 +3,5 @@ pub enum Opcode { CamIrq, InvalidCall, } + +pub const SERVER_NAME_GFX: &str = "_Graphics_"; diff --git a/services/bao-video/src/gfx.rs b/services/bao-video/src/gfx.rs index 1bcfe4c0d..b3cf20e86 100644 --- a/services/bao-video/src/gfx.rs +++ b/services/bao-video/src/gfx.rs @@ -1,3 +1,6 @@ +#[cfg(feature = "hosted-baosec")] +use cramium_emu::display::Mono; +#[cfg(feature = "board-baosec")] use cramium_hal::sh1107::Mono; use ux_api::minigfx::*; diff --git a/services/bao-video/src/hosted.rs b/services/bao-video/src/hosted.rs deleted file mode 100644 index 495675485..000000000 --- a/services/bao-video/src/hosted.rs +++ /dev/null @@ -1,24 +0,0 @@ -pub fn wrapped_main() -> ! { - log_server::init_wait().unwrap(); - log::set_max_level(log::LevelFilter::Info); - - let xns = xous_names::XousNames::new().unwrap(); - let sid = xns.register_name("bao video subsystem", None).expect("can't register server"); - - let tt = ticktimer::Ticktimer::new().unwrap(); - - loop { - xous::reply_and_receive_next(sid, &mut msg_opt).unwrap(); - let msg = msg_opt.as_mut().unwrap(); - let opcode = num_traits::FromPrimitive::from_usize(msg.body.id()).unwrap_or(Opcode::InvalidCall); - log::debug!("{:?}", opcode); - match opcode { - Opcode::CamIrq => { - unimplemented!() - } - Opcode::InvalidCall => { - log::error!("Invalid call to bao video server: {:?}", msg); - } - } - } -} diff --git a/services/bao-video/src/hw.rs b/services/bao-video/src/hw.rs deleted file mode 100644 index 1cbb4f095..000000000 --- a/services/bao-video/src/hw.rs +++ /dev/null @@ -1,449 +0,0 @@ -pub fn blit_to_display(sh1107: &mut Oled128x128, frame: &[u8], display_cleared: bool) { - for (y, row) in frame.chunks(IMAGE_WIDTH).enumerate() { - if y & 1 == 0 { - for (x, &pixval) in row.iter().enumerate() { - if x & 1 == 0 { - if x < sh1107.dimensions().x as usize * 2 - && y < sh1107.dimensions().y as usize * 2 - (gfx::CHAR_HEIGHT as usize + 1) * 2 - { - let luminance = pixval & 0xff; - if luminance > BW_THRESH { - sh1107.put_pixel(Point::new(x as isize / 2, y as isize / 2), Mono::White.into()); - } else { - // optimization to avoid some computation if we're blitting to an already-black - // buffer - if !display_cleared { - sh1107.put_pixel( - Point::new(x as isize / 2, y as isize / 2), - Mono::Black.into(), - ); - } - } - } else { - break; - } - } - } - } - } -} - -#[repr(align(32))] -struct CamIrq { - csr: utralib::CSR, - cid: u32, -} - -fn handle_irq(_irq_no: usize, arg: *mut usize) { - let cam_irq: &mut CamIrq = unsafe { &mut *(arg as *mut CamIrq) }; - // clear the pending interrupt - assume it's just the camera for now - let pending = cam_irq.csr.r(utra::irqarray8::EV_PENDING); - cam_irq.csr.wo(utra::irqarray8::EV_PENDING, pending); - - // activate the handler - xous::try_send_message( - cam_irq.cid, - xous::Message::new_scalar(Opcode::CamIrq.to_usize().unwrap(), pending as usize, 0, 0, 0), - ) - .ok(); -} - -pub fn wrapped_main() -> ! { - log_server::init_wait().unwrap(); - log::set_max_level(log::LevelFilter::Info); - - let xns = xous_names::XousNames::new().unwrap(); - let sid = xns.register_name("bao video subsystem", None).expect("can't register server"); - - let tt = ticktimer::Ticktimer::new().unwrap(); - - let iox = cram_hal_service::IoxHal::new(); - let udma_global = cram_hal_service::UdmaGlobal::new(); - let mut i2c = cram_hal_service::I2c::new(); - - let mut sh1107 = cramium_hal::sh1107::Oled128x128::new(cram_hal_service::PERCLK, &iox, &udma_global); - sh1107.init(); - sh1107.buffer_swap(); - sh1107.draw(); - - // setup camera pins - let (cam_pdwn_bnk, cam_pdwn_pin) = cramium_hal::board::setup_ov2640_pins(&iox); - // disable camera powerdown - iox.set_gpio_pin_value(cam_pdwn_bnk, cam_pdwn_pin, cramium_hal::iox::IoxValue::Low); - udma_global.udma_clock_config(PeriphId::Cam, true); - // this is safe because we turned on the clocks before calling it - let mut cam = unsafe { cramium_hal::ov2640::Ov2640::new().expect("couldn't allocate camera") }; - - tt.sleep_ms(100).ok(); - - let (pid, mid) = cam.read_id(&mut i2c); - log::info!("Camera pid {:x}, mid {:x}", pid, mid); - cam.init(&mut i2c, cramium_hal::ov2640::Resolution::Res320x240); - cam.poke(&mut i2c, 0xFF, 0x00); - cam.poke(&mut i2c, 0xDA, 0x01); // YUV LE - tt.sleep_ms(1).ok(); - - let (cols, _rows) = cam.resolution(); - let border = (cols - IMAGE_WIDTH) / 2; - cam.set_slicing((border, 0), (cols - border, IMAGE_HEIGHT)); - log::info!("320x240 resolution setup with 256x240 slicing"); - - #[cfg(feature = "decongest-udma")] - log::info!("Decongest udma option enabled."); - - // register interrupt handler - let irq = xous::syscall::map_memory( - xous::MemoryAddress::new(utra::irqarray8::HW_IRQARRAY8_BASE), - None, - 4096, - xous::MemoryFlags::R | xous::MemoryFlags::W, - ) - .expect("couldn't map IRQ CSR range"); - let mut irq_csr = utralib::CSR::new(irq.as_mut_ptr() as *mut u32); - irq_csr.wo(utra::irqarray8::EV_PENDING, 0xFFFF); // clear any pending interrupts - - let cid = xous::connect(sid).unwrap(); // self-connection always succeeds - let cam_irq = CamIrq { csr: utralib::CSR::new(irq.as_mut_ptr() as *mut u32), cid }; - let irq_arg = &cam_irq as *const CamIrq as *mut usize; - log::debug!("irq_arg: {:x}", irq_arg as usize); - xous::claim_interrupt(utra::irqarray8::IRQARRAY8_IRQ, handle_irq, irq_arg).expect("couldn't claim IRQ8"); - // enable camera Rx IRQ - irq_csr.wfo(utra::irqarray8::EV_ENABLE_CAM_RX, 1); - - // kick off the first acquisition synchronous to the frame sync. With NTO/DAR-704 fix we - // could turn on the sync_sof field and skip this step. - while iox.get_gpio_pin_value(IoxPort::PB, 9) == IoxValue::High {} - cam.capture_async(); - - let mut frames = 0; - let mut frame = [0u8; IMAGE_WIDTH * IMAGE_HEIGHT]; - let mut decode_success; - let mut msg_opt = None; - loop { - xous::reply_and_receive_next(sid, &mut msg_opt).unwrap(); - let msg = msg_opt.as_mut().unwrap(); - let opcode = num_traits::FromPrimitive::from_usize(msg.body.id()).unwrap_or(Opcode::InvalidCall); - log::debug!("{:?}", opcode); - match opcode { - Opcode::CamIrq => { - #[cfg(not(feature = "decongest-udma"))] - cam.capture_async(); - - // copy the camera data to our FB - let fb: &[u32] = cam.rx_buf(); - // fb is an array of IMAGE_WIDTH x IMAGE_HEIGHT x u16 - // frame is an array of IMAGE_WIDTH x IMAGE_HEIGHT x u8 - // Take only the "Y" channel out of the fb array and write it to frame, but do it - // such that we are fetching a u32 each read from fb as this matches the native - // width of the bus (because fb is non-cacheable reading u16 ends up fetching the - // same word twice, then masking it at the CPU side in hardware). Also, the fb - // is slow to access relative to main memory. - // - // Also, commit the data to `frame` in inverse line order, e.g. flip the image - // vertically. - for (y_src, line) in fb.chunks(IMAGE_WIDTH / 2).enumerate() { - for (x_src, &u32src) in line.iter().enumerate() { - frame[(IMAGE_HEIGHT - y_src - 1) * IMAGE_WIDTH + 2 * x_src] = (u32src & 0xff) as u8; - frame[(IMAGE_HEIGHT - y_src - 1) * IMAGE_WIDTH + 2 * x_src + 1] = - ((u32src >> 16) & 0xff) as u8; - } - } - frames += 1; - - let mut candidates = Vec::::new(); - decode_success = false; - log::info!("------------- SEARCH {} -----------", frames); - let finder_width = qr::find_finders(&mut candidates, &frame, BW_THRESH, IMAGE_WIDTH) as isize; - if candidates.len() == 3 { - let candidates_orig = candidates.clone(); - let mut x_candidates: [Point; 3] = [Point::new(0, 0); 3]; - // apply homography to generate a new buffer for processing - let mut aligned = Vec::new(); - let mut qr_pixels: Option = None; - if let Some(mut qr_corners) = qr::QrCorners::from_finders( - &candidates.try_into().unwrap(), - Point::new(IMAGE_WIDTH as isize, IMAGE_HEIGHT as isize), - // add a search margin on the finder width - (finder_width + (qr::FINDER_SEARCH_MARGIN * finder_width) / (1 + 1 + 3 + 1 + 1)) - as usize, - ) { - let dims = Point::new(IMAGE_WIDTH as isize, IMAGE_HEIGHT as isize); - let mut il = qr::ImageRoi::new(&mut frame, dims, BW_THRESH); - let (src, dst) = qr_corners.mapping(&mut il, qr::HOMOGRAPHY_MARGIN); - let mut src_f: [(f32, f32); 4] = [(0.0, 0.0); 4]; - let mut dst_f: [(f32, f32); 4] = [(0.0, 0.0); 4]; - let mut all_found = true; - for (s, s_f32) in src.iter().zip(src_f.iter_mut()) { - if let Some(p) = s { - *s_f32 = p.to_f32(); - } else { - all_found = false; - } - } - for (d, d_f32) in dst.iter().zip(dst_f.iter_mut()) { - if let Some(p) = d { - *d_f32 = p.to_f32(); - } else { - all_found = false; - } - } - - if all_found { - if let Some(h) = homography::find_homography(src_f, dst_f) { - if let Some(h_inv) = h.try_inverse() { - log::info!("{:?}", h_inv); - let h_inv_fp = homography::matrix3_to_fixp(h_inv); - log::info!("{:?}", h_inv_fp); - - aligned = vec![0u8; qr_corners.qr_pixels() * qr_corners.qr_pixels()]; - // iterate through pixels and apply homography - for y in 0..qr_corners.qr_pixels() { - for x in 0..qr_corners.qr_pixels() { - let (x_src, y_src) = homography::apply_fixp_homography( - &h_inv_fp, - (x as i32, y as i32), - ); - if (x_src as i32 >= 0) - && ((x_src as i32) < dims.x as i32) - && (y_src as i32 >= 0) - && ((y_src as i32) < dims.y as i32) - { - // println!("{},{} -> {},{}", x_src as i32, y_src as i32, x, - // y); - aligned[qr_corners.qr_pixels() * y as usize + x as usize] = - frame[IMAGE_WIDTH * y_src as usize + x_src as usize]; - } else { - aligned[qr_corners.qr_pixels() * y as usize + x as usize] = - 255; - } - } - } - - // we can also know the location of the finders by transforming them - let h_fp = homography::matrix3_to_fixp(h); - for (i, &c) in candidates_orig.iter().enumerate() { - let (x, y) = homography::apply_fixp_homography( - &h_fp, - (c.x as i32, c.y as i32), - ); - x_candidates[i] = Point::new(x as isize, y as isize); - } - qr_pixels = Some(qr_corners.qr_pixels()); - } - } - } - } - - if let Some(qr_width) = qr_pixels { - // show the transformed/aligned frame - frame.fill(255); - for (dst_line, src_line) in - frame.chunks_mut(IMAGE_WIDTH).zip(aligned.chunks(qr_width)) - { - // "center up" the QR in the middle by the estimated margin - // this is just a perceptual trick to prevent users from shifting the position of - // the camera - for (dst, &src) in dst_line[qr::HOMOGRAPHY_MARGIN.abs() as usize..] - .iter_mut() - .zip(src_line.iter()) - { - *dst = src; - } - } - blit_to_display(&mut sh1107, &frame, true); - - // we now have a QR code in "canonical" orientation, with a - // known width in pixels - for &x in x_candidates.iter() { - log::info!("transformed finder location {:?}", x); - } - - // Confirm that the finders coordinates are valid - let mut checked_candidates = Vec::::new(); - let x_finder_width = - qr::find_finders(&mut checked_candidates, &aligned, BW_THRESH, qr_width as _) - as isize; - log::info!("x_finder width: {}", x_finder_width); - - // check that the new coordinates are within delta pixels of the original - const XFORM_DELTA: isize = 2; - let mut deltas = Vec::::new(); - for c in checked_candidates { - log::info!("x_point: {:?}", c); - for &xformed in x_candidates.iter() { - let delta = xformed - c; - log::info!("delta: {:?}", delta); - if delta.x.abs() <= XFORM_DELTA && delta.y.abs() <= XFORM_DELTA { - deltas.push(delta); - } - } - } - if deltas.len() == 3 { - let (version, modules) = qr::guess_code_version( - x_finder_width as usize, - (qr_width as isize + qr::HOMOGRAPHY_MARGIN * 2) as usize, - ); - - log::info!("image dims: {}", qr_width); - log::info!("guessed version: {}, modules: {}", version, modules); - log::info!( - "QR symbol width in pixels: {}", - qr_width - 2 * (qr::HOMOGRAPHY_MARGIN.abs() as usize) - ); - - let qr = qr::ImageRoi::new( - &mut aligned, - Point::new(qr_width as _, qr_width as _), - BW_THRESH, - ); - let grid = modules::stream_to_grid( - &qr, - qr_width, - modules, - qr::HOMOGRAPHY_MARGIN.abs() as usize, - ); - - println!("grid len {}", grid.len()); - for y in 0..modules { - for x in 0..modules { - if grid[y * modules + x] { - print!("X"); - } else { - print!(" "); - } - } - println!(" {:2}", y); - } - let simple = rqrr::SimpleGrid::from_func(modules, |x, y| { - grid[(modules - 1) - x + y * modules] - }); - let grid = rqrr::Grid::new(simple); - match grid.decode() { - Ok((meta, content)) => { - log::info!("meta: {:?}", meta); - log::info!("************ {} ***********", content); - decode_success = true; - gfx::msg( - &mut sh1107, - &format!("{:?}", meta), - Point::new(0, 0), - Mono::White.into(), - Mono::Black.into(), - ); - gfx::msg( - &mut sh1107, - &format!("{:?}", content), - Point::new(0, 64), - Mono::White.into(), - Mono::Black.into(), - ); - } - Err(e) => { - log::info!("{:?}", e); - gfx::msg( - &mut sh1107, - &format!("{:?}", e), - Point::new(0, 0), - Mono::White.into(), - Mono::Black.into(), - ); - } - } - } else { - log::info!("Transformed image did not survive sanity check!"); - gfx::msg( - &mut sh1107, - "Hold device steady...", - Point::new(0, 0), - Mono::White.into(), - Mono::Black.into(), - ); - } - } else { - blit_to_display(&mut sh1107, &frame, true); - for c in candidates_orig.iter() { - log::debug!("****** candidate: {}, {} ******", c.x, c.y); - // remap image to screen coordinates (it's 2:1) - let c_screen = *c / 2; - // flip coordinates to match the camera data - // c_screen = Point::new(c_screen.x, sh1107.dimensions().y - 1 - c_screen.y); - qr::draw_crosshair(&mut sh1107, c_screen); - } - gfx::msg( - &mut sh1107, - "Align the QR code...", - Point::new(0, 0), - Mono::White.into(), - Mono::Black.into(), - ); - } - } else { - // blit raw camera fb to sh1107 - blit_to_display(&mut sh1107, &frame, true); - gfx::msg( - &mut sh1107, - "Scan QR code...", - Point::new(0, 0), - Mono::White.into(), - Mono::Black.into(), - ); - } - - // swap the double buffer and update to the display - sh1107.buffer_swap(); - sh1107.draw(); - if decode_success { - tt.sleep_ms(2000).ok(); - } - - // clear the front buffer - sh1107.clear(); - - // re-initiate the capture. This is done at the bottom of the loop because UDMA - // congestion leads to system instability. When this problem is solved, we would - // actually want to re-initiate the capture immediately (or leave it on continuous mode) - // to allow capture to process concurrently with the code. However, there is a bug - // in the SPIM block that prevents proper usage with high bus contention that should - // be fixed in NTO. - const TIMEOUT_MS: u64 = 100; - #[cfg(feature = "decongest-udma")] - { - let start = tt.elapsed_ms(); - let mut now = tt.elapsed_ms(); - // this is required because if we initiate the capture in the middle - // of a frame, we get an offset result. This should be fixed by DAR-704 - // on NTO if the pull request is accepted; in which case, we can just rely - // on setting bit 30 of the CFG_GLOBAL register which will cause any - // RX start request to align to the beginning of a frame automatically. - while iox.get_gpio_pin_value(IoxPort::PB, 9) == IoxValue::High - && ((now - start) < TIMEOUT_MS) - { - now = tt.elapsed_ms(); - } - if now - start >= TIMEOUT_MS { - log::info!("Timeout before capture_async()!"); - } - cam.capture_async(); - } - - // this is no longer the case because we're relying on interrupts to wake us up. - /* - // wait for the transfer to finish - let start = tt.elapsed_ms(); - let mut now = tt.elapsed_ms(); - use cramium_hal::udma::Udma; - while cam.udma_busy(cramium_hal::udma::Bank::Rx) && ((now - start) < TIMEOUT_MS) { - now = tt.elapsed_ms(); - // busy-wait to get better time resolution on when the frame ends - } - if now - start >= TIMEOUT_MS { - log::info!("Timeout before rx_buf()!"); - } - */ - } - Opcode::InvalidCall => { - log::error!("Invalid call to bao video server: {:?}", msg); - } - } - } -} diff --git a/services/bao-video/src/main.rs b/services/bao-video/src/main.rs index a112b85a9..afbe2d944 100644 --- a/services/bao-video/src/main.rs +++ b/services/bao-video/src/main.rs @@ -1,8 +1,3 @@ -use cramium_hal::iox::{IoxPort, IoxValue}; -use cramium_hal::sh1107::{Mono, Oled128x128}; -use cramium_hal::udma::PeriphId; -use num_traits::ToPrimitive; -use utralib::utra; use ux_api::minigfx::*; mod api; @@ -12,16 +7,25 @@ mod modules; mod qr; use api::*; #[cfg(feature = "board-baosec")] -mod hw; -#[cfg(feature = "board-baosec")] -use hw::wrapped_main; - -#[cfg(feature = "hosted-baosec")] -mod hosted; +use cram_hal_service::{I2c, UdmaGlobal}; +use cramium_api::*; #[cfg(feature = "hosted-baosec")] -use hosted::wrapped_main; +use cramium_emu::{ + camera::Ov2640, + display::{MainThreadToken, Mono, Oled128x128, claim_main_thread}, + i2c::I2c, + udma::UdmaGlobal, +}; +#[cfg(feature = "board-baosec")] +use cramium_hal::{ + ov2640::Ov2640, + sh1107::{MainThreadToken, Mono, Oled128x128, claim_main_thread}, +}; +use num_traits::ToPrimitive; +#[cfg(not(feature = "hosted-baosec"))] +use utralib::utra; -// Scope of this crate: +// Scope of this crate: *No calls to modals* this can create dependency lockups. // // bao-video contains the platform-specific drivers for the baosec platform that pertain // to video: both the capture of video, as well as any operations involving drawing to @@ -51,7 +55,475 @@ pub const BW_THRESH: u8 = 128; // luck of the draw if the interpolation hits exactly right, or if we're roughly a module // off from ideal, which causes the data around that point to be interpreted incorrectly. +pub fn blit_to_display(sh1107: &mut Oled128x128, frame: &[u8], display_cleared: bool) { + for (y, row) in frame.chunks(IMAGE_WIDTH).enumerate() { + if y & 1 == 0 { + for (x, &pixval) in row.iter().enumerate() { + if x & 1 == 0 { + if x < sh1107.dimensions().x as usize * 2 + && y < sh1107.dimensions().y as usize * 2 - (gfx::CHAR_HEIGHT as usize + 1) * 2 + { + let luminance = pixval & 0xff; + if luminance > BW_THRESH { + sh1107.put_pixel(Point::new(x as isize / 2, y as isize / 2), Mono::White.into()); + } else { + // optimization to avoid some computation if we're blitting to an already-black + // buffer + if !display_cleared { + sh1107.put_pixel( + Point::new(x as isize / 2, y as isize / 2), + Mono::Black.into(), + ); + } + } + } else { + break; + } + } + } + } + } +} + +#[repr(align(32))] +#[cfg(not(feature = "hosted-baosec"))] +struct CamIrq { + csr: utralib::CSR, + cid: u32, +} + +#[cfg(not(feature = "hosted-baosec"))] +fn handle_irq(_irq_no: usize, arg: *mut usize) { + let cam_irq: &mut CamIrq = unsafe { &mut *(arg as *mut CamIrq) }; + // clear the pending interrupt - assume it's just the camera for now + let pending = cam_irq.csr.r(utra::irqarray8::EV_PENDING); + cam_irq.csr.wo(utra::irqarray8::EV_PENDING, pending); + + // activate the handler + xous::try_send_message( + cam_irq.cid, + xous::Message::new_scalar(Opcode::CamIrq.to_usize().unwrap(), pending as usize, 0, 0, 0), + ) + .ok(); +} +#[cfg(feature = "hosted-baosec")] +fn handle_irq(_irq_no: usize, arg: *mut usize) {} + fn main() -> ! { let stack_size = 1 * 1024 * 1024; - std::thread::Builder::new().stack_size(stack_size).spawn(wrapped_main).unwrap().join().unwrap() + claim_main_thread(move |main_thread_token| { + std::thread::Builder::new() + .stack_size(stack_size) + .spawn(move || wrapped_main(main_thread_token)) + .unwrap() + .join() + .unwrap() + }) +} + +pub fn wrapped_main(main_thread_token: MainThreadToken) -> ! { + log_server::init_wait().unwrap(); + log::set_max_level(log::LevelFilter::Info); + + let xns = xous_names::XousNames::new().unwrap(); + let sid = xns.register_name(crate::api::SERVER_NAME_GFX, None).expect("can't register server"); + + let tt = ticktimer::Ticktimer::new().unwrap(); + + let iox = IoxHal::new(); + let udma_global = UdmaGlobal::new(); + let mut i2c = I2c::new(); + + let mut sh1107 = Oled128x128::new(main_thread_token, cramium_api::PERCLK, &iox, &udma_global); + sh1107.init(); + sh1107.buffer_swap(); + sh1107.draw(); + + #[cfg(not(feature = "hosted-baosec"))] + { + // setup camera pins + let (cam_pdwn_bnk, cam_pdwn_pin) = cramium_hal::board::setup_ov2640_pins(&iox); + // disable camera powerdown + iox.set_gpio_pin_value(cam_pdwn_bnk, cam_pdwn_pin, IoxValue::Low); + } + udma_global.udma_clock_config(PeriphId::Cam, true); + // this is safe because we turned on the clocks before calling it + let mut cam = unsafe { Ov2640::new().expect("couldn't allocate camera") }; + + tt.sleep_ms(100).ok(); + + let (pid, mid) = cam.read_id(&mut i2c); + log::info!("Camera pid {:x}, mid {:x}", pid, mid); + cam.init(&mut i2c, cramium_api::camera::Resolution::Res320x240); + cam.poke(&mut i2c, 0xFF, 0x00); + cam.poke(&mut i2c, 0xDA, 0x01); // YUV LE + tt.sleep_ms(1).ok(); + + let (cols, _rows) = cam.resolution(); + let border = (cols - IMAGE_WIDTH) / 2; + cam.set_slicing((border, 0), (cols - border, IMAGE_HEIGHT)); + log::info!("320x240 resolution setup with 256x240 slicing"); + + #[cfg(feature = "decongest-udma")] + log::info!("Decongest udma option enabled."); + + let cid = xous::connect(sid).unwrap(); // self-connection always succeeds + // register interrupt handler + #[cfg(not(feature = "hosted-baosec"))] + { + let irq = xous::syscall::map_memory( + xous::MemoryAddress::new(utra::irqarray8::HW_IRQARRAY8_BASE), + None, + 4096, + xous::MemoryFlags::R | xous::MemoryFlags::W, + ) + .expect("couldn't map IRQ CSR range"); + let mut irq_csr = utralib::CSR::new(irq.as_mut_ptr() as *mut u32); + irq_csr.wo(utra::irqarray8::EV_PENDING, 0xFFFF); // clear any pending interrupts + + let cam_irq = CamIrq { csr: utralib::CSR::new(irq.as_mut_ptr() as *mut u32), cid }; + let irq_arg = &cam_irq as *const CamIrq as *mut usize; + log::debug!("irq_arg: {:x}", irq_arg as usize); + xous::claim_interrupt(utra::irqarray8::IRQARRAY8_IRQ, handle_irq, irq_arg) + .expect("couldn't claim IRQ8"); + // enable camera Rx IRQ + irq_csr.wfo(utra::irqarray8::EV_ENABLE_CAM_RX, 1); + } + + // kick off the first acquisition synchronous to the frame sync. With NTO/DAR-704 fix we + // could turn on the sync_sof field and skip this step. + while iox.get_gpio_pin_value(IoxPort::PB, 9) == IoxValue::High {} + cam.capture_async(); + + let mut frames = 0; + let mut frame = [0u8; IMAGE_WIDTH * IMAGE_HEIGHT]; + let mut decode_success; + let mut msg_opt = None; + loop { + xous::reply_and_receive_next(sid, &mut msg_opt).unwrap(); + let msg = msg_opt.as_mut().unwrap(); + let opcode = num_traits::FromPrimitive::from_usize(msg.body.id()).unwrap_or(Opcode::InvalidCall); + log::debug!("{:?}", opcode); + match opcode { + Opcode::CamIrq => { + #[cfg(not(feature = "decongest-udma"))] + cam.capture_async(); + + // copy the camera data to our FB + let fb: &[u32] = cam.rx_buf(); + // fb is an array of IMAGE_WIDTH x IMAGE_HEIGHT x u16 + // frame is an array of IMAGE_WIDTH x IMAGE_HEIGHT x u8 + // Take only the "Y" channel out of the fb array and write it to frame, but do it + // such that we are fetching a u32 each read from fb as this matches the native + // width of the bus (because fb is non-cacheable reading u16 ends up fetching the + // same word twice, then masking it at the CPU side in hardware). Also, the fb + // is slow to access relative to main memory. + // + // Also, commit the data to `frame` in inverse line order, e.g. flip the image + // vertically. + for (y_src, line) in fb.chunks(IMAGE_WIDTH / 2).enumerate() { + for (x_src, &u32src) in line.iter().enumerate() { + frame[(IMAGE_HEIGHT - y_src - 1) * IMAGE_WIDTH + 2 * x_src] = (u32src & 0xff) as u8; + frame[(IMAGE_HEIGHT - y_src - 1) * IMAGE_WIDTH + 2 * x_src + 1] = + ((u32src >> 16) & 0xff) as u8; + } + } + frames += 1; + + let mut candidates = Vec::::new(); + decode_success = false; + log::info!("------------- SEARCH {} -----------", frames); + let finder_width = qr::find_finders(&mut candidates, &frame, BW_THRESH, IMAGE_WIDTH) as isize; + if candidates.len() == 3 { + let candidates_orig = candidates.clone(); + let mut x_candidates: [Point; 3] = [Point::new(0, 0); 3]; + // apply homography to generate a new buffer for processing + let mut aligned = Vec::new(); + let mut qr_pixels: Option = None; + if let Some(mut qr_corners) = qr::QrCorners::from_finders( + &candidates.try_into().unwrap(), + Point::new(IMAGE_WIDTH as isize, IMAGE_HEIGHT as isize), + // add a search margin on the finder width + (finder_width + (qr::FINDER_SEARCH_MARGIN * finder_width) / (1 + 1 + 3 + 1 + 1)) + as usize, + ) { + let dims = Point::new(IMAGE_WIDTH as isize, IMAGE_HEIGHT as isize); + let mut il = qr::ImageRoi::new(&mut frame, dims, BW_THRESH); + let (src, dst) = qr_corners.mapping(&mut il, qr::HOMOGRAPHY_MARGIN); + let mut src_f: [(f32, f32); 4] = [(0.0, 0.0); 4]; + let mut dst_f: [(f32, f32); 4] = [(0.0, 0.0); 4]; + let mut all_found = true; + for (s, s_f32) in src.iter().zip(src_f.iter_mut()) { + if let Some(p) = s { + *s_f32 = p.to_f32(); + } else { + all_found = false; + } + } + for (d, d_f32) in dst.iter().zip(dst_f.iter_mut()) { + if let Some(p) = d { + *d_f32 = p.to_f32(); + } else { + all_found = false; + } + } + + if all_found { + if let Some(h) = homography::find_homography(src_f, dst_f) { + if let Some(h_inv) = h.try_inverse() { + log::info!("{:?}", h_inv); + let h_inv_fp = homography::matrix3_to_fixp(h_inv); + log::info!("{:?}", h_inv_fp); + + aligned = vec![0u8; qr_corners.qr_pixels() * qr_corners.qr_pixels()]; + // iterate through pixels and apply homography + for y in 0..qr_corners.qr_pixels() { + for x in 0..qr_corners.qr_pixels() { + let (x_src, y_src) = homography::apply_fixp_homography( + &h_inv_fp, + (x as i32, y as i32), + ); + if (x_src as i32 >= 0) + && ((x_src as i32) < dims.x as i32) + && (y_src as i32 >= 0) + && ((y_src as i32) < dims.y as i32) + { + // println!("{},{} -> {},{}", x_src as i32, y_src as i32, x, + // y); + aligned[qr_corners.qr_pixels() * y as usize + x as usize] = + frame[IMAGE_WIDTH * y_src as usize + x_src as usize]; + } else { + aligned[qr_corners.qr_pixels() * y as usize + x as usize] = + 255; + } + } + } + + // we can also know the location of the finders by transforming them + let h_fp = homography::matrix3_to_fixp(h); + for (i, &c) in candidates_orig.iter().enumerate() { + let (x, y) = homography::apply_fixp_homography( + &h_fp, + (c.x as i32, c.y as i32), + ); + x_candidates[i] = Point::new(x as isize, y as isize); + } + qr_pixels = Some(qr_corners.qr_pixels()); + } + } + } + } + + if let Some(qr_width) = qr_pixels { + // show the transformed/aligned frame + frame.fill(255); + for (dst_line, src_line) in + frame.chunks_mut(IMAGE_WIDTH).zip(aligned.chunks(qr_width)) + { + // "center up" the QR in the middle by the estimated margin + // this is just a perceptual trick to prevent users from shifting the position of + // the camera + for (dst, &src) in dst_line[qr::HOMOGRAPHY_MARGIN.abs() as usize..] + .iter_mut() + .zip(src_line.iter()) + { + *dst = src; + } + } + blit_to_display(&mut sh1107, &frame, true); + + // we now have a QR code in "canonical" orientation, with a + // known width in pixels + for &x in x_candidates.iter() { + log::info!("transformed finder location {:?}", x); + } + + // Confirm that the finders coordinates are valid + let mut checked_candidates = Vec::::new(); + let x_finder_width = + qr::find_finders(&mut checked_candidates, &aligned, BW_THRESH, qr_width as _) + as isize; + log::info!("x_finder width: {}", x_finder_width); + + // check that the new coordinates are within delta pixels of the original + const XFORM_DELTA: isize = 2; + let mut deltas = Vec::::new(); + for c in checked_candidates { + log::info!("x_point: {:?}", c); + for &xformed in x_candidates.iter() { + let delta = xformed - c; + log::info!("delta: {:?}", delta); + if delta.x.abs() <= XFORM_DELTA && delta.y.abs() <= XFORM_DELTA { + deltas.push(delta); + } + } + } + if deltas.len() == 3 { + let (version, modules) = qr::guess_code_version( + x_finder_width as usize, + (qr_width as isize + qr::HOMOGRAPHY_MARGIN * 2) as usize, + ); + + log::info!("image dims: {}", qr_width); + log::info!("guessed version: {}, modules: {}", version, modules); + log::info!( + "QR symbol width in pixels: {}", + qr_width - 2 * (qr::HOMOGRAPHY_MARGIN.abs() as usize) + ); + + let qr = qr::ImageRoi::new( + &mut aligned, + Point::new(qr_width as _, qr_width as _), + BW_THRESH, + ); + let grid = modules::stream_to_grid( + &qr, + qr_width, + modules, + qr::HOMOGRAPHY_MARGIN.abs() as usize, + ); + + println!("grid len {}", grid.len()); + for y in 0..modules { + for x in 0..modules { + if grid[y * modules + x] { + print!("X"); + } else { + print!(" "); + } + } + println!(" {:2}", y); + } + let simple = rqrr::SimpleGrid::from_func(modules, |x, y| { + grid[(modules - 1) - x + y * modules] + }); + let grid = rqrr::Grid::new(simple); + match grid.decode() { + Ok((meta, content)) => { + log::info!("meta: {:?}", meta); + log::info!("************ {} ***********", content); + decode_success = true; + gfx::msg( + &mut sh1107, + &format!("{:?}", meta), + Point::new(0, 0), + Mono::White.into(), + Mono::Black.into(), + ); + gfx::msg( + &mut sh1107, + &format!("{:?}", content), + Point::new(0, 64), + Mono::White.into(), + Mono::Black.into(), + ); + } + Err(e) => { + log::info!("{:?}", e); + gfx::msg( + &mut sh1107, + &format!("{:?}", e), + Point::new(0, 0), + Mono::White.into(), + Mono::Black.into(), + ); + } + } + } else { + log::info!("Transformed image did not survive sanity check!"); + gfx::msg( + &mut sh1107, + "Hold device steady...", + Point::new(0, 0), + Mono::White.into(), + Mono::Black.into(), + ); + } + } else { + blit_to_display(&mut sh1107, &frame, true); + for c in candidates_orig.iter() { + log::debug!("****** candidate: {}, {} ******", c.x, c.y); + // remap image to screen coordinates (it's 2:1) + let c_screen = *c / 2; + // flip coordinates to match the camera data + // c_screen = Point::new(c_screen.x, sh1107.dimensions().y - 1 - c_screen.y); + qr::draw_crosshair(&mut sh1107, c_screen); + } + gfx::msg( + &mut sh1107, + "Align the QR code...", + Point::new(0, 0), + Mono::White.into(), + Mono::Black.into(), + ); + } + } else { + // blit raw camera fb to sh1107 + blit_to_display(&mut sh1107, &frame, true); + gfx::msg( + &mut sh1107, + "Scan QR code...", + Point::new(0, 0), + Mono::White.into(), + Mono::Black.into(), + ); + } + + // swap the double buffer and update to the display + sh1107.buffer_swap(); + sh1107.draw(); + if decode_success { + tt.sleep_ms(2000).ok(); + } + + // clear the front buffer + sh1107.clear(); + + // re-initiate the capture. This is done at the bottom of the loop because UDMA + // congestion leads to system instability. When this problem is solved, we would + // actually want to re-initiate the capture immediately (or leave it on continuous mode) + // to allow capture to process concurrently with the code. However, there is a bug + // in the SPIM block that prevents proper usage with high bus contention that should + // be fixed in NTO. + const TIMEOUT_MS: u64 = 100; + #[cfg(feature = "decongest-udma")] + { + let start = tt.elapsed_ms(); + let mut now = tt.elapsed_ms(); + // this is required because if we initiate the capture in the middle + // of a frame, we get an offset result. This should be fixed by DAR-704 + // on NTO if the pull request is accepted; in which case, we can just rely + // on setting bit 30 of the CFG_GLOBAL register which will cause any + // RX start request to align to the beginning of a frame automatically. + while iox.get_gpio_pin_value(IoxPort::PB, 9) == IoxValue::High + && ((now - start) < TIMEOUT_MS) + { + now = tt.elapsed_ms(); + } + if now - start >= TIMEOUT_MS { + log::info!("Timeout before capture_async()!"); + } + cam.capture_async(); + } + + // this is no longer the case because we're relying on interrupts to wake us up. + /* + // wait for the transfer to finish + let start = tt.elapsed_ms(); + let mut now = tt.elapsed_ms(); + use cramium_hal::udma::Udma; + while cam.udma_busy(cramium_hal::udma::Bank::Rx) && ((now - start) < TIMEOUT_MS) { + now = tt.elapsed_ms(); + // busy-wait to get better time resolution on when the frame ends + } + if now - start >= TIMEOUT_MS { + log::info!("Timeout before rx_buf()!"); + } + */ + } + Opcode::InvalidCall => { + log::error!("Invalid call to bao video server: {:?}", msg); + } + } + } } diff --git a/services/cram-hal-service/Cargo.toml b/services/cram-hal-service/Cargo.toml index 7c149d73e..2ff1207ee 100644 --- a/services/cram-hal-service/Cargo.toml +++ b/services/cram-hal-service/Cargo.toml @@ -19,6 +19,10 @@ cramium-hal = { path = "../../libs/cramium-hal", features = [ "derive-rkyv", "std", ] } +cramium-api = { path = "../../libs/cramium-api", features = [ + "derive-rkyv", + "std", +] } xous-pl230 = { path = "../../libs/xous-pl230", features = ["cramium-soc"] } # xous-pio = { path = "../../libs/xous-pio", default-features = false, features = [ # "cramium-soc", diff --git a/services/cram-hal-service/src/api.rs b/services/cram-hal-service/src/api.rs index 11a4f543a..c6a1fd3a9 100644 --- a/services/cram-hal-service/src/api.rs +++ b/services/cram-hal-service/src/api.rs @@ -1,7 +1,3 @@ -pub mod keyboard; -use cramium_hal::iox::{self, IoxPort, IoxValue}; -pub use keyboard::*; - /// The Opcode numbers here should not be changed. You can add new ones, /// but do not re-use old numbers or repurpose them. This is because the /// numbers are hard-coded in other libraries in order to break circular @@ -103,58 +99,3 @@ pub enum MemoryLcdOpcode { /// Sets a callback address as SID + opcode for pingbacks when the blit is done. SetCallbackServer = 3, } - -#[derive(Debug, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)] -pub struct IoxConfigMessage { - pub port: iox::IoxPort, - pub pin: u8, - pub direction: Option, - pub function: Option, - pub schmitt_trigger: Option, - pub pullup: Option, - pub slow_slew: Option, - pub strength: Option, -} - -#[derive(Debug, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize, PartialEq, Eq)] -pub enum I2cTransactionType { - Write, - Read, - ReadRepeatedStart, -} - -#[derive(Debug, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)] -pub enum I2cResult { - /// For the outbound message holder - Pending, - /// Returns # of bytes read or written if successful - Ack(usize), - /// An error occurred. - Nack, -} - -#[derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)] -pub struct I2cTransaction { - pub i2c_type: I2cTransactionType, - pub device: u8, - pub address: u8, - pub data: Vec, - pub result: I2cResult, -} - -#[derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)] -pub struct I2cTransactions { - pub transactions: Vec, -} -impl From> for I2cTransactions { - fn from(value: Vec) -> Self { Self { transactions: value } } -} - -#[derive(Debug, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)] -pub struct IoxIrqRegistration { - pub server: String, - pub opcode: usize, - pub port: IoxPort, - pub pin: u8, - pub active: IoxValue, -} diff --git a/services/cram-hal-service/src/api/keyboard.rs b/services/cram-hal-service/src/api/keyboard.rs deleted file mode 100644 index 9c08e99cc..000000000 --- a/services/cram-hal-service/src/api/keyboard.rs +++ /dev/null @@ -1,115 +0,0 @@ -use std::fmt::Display; - -pub const SERVER_NAME_KBD: &str = "_Matrix keyboard driver_"; - -#[derive(Debug, Default, Copy, Clone)] -#[allow(dead_code)] -pub struct ScanCode { - /// base key value - pub key: Option, - /// tap blue shift key, then key - pub shift: Option, - /// hold blue shift key, then key - pub hold: Option, - /// hold orange shift key, then key - pub alt: Option, -} - -/// Maintainer note: there is a "BackupKeyboardLayout" serializer inside -/// root-keys/api.rs that needs to be updated when this changes. -#[derive(Debug, Copy, Clone, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)] -pub enum KeyMap { - Qwerty, - Azerty, - Qwertz, - Dvorak, - Braille, - Undefined, -} -impl From for KeyMap { - fn from(code: usize) -> Self { - match code { - 0 => KeyMap::Qwerty, - 1 => KeyMap::Azerty, - 2 => KeyMap::Qwertz, - 3 => KeyMap::Dvorak, - 4 => KeyMap::Braille, - _ => KeyMap::Qwerty, - } - } -} -impl From for usize { - fn from(map: KeyMap) -> usize { - match map { - // note: these indicese correspond to the position on the keyboard menu - KeyMap::Qwerty => 0, - KeyMap::Azerty => 1, - KeyMap::Qwertz => 2, - KeyMap::Dvorak => 3, - KeyMap::Braille => 4, - KeyMap::Undefined => 255, - } - } -} - -impl Display for KeyMap { - fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - match self { - Self::Azerty => write!(f, "AZERTY"), - Self::Qwerty => write!(f, "QWERTY"), - Self::Qwertz => write!(f, "QWERTZ"), - Self::Dvorak => write!(f, "Dvorak"), - Self::Braille => write!(f, "Braille"), - Self::Undefined => write!(f, "Undefined"), - } - } -} - -// Opcodes are pinned down to allow for unsafe FFI extraction of key hits -#[derive(Debug, num_derive::FromPrimitive, num_derive::ToPrimitive)] -pub(crate) enum KeyboardOpcode { - /// set which keyboard mapping is present - SelectKeyMap = 0, //(KeyMap), - GetKeyMap = 1, - - /// request for ScanCodes - RegisterListener = 2, - - /// request for updates for *when* keyboard is pressed - RegisterKeyObserver = 12, - - /// set repeat delay, rate; both in ms - SetRepeat = 4, //(u32, u32), - - /// set chording interval (how long to wait for all keydowns to happen before interpreting as a chord), - /// in ms (for braille keyboards) - SetChordInterval = 5, //(u32), - - /// used by host mode emulation and debug UART to inject keys - InjectKey = 6, //(char), - - /// used by the interrupt handler to transfer results to the main loop - HandlerTrigger = 7, - - /// a blocking key listener - blocks until a key is hit - BlockingKeyListener = 9, -} - -// this structure is used to register a keyboard listener. Currently, we only accept -// one trusted listener (enforced by name server and structurally in the code), -// which is the GAM. -#[derive(Debug, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize, Clone)] -pub(crate) struct KeyboardRegistration { - pub server_name: String, - pub listener_op_id: usize, -} - -#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, Hash)] -pub struct RowCol { - pub r: u8, - pub c: u8, -} -impl RowCol { - #[allow(dead_code)] - pub fn new(r: u8, c: u8) -> RowCol { RowCol { r, c } } -} diff --git a/services/cram-hal-service/src/hw/keyboard.rs b/services/cram-hal-service/src/hw/keyboard.rs index 84083283a..901e7a52e 100644 --- a/services/cram-hal-service/src/hw/keyboard.rs +++ b/services/cram-hal-service/src/hw/keyboard.rs @@ -1,10 +1,8 @@ +use cramium_api::keyboard::*; use num_traits::*; use xous::{CID, MessageSender, msg_blocking_scalar_unpack, msg_scalar_unpack}; use xous_ipc::Buffer; -use crate::api; -pub use crate::api::keyboard::*; - pub fn start_keyboard_service() { std::thread::spawn(move || { keyboard_service(); @@ -20,7 +18,7 @@ fn keyboard_bouncer() { let sid = xous::create_server_with_address(b"keyboard_bouncer") .expect("couldn't create keyboard log bounce server"); let xns = xous_names::XousNames::new().unwrap(); - let kbd = cram_hal_service::keyboard::Keyboard::new(&xns).unwrap(); + let kbd = cramium_api::keyboard::Keyboard::new(&xns).unwrap(); let mut msg_opt = None; loop { xous::reply_and_receive_next(sid, &mut msg_opt).unwrap(); @@ -39,7 +37,7 @@ fn keyboard_bouncer() { fn keyboard_service() { let xns = xous_names::XousNames::new().unwrap(); - let kbd_sid = xns.register_name(api::SERVER_NAME_KBD, None).expect("can't register server"); + let kbd_sid = xns.register_name(cramium_api::SERVER_NAME_KBD, None).expect("can't register server"); let mut listener_conn: Option = None; let mut listener_op: Option = None; diff --git a/services/cram-hal-service/src/i2c_lib.rs b/services/cram-hal-service/src/i2c_lib.rs index dd20cade6..f33d0297f 100644 --- a/services/cram-hal-service/src/i2c_lib.rs +++ b/services/cram-hal-service/src/i2c_lib.rs @@ -1,9 +1,9 @@ use core::sync::atomic::{AtomicU32, Ordering}; -use cramium_hal::udma::I2cApi; +use cramium_api::*; use xous_ipc::Buffer; -use crate::api::{I2cResult, I2cTransaction, I2cTransactionType, I2cTransactions, Opcode}; +use crate::api::Opcode; pub struct I2c { conn: xous::CID, diff --git a/services/cram-hal-service/src/lib.rs b/services/cram-hal-service/src/lib.rs index 814d1f2a4..5ad152cba 100644 --- a/services/cram-hal-service/src/lib.rs +++ b/services/cram-hal-service/src/lib.rs @@ -1,21 +1,13 @@ pub mod api; pub mod i2c_lib; -pub mod iox_lib; -pub mod keyboard; pub mod trng; +use core::sync::atomic::{AtomicU32, Ordering}; + use api::Opcode; -use cramium_hal::udma::{EventChannel, PeriphEventType, PeriphId, UdmaGlobalConfig}; +use cramium_api::*; pub use i2c_lib::*; -pub use iox_lib::*; use num_traits::*; - -/// Do not change this constant, it is hard-coded into libraries in order to break -/// circular dependencies on the IFRAM block. -pub const SERVER_NAME_CRAM_HAL: &str = "_Cramium-SoC HAL_"; -pub const PERCLK: u32 = 100_000_000; - -use core::sync::atomic::{AtomicU32, Ordering}; static REFCOUNT: AtomicU32 = AtomicU32::new(0); pub struct UdmaGlobal { diff --git a/services/cram-hal-service/src/main.rs b/services/cram-hal-service/src/main.rs index f62a02365..35d8c0232 100644 --- a/services/cram-hal-service/src/main.rs +++ b/services/cram-hal-service/src/main.rs @@ -3,10 +3,8 @@ mod hw; use api::*; use bitfield::*; -use cramium_hal::{ - iox::{self, IoxPort, IoxValue}, - udma::{EventChannel, GlobalConfig, I2cApi, PeriphId}, -}; +use cramium_api::*; +use cramium_hal::{iox::Iox, udma::GlobalConfig}; use num_traits::*; use utralib::CSR; #[cfg(feature = "quantum-timer")] @@ -147,7 +145,7 @@ fn main() { log::info!("my PID is {}", xous::process::id()); let xns = xous_names::XousNames::new().unwrap(); - let sid = xns.register_name(cram_hal_service::SERVER_NAME_CRAM_HAL, None).expect("can't register server"); + let sid = xns.register_name(cramium_api::SERVER_NAME_CRAM_HAL, None).expect("can't register server"); let self_cid = xous::connect(sid).expect("couldn't create self-connection"); let mut ifram_allocs = [Vec::new(), Vec::new()]; @@ -186,7 +184,7 @@ fn main() { xous::MemoryFlags::R | xous::MemoryFlags::W, ) .expect("couldn't claim the IOX hardware page"); - let iox = iox::Iox::new(iox_page.as_ptr() as *mut u32); + let iox = Iox::new(iox_page.as_ptr() as *mut u32); let udma_global_csr = xous::syscall::map_memory( xous::MemoryAddress::new(utralib::generated::HW_UDMA_CTRL_BASE), @@ -225,7 +223,7 @@ fn main() { cramium_hal::udma::I2c::new_with_ifram( i2c_channel, 400_000, - cram_hal_service::PERCLK, + cramium_api::PERCLK, i2c_ifram, &udma_global, ) @@ -501,8 +499,7 @@ fn main() { } Opcode::SetGpioBank => { if let Some(scalar) = msg.body.scalar_message() { - let port: cramium_hal::iox::IoxPort = - num_traits::FromPrimitive::from_usize(scalar.arg1).unwrap(); + let port: IoxPort = num_traits::FromPrimitive::from_usize(scalar.arg1).unwrap(); let value = scalar.arg2 as u16; let bitmask = scalar.arg3 as u16; iox.set_gpio_bank(port, value, bitmask); @@ -510,8 +507,7 @@ fn main() { } Opcode::GetGpioBank => { if let Some(scalar) = msg.body.scalar_message_mut() { - let port: cramium_hal::iox::IoxPort = - num_traits::FromPrimitive::from_usize(scalar.arg1).unwrap(); + let port: IoxPort = num_traits::FromPrimitive::from_usize(scalar.arg1).unwrap(); scalar.arg1 = iox.get_gpio_bank(port) as usize; } } diff --git a/services/cramium-emu/Cargo.toml b/services/cramium-emu/Cargo.toml new file mode 100644 index 000000000..977281609 --- /dev/null +++ b/services/cramium-emu/Cargo.toml @@ -0,0 +1,30 @@ +[package] +name = "cramium-emu" +version = "0.1.0" +edition = "2021" + +[dependencies] +xous = "0.9.64" +xous-ipc = "0.10.4" +xous-names = { package = "xous-api-names", version = "0.9.65" } +"ux-api" = { path = "../../libs/ux-api" } +"rand" = "0.8" +rand_core = "0.6.4" +log = "0.4.14" +num-derive = { version = "0.4.2", default-features = false } +num-traits = { version = "0.2.14", default-features = false } +rkyv = { version = "0.8.8", default-features = false, features = [ + "std", + "alloc", +] } +cramium-api = { path = "../../libs/cramium-api", features = [ + "derive-rkyv", + "std", +] } + +[target.'cfg(any(windows,unix))'.dependencies] +minifb = "0.26.0" + +[features] +hosted-baosec = [] +default = ["hosted-baosec"] diff --git a/services/cramium-emu/src/camera.rs b/services/cramium-emu/src/camera.rs new file mode 100644 index 000000000..8fc80b5c3 --- /dev/null +++ b/services/cramium-emu/src/camera.rs @@ -0,0 +1,85 @@ +use cramium_api::camera; +use cramium_api::*; + +const EMU_COLS: usize = 320; +const EMU_ROWS: usize = 240; + +#[repr(C)] +pub struct Ov2640 { + frame: [u16; EMU_COLS * EMU_ROWS], +} + +impl Ov2640 { + /// Safety: clocks must be turned on before this is called + pub unsafe fn new() -> Result { Ok(Self { frame: [0u16; EMU_COLS * EMU_ROWS] }) } + + pub fn release_ifram(&mut self) {} + + pub fn claim_ifram(&mut self) -> Result<(), xous::Error> { Ok(()) } + + pub fn has_ifram(&self) -> bool { false } + + pub fn poke(&self, _i2c: &mut dyn I2cApi, _adr: u8, _dat: u8) {} + + // chip does not support sequential reads + pub fn peek(&self, _i2c: &mut dyn I2cApi, _adr: u8, _dat: &mut [u8]) {} + + pub fn init(&mut self, _i2c: &mut dyn I2cApi, _resolution: camera::Resolution) {} + + /// Safety: definitely not safe, but only used in emulation mode so I'm not going to worry about it too + /// much. + pub fn rx_buf(&self) -> &[T] { + unsafe { + core::slice::from_raw_parts( + self.frame.as_ptr() as *const T, + self.frame.len() / core::mem::size_of::(), + ) + } + } + + /// Safety: definitely not safe, but only used in emulation mode so I'm not going to worry about it too + /// much. + pub unsafe fn rx_buf_phys(&self) -> &[T] { + unsafe { + core::slice::from_raw_parts( + self.frame.as_ptr() as *const T, + self.frame.len() / core::mem::size_of::(), + ) + } + } + + pub fn capture_async(&mut self) {} + + pub fn capture_await(&mut self, _use_yield: bool) {} + + /// For emulation, we fix the resolution at 320x240 + pub fn resolution(&self) -> (usize, usize) { (EMU_COLS, EMU_ROWS) } + + pub fn set_slicing(&mut self, _ll: (usize, usize), _ur: (usize, usize)) {} + + pub fn disable_slicing(&mut self) {} + + // Commented out to avoid leaking the ColorMode type + // pub fn color_mode(&mut self, _i2c: &mut dyn I2cApi, _mode: ColorMode) {} + + // Commented out to avoid leaking the Contrast & Brightness types + /* + pub fn contrast_brightness( + &mut self, + _i2c: &mut dyn I2cApi, + _contrast: Contrast, + _brightness: Brightness, + ) { + } + */ + + // Commented out to avoid leaking the Effect types + // pub fn effect(&mut self, _i2c: &mut dyn I2cApi, _effect: Effect) {} + + /// Returns (product ID, manufacturer ID) - hard coded to match OV2640 values + pub fn read_id(&self, i2c: &mut dyn I2cApi) -> (u16, u16) { (0x2641, 0x7fa2) } + + pub fn delay(&self, quantum: usize) { + std::thread::sleep(std::time::Duration::from_millis(quantum as u64)); + } +} diff --git a/services/cramium-emu/src/display.rs b/services/cramium-emu/src/display.rs new file mode 100644 index 000000000..c36d692de --- /dev/null +++ b/services/cramium-emu/src/display.rs @@ -0,0 +1,377 @@ +use std::sync::{Arc, Mutex, mpsc}; + +use cramium_api::*; +use minifb::{Key, Window, WindowOptions}; +use ux_api::minigfx::{ColorNative, FrameBuffer, Point}; + +pub const COLUMN: isize = 128; +pub const ROW: isize = 128; +pub const PAGE: u8 = ROW as u8 / 8; + +const MAX_FPS: usize = 60; +const DARK_COLOUR: u32 = 0xB5B5AD; +const LIGHT_COLOUR: u32 = 0x1B1B19; + +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub struct MonoColor(ColorNative); +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub enum Mono { + Black, + White, +} +impl From for Mono { + fn from(value: ColorNative) -> Self { + match value.0 as u32 { + DARK_COLOUR => Mono::Black, + _ => Mono::White, + } + } +} +impl Into for Mono { + fn into(self) -> ColorNative { + match self { + Mono::Black => ColorNative::from(DARK_COLOUR as usize), + Mono::White => ColorNative::from(LIGHT_COLOUR as usize), + } + } +} + +#[repr(usize)] +#[derive(Eq, PartialEq, Copy, Clone)] +enum BufferState { + A = 0, + B = 2048, +} +impl BufferState { + pub fn swap(self) -> Self { if self == BufferState::A { BufferState::B } else { BufferState::A } } + + pub fn as_index(&self) -> usize { + match self { + BufferState::A => 0, + BufferState::B => 1, + } + } +} + +/// The channel for the backend to communicate back to the main thread that it +/// has claimed. +pub struct MainThreadToken(mpsc::SyncSender); + +/// A substitute for the native never type (`!`), which is still unstable on +/// `Fn` bounds. +pub enum Never {} + +/// Claim the calling thread (which must be a main thread) for use by the +/// backend and call the specified closure on a new thread. +pub fn claim_main_thread(f: impl FnOnce(MainThreadToken) -> Never + Send + 'static) -> ! { + // Some operating systems and GUI frameworks, in particular Cocoa, don't + // allow creating an event loop from a thread other than the main thread + // (TID 1) and will abort a program on violation (see issue #373), hence we + // need to claim the main thread for use by the backend. + let (send, recv) = mpsc::sync_channel(0); + + // Call the closure on a fake main thread + let fake_main_thread = std::thread::Builder::new() + .name("wrapped_main".into()) + .spawn(move || f(MainThreadToken(send))) + .unwrap(); + + // Process up to one request (that's the maximum because + // `MainThreadToken: !Clone`) + match recv.recv() { + Ok(thread_params) => { + // Run a GUI event loop. Abort if the fake main thread panics + thread_params.run_while(|| !fake_main_thread.is_finished()); + } + Err(mpsc::RecvError) => {} + } + + // Join on the fake main thread + match fake_main_thread.join() { + Ok(x) => match x {}, + // The default panic handler should have already outputted the panic + // message, so we can just call `abort` here + Err(_) => std::process::abort(), + } +} + +/// Encapsulates the data passed to the thread handling minifb screen updates +/// and input events. +struct MinifbThread { + native_buffer: Arc>>, +} + +struct XousKeyboardHandler { + kbd: cramium_api::keyboard::Keyboard, + left_shift: bool, + right_shift: bool, +} + +pub struct Oled128x128 { + native_buffer: Arc>>, //[u32; WIDTH * HEIGHT], + // front and back buffers + buffers: [[u32; COLUMN as usize * ROW as usize]; 2], + active_buffer: BufferState, +} + +impl<'a> Oled128x128 { + pub fn new( + main_thread_token: MainThreadToken, + _perclk_freq: u32, + _iox: &'a T, + _udma_global: &'a dyn UdmaGlobalConfig, + ) -> Self { + let native_buffer = vec![DARK_COLOUR; COLUMN as usize * ROW as usize]; + let native_buffer = Arc::new(Mutex::new(native_buffer)); + + // Start a GUI event loop on the main thread + let thread_params = MinifbThread { native_buffer: Arc::clone(&native_buffer) }; + main_thread_token.0.send(thread_params).unwrap(); + + Self { + native_buffer, + buffers: [[DARK_COLOUR; COLUMN as usize * ROW as usize]; 2], + active_buffer: BufferState::A, + } + } + + fn set_data(&self) {} + + fn set_command(&self) {} + + pub fn buffer_swap(&mut self) { self.active_buffer = self.active_buffer.swap(); } + + pub fn buffer_mut(&mut self) -> &mut [u32] { &mut self.buffers[self.active_buffer.as_index()] } + + pub fn buffer(&self) -> &[u32] { &self.buffers[self.active_buffer.as_index()] } + + pub fn send_command<'b, U>(&'b mut self, _cmd: U) + where + U: IntoIterator + 'b, + { + } + + pub fn init(&mut self) {} +} + +impl FrameBuffer for Oled128x128 { + /// Transfers the back buffer + fn draw(&mut self) { + // this must be opposite of what `buffer` / `buffer_mut` returns + let buffer = self.buffers[self.active_buffer.swap().as_index()]; + let mut native_buffer = self.native_buffer.lock().unwrap(); + native_buffer.copy_from_slice(&buffer); + } + + fn clear(&mut self) { self.buffer_mut().fill(DARK_COLOUR); } + + fn put_pixel(&mut self, p: Point, on: ColorNative) { + if p.x > COLUMN || p.y > ROW || p.x < 0 || p.y < 0 { + return; + } + let buffer = self.buffer_mut(); + buffer[(p.x + p.y * ROW) as usize] = on.0 as u32; + } + + fn dimensions(&self) -> Point { Point::new(COLUMN, ROW) } + + fn get_pixel(&mut self, p: Point) -> Option { + if p.x > COLUMN || p.y > ROW || p.x < 0 || p.y < 0 { + return None; + } + let buffer = self.buffer(); + let color = ColorNative(buffer[(p.x + p.y * ROW) as usize] as usize); + Some(color) + } +} + +impl MinifbThread { + pub fn run_while(self, mut predicate: impl FnMut() -> bool) { + let mut window = Window::new( + "baosec", + COLUMN as usize, + ROW as usize, + WindowOptions { + scale_mode: minifb::ScaleMode::AspectRatioStretch, + resize: true, + ..WindowOptions::default() + }, + ) + .unwrap_or_else(|e| { + log::error!("{e:?}"); + std::process::abort(); + }); + + // Limit the maximum update rate + window.set_target_fps(MAX_FPS); + + let xns = xous_names::XousNames::new().unwrap(); + let kbd = cramium_api::keyboard::Keyboard::new(&xns).unwrap(); + let keyboard_handler = Box::new(XousKeyboardHandler { kbd, left_shift: false, right_shift: false }); + window.set_input_callback(keyboard_handler); + + let mut native_buffer = Vec::new(); + + while predicate() { + // Copy the contents of `native_buffer`. Release the lock + // immediately so as not to starve the server thread. + native_buffer.clear(); + native_buffer.extend_from_slice(&self.native_buffer.lock().unwrap()); + + // Render the contents of the minifb window and handle input events. + // This may block to regulate the update rate. + window.update_with_buffer(&native_buffer, COLUMN as usize, ROW as usize).unwrap(); + if !window.is_open() || window.is_key_down(Key::Escape) { + std::process::exit(0); + } + } + } +} + +impl XousKeyboardHandler { + fn decode_key(&mut self, k: Key) -> char { + let shift = self.left_shift || self.right_shift; + let base: char = if shift == false { + match k { + // key maps are commented out so we can use the add_char routine for all the characters + // natively handled by mini_fb this allows us to apply the native keyboard map + // to all the typed characters, while still passing through the special + // keys needed to emulate the special buttons on the device. + /* Key::A => 'a', + Key::B => 'b', + Key::C => 'c', + Key::D => 'd', + Key::E => 'e', + Key::F => 'f', + Key::G => 'g', + Key::H => 'h', + Key::I => 'i', + Key::J => 'j', + Key::K => 'k', + Key::L => 'l', + Key::M => 'm', + Key::N => 'n', + Key::O => 'o', + Key::P => 'p', + Key::Q => 'q', + Key::R => 'r', + Key::S => 's', + Key::T => 't', + Key::U => 'u', + Key::V => 'v', + Key::W => 'w', + Key::X => 'x', + Key::Y => 'y', + Key::Z => 'z', + Key::Key0 => '0', + Key::Key1 => '1', + Key::Key2 => '2', + Key::Key3 => '3', + Key::Key4 => '4', + Key::Key5 => '5', + Key::Key6 => '6', + Key::Key7 => '7', + Key::Key8 => '8', + Key::Key9 => '9',*/ + Key::Left => '←', + Key::Right => '→', + Key::Up => '↑', + Key::Down => '↓', + Key::Home => '∴', + Key::Backspace => '\u{0008}', + Key::Delete => '\u{0008}', + Key::Enter => 0xd_u8.into(), + //Key::Space => ' ', + //Key::Comma => ',', + //Key::Period => '.', + Key::F1 => 0x11_u8.into(), + Key::F2 => 0x12_u8.into(), + Key::F3 => 0x13_u8.into(), + Key::F4 => 0x14_u8.into(), + Key::F5 => '😊', + Key::F6 => '福', + _ => '\u{0000}', + } + } else { + match k { + /* Key::A => 'A', + Key::B => 'B', + Key::C => 'C', + Key::D => 'D', + Key::E => 'E', + Key::F => 'F', + Key::G => 'G', + Key::H => 'H', + Key::I => 'I', + Key::J => 'J', + Key::K => 'K', + Key::L => 'L', + Key::M => 'M', + Key::N => 'N', + Key::O => 'O', + Key::P => 'P', + Key::Q => 'Q', + Key::R => 'R', + Key::S => 'S', + Key::T => 'T', + Key::U => 'U', + Key::V => 'V', + Key::W => 'W', + Key::X => 'X', + Key::Y => 'Y', + Key::Z => 'Z', + Key::Key0 => ')', + Key::Key1 => '!', + Key::Key2 => '@', + Key::Key3 => '#', + Key::Key4 => '$', + Key::Key5 => '%', + Key::Key6 => '^', + Key::Key7 => '&', + Key::Key8 => '*', + Key::Key9 => '(', */ + Key::Left => '←', + Key::Right => '→', + Key::Up => '↑', + Key::Down => '↓', + Key::Home => '∴', + Key::Backspace => '\u{0008}', + Key::Delete => '\u{0008}', + //Key::Space => ' ', + //Key::Comma => '<', + //Key::Period => '>', + _ => '\u{0000}', + } + }; + base + } +} + +impl minifb::InputCallback for XousKeyboardHandler { + fn add_char(&mut self, uni_char: u32) { + let c = char::from_u32(uni_char).unwrap_or('\u{0000}'); + if c != '\u{0008}' && c != '\u{000d}' && c != '\u{007f}' { + self.kbd.inject_key(c); + } + } + + fn set_key_state(&mut self, key: minifb::Key, state: bool) { + if key == Key::LeftShift { + self.left_shift = state; + return; + } + if key == Key::RightShift { + self.right_shift = state; + return; + } + if !state { + return; + } + + log::debug!("GFX|hosted: sending key {:?}", key); + let c = self.decode_key(key); + if c != '\u{0000}' { + self.kbd.inject_key(c); + } + } +} diff --git a/services/cramium-emu/src/i2c.rs b/services/cramium-emu/src/i2c.rs new file mode 100644 index 000000000..2da575cf8 --- /dev/null +++ b/services/cramium-emu/src/i2c.rs @@ -0,0 +1,27 @@ +use cramium_api::*; + +pub struct I2c {} + +impl I2c { + pub fn new() -> Self { I2c {} } + + /// This is used to pass a list of I2C transactions that must be completed atomically + /// No further I2C requests may happen while this is processing. + pub fn i2c_transactions(&self, list: I2cTransactions) -> Result { + Ok(I2cTransactions { transactions: vec![] }) + } +} + +impl I2cApi for I2c { + fn i2c_read( + &mut self, + _dev: u8, + _adr: u8, + buf: &mut [u8], + _repeated_start: bool, + ) -> Result { + Ok(buf.len()) + } + + fn i2c_write(&mut self, _dev: u8, _adr: u8, data: &[u8]) -> Result { Ok(data.len()) } +} diff --git a/services/cramium-emu/src/keyboard.rs b/services/cramium-emu/src/keyboard.rs new file mode 100644 index 000000000..fd0852c54 --- /dev/null +++ b/services/cramium-emu/src/keyboard.rs @@ -0,0 +1,256 @@ +use cramium_api::keyboard::*; +use num_traits::*; +use xous::{CID, MessageSender, msg_blocking_scalar_unpack, msg_scalar_unpack}; +use xous_ipc::Buffer; + +pub fn start_keyboard_service() { + std::thread::spawn(move || { + keyboard_service(); + }); + std::thread::spawn(move || { + keyboard_bouncer(); + }); +} + +fn keyboard_bouncer() { + // private server that has no dependencies but a "well-known-name" for the log server + // to forward keystrokes into. + let sid = xous::create_server_with_address(b"keyboard_bouncer") + .expect("couldn't create keyboard log bounce server"); + let xns = xous_names::XousNames::new().unwrap(); + let kbd = cramium_api::keyboard::Keyboard::new(&xns).unwrap(); + let mut msg_opt = None; + loop { + xous::reply_and_receive_next(sid, &mut msg_opt).unwrap(); + let msg = msg_opt.as_mut().unwrap(); + // only one type of message is expected + match msg.body.scalar_message() { + Some(m) => { + if let Some(c) = char::from_u32(m.arg1 as u32) { + kbd.inject_key(c); + } + } + _ => {} + } + } +} + +fn keyboard_service() { + let xns = xous_names::XousNames::new().unwrap(); + let kbd_sid = xns.register_name(cramium_api::SERVER_NAME_KBD, None).expect("can't register server"); + + let mut listener_conn: Option = None; + let mut listener_op: Option = None; + let mut observer_conn: Option = None; + let mut observer_op: Option = None; + + let mut esc_index: Option = None; + let mut esc_chars = [0u8; 16]; + // storage for any blocking listeners + let mut blocking_listener = Vec::::new(); + + loop { + let msg = xous::receive_message(kbd_sid).unwrap(); // this blocks until we get a message + let op = FromPrimitive::from_usize(msg.body.id()); + log::debug!("{:?}", op); + match op { + Some(KeyboardOpcode::BlockingKeyListener) => { + blocking_listener.push(msg.sender); + } + Some(KeyboardOpcode::RegisterListener) => { + let buffer = unsafe { Buffer::from_memory_message(msg.body.memory_message().unwrap()) }; + let kr = buffer.as_flat::().unwrap(); + match xns.request_connection_blocking(kr.server_name.as_str()) { + Ok(cid) => { + listener_conn = Some(cid); + listener_op = Some(>::from(kr.listener_op_id.into()) as usize); + } + Err(e) => { + log::error!("couldn't connect to listener: {:?}", e); + listener_conn = None; + listener_op = None; + } + } + } + Some(KeyboardOpcode::RegisterKeyObserver) => { + let buffer = unsafe { Buffer::from_memory_message(msg.body.memory_message().unwrap()) }; + let kr = buffer.as_flat::().unwrap(); + if observer_conn.is_none() { + match xns.request_connection_blocking(kr.server_name.as_str()) { + Ok(cid) => { + observer_conn = Some(cid); + observer_op = Some(>::from(kr.listener_op_id.into()) as usize); + } + Err(e) => { + log::error!("couldn't connect to observer: {:?}", e); + observer_conn = None; + observer_op = None; + } + } + } + } + Some(KeyboardOpcode::SelectKeyMap) => { + todo!(); + } + Some(KeyboardOpcode::GetKeyMap) => msg_blocking_scalar_unpack!(msg, _, _, _, _, { + log::warn!("Defaulting to DVORAK map"); + xous::return_scalar(msg.sender, KeyMap::Dvorak.into()).expect("can't retrieve keymap"); + }), + Some(KeyboardOpcode::SetRepeat) => msg_scalar_unpack!(msg, _rate, _delay, _, _, { + todo!(); + }), + Some(KeyboardOpcode::SetChordInterval) => msg_scalar_unpack!(msg, _delay, _, _, _, { + todo!(); + }), + Some(KeyboardOpcode::InjectKey) => msg_scalar_unpack!(msg, k, _, _, _, { + // key substitutions to help things work better + // 1b5b317e = home + // 1b5b44 = left + // 1b5b43 = right + // 1b5b41 = up + // 1b5b42 = down + let key = match esc_index { + Some(i) => { + esc_chars[i] = (k & 0xff) as u8; + match esc_match(&esc_chars[..i + 1]) { + Ok(m) => { + if let Some(code) = m { + // Ok(Some(code)) is a character found + esc_chars = [0u8; 16]; + esc_index = None; + code + } else { + // Ok(None) means we're still accumulating characters + if i + 1 < esc_chars.len() { + esc_index = Some(i + 1); + } else { + esc_index = None; + esc_chars = [0u8; 16]; + } + '\u{0000}' + } + } + // invalid sequence encountered, abort + Err(_) => { + log::warn!("Unhandled escape sequence: {:x?}", &esc_chars[..i + 1]); + esc_chars = [0u8; 16]; + esc_index = None; + '\u{0000}' + } + } + } + _ => { + if k == 0x1b { + esc_index = Some(1); + esc_chars = [0u8; 16]; // clear the full search array with every escape sequence init + esc_chars[0] = 0x1b; + '\u{0000}' + } else { + let bs_del_fix = if k == 0x7f { 0x08 } else { k }; + core::char::from_u32(bs_del_fix as u32).unwrap_or('\u{0000}') + } + } + }; + + if let Some(conn) = listener_conn { + if key != '\u{0000}' { + if key >= '\u{f700}' && key <= '\u{f8ff}' { + log::info!("ignoring key '{}'({:x})", key, key as u32); // ignore Apple PUA characters + } else { + log::debug!("injecting key '{}'({:x})", key, key as u32); // always be noisy about this, it's an exploit path + xous::try_send_message( + conn, + xous::Message::new_scalar( + listener_op.unwrap(), + key as u32 as usize, + '\u{0000}' as u32 as usize, + '\u{0000}' as u32 as usize, + '\u{0000}' as u32 as usize, + ), + ) + .unwrap_or_else(|_| { + log::info!("Input overflow, dropping keys!"); + xous::Result::Ok + }); + } + } + } + + if observer_conn.is_some() && observer_op.is_some() { + log::trace!("sending observer key"); + xous::try_send_message( + observer_conn.unwrap(), + xous::Message::new_scalar(observer_op.unwrap(), 0, 0, 0, 0), + ) + .ok(); + } + + for listener in blocking_listener.drain(..) { + // we must unblock anyways once the key is hit; even if the key is invalid, + // send the invalid key. The receiving library function will clean this up into a + // nil-response vector. + xous::return_scalar2(listener, key as u32 as usize, 0).unwrap(); + } + }), + Some(KeyboardOpcode::HandlerTrigger) => { + todo!("Write this once we have an IRQ handler for keyboard interrupts"); + } + None => { + log::error!("couldn't convert KeyboardOpcode"); + break; + } + } + } + xns.unregister_server(kbd_sid).unwrap(); + xous::destroy_server(kbd_sid).unwrap(); + xous::terminate_process(0) +} + +fn esc_match(esc_chars: &[u8]) -> Result, ()> { + let mut extended = Vec::::new(); + for (i, &c) in esc_chars.iter().enumerate() { + match i { + 0 => { + if c != 0x1b { + return Err(()); + } + } + 1 => { + match c { + 0x41 => return Ok(Some('↑')), + 0x42 => return Ok(Some('↓')), + 0x43 => return Ok(Some('→')), + 0x44 => return Ok(Some('←')), + 0x7e => return Err(()), // premature end + _ => { + if c != 0x5b { + return Err(()); + } + } + } + } + 2 => match c { + 0x41 => return Ok(Some('↑')), + 0x42 => return Ok(Some('↓')), + 0x43 => return Ok(Some('→')), + 0x44 => return Ok(Some('←')), + 0x7e => return Err(()), // premature end + _ => extended.push(c), + }, + _ => { + if c == 0x7e { + if extended.len() == 1 { + if extended[0] == 0x31 { + return Ok(Some('∴')); + } + } else { + return Err(()); // code unrecognized + } + } else { + extended.push(c) + } + } + } + } + Ok(None) +} diff --git a/services/cramium-emu/src/lib.rs b/services/cramium-emu/src/lib.rs new file mode 100644 index 000000000..7c97f57da --- /dev/null +++ b/services/cramium-emu/src/lib.rs @@ -0,0 +1,6 @@ +pub mod camera; +pub mod display; +pub mod i2c; +pub mod keyboard; +pub mod trng; +pub mod udma; diff --git a/services/cramium-emu/src/trng.rs b/services/cramium-emu/src/trng.rs new file mode 100644 index 000000000..cb5e7fe98 --- /dev/null +++ b/services/cramium-emu/src/trng.rs @@ -0,0 +1,54 @@ +use rand::{CryptoRng, Rng, RngCore}; + +#[derive(Debug)] +pub struct Trng {} +impl Trng { + pub fn new(_xns: &xous_names::XousNames) -> Result { Ok(Trng {}) } + + fn reseed(&self) {} + + pub fn get_u32(&self) -> Result { Ok(rand::thread_rng().gen()) } + + pub fn get_u64(&self) -> Result { Ok(rand::thread_rng().gen()) } + + pub fn fill_buf(&self, data: &mut [u32]) -> Result<(), xous::Error> { + for d in data.iter_mut() { + *d = rand::thread_rng().gen(); + } + Ok(()) + } + + /// This is copied out of the 0.5 API for rand_core + pub fn fill_bytes_via_next(&mut self, dest: &mut [u8]) { + use core::mem::transmute; + let mut left = dest; + while left.len() >= 8 { + let (l, r) = { left }.split_at_mut(8); + left = r; + let chunk: [u8; 8] = unsafe { transmute(rand::thread_rng().next_u64().to_le()) }; + l.copy_from_slice(&chunk); + } + let n = left.len(); + if n > 4 { + let chunk: [u8; 8] = unsafe { transmute(rand::thread_rng().next_u64().to_le()) }; + left.copy_from_slice(&chunk[..n]); + } else if n > 0 { + let chunk: [u8; 4] = unsafe { transmute(rand::thread_rng().next_u32().to_le()) }; + left.copy_from_slice(&chunk[..n]); + } + } +} + +impl RngCore for Trng { + fn next_u32(&mut self) -> u32 { rand::thread_rng().gen() } + + fn next_u64(&mut self) -> u64 { rand::thread_rng().gen() } + + fn fill_bytes(&mut self, dest: &mut [u8]) { self.fill_bytes_via_next(dest); } + + fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand_core::Error> { + Ok(self.fill_bytes(dest)) + } +} + +impl CryptoRng for Trng {} diff --git a/services/cramium-emu/src/udma.rs b/services/cramium-emu/src/udma.rs new file mode 100644 index 000000000..5bda395b2 --- /dev/null +++ b/services/cramium-emu/src/udma.rs @@ -0,0 +1,36 @@ +use cramium_api::*; + +pub struct UdmaGlobal {} + +impl UdmaGlobal { + pub fn new() -> Self { Self {} } + + pub fn udma_clock_config(&self, _peripheral: PeriphId, _enable: bool) {} + + /// Safety: this event does no checking if an event has been previously mapped. It is up + /// to the caller to ensure that no events are being stomped on. + pub unsafe fn udma_event_map( + &self, + _peripheral: PeriphId, + _event_type: PeriphEventType, + _to_channel: EventChannel, + ) { + } + + pub fn reset(&self, _peripheral: PeriphId) {} +} + +impl UdmaGlobalConfig for UdmaGlobal { + fn clock(&self, peripheral: PeriphId, enable: bool) { self.udma_clock_config(peripheral, enable); } + + unsafe fn udma_event_map( + &self, + peripheral: PeriphId, + event_type: PeriphEventType, + to_channel: EventChannel, + ) { + self.udma_event_map(peripheral, event_type, to_channel); + } + + fn reset(&self, peripheral: PeriphId) { self.reset(peripheral); } +} diff --git a/services/graphics-server/src/wordwrap.rs b/services/graphics-server/src/wordwrap.rs index 88bb8f404..15f532824 100644 --- a/services/graphics-server/src/wordwrap.rs +++ b/services/graphics-server/src/wordwrap.rs @@ -44,7 +44,7 @@ pub struct TypesetWord { /// overall height for the word pub height: i16, /// set if this `word` is not drawable, e.g. a newline placeholder. - /// *however* the Vec should still be checked for an insertion point, so that + /// *however* the `Vec` should still be checked for an insertion point, so that /// successive newlines properly get their insertion point drawn pub non_drawable: bool, /// the position in the originating abstract string of the first character in the word @@ -294,7 +294,7 @@ impl Typesetter { /// the caller must reset the cursor to the desired resume position, otherwise it will pick /// up again at the overflow position. /// - /// The final Vec:: is snapped to the top left of the Renderable region. This + /// The final `Vec::` is snapped to the top left of the Renderable region. This /// needs to be transformed into the final screen coordinate space before blitting. pub fn typeset(&mut self, strat: OverflowStrategy) -> ComposedType { // a composition only lasts as long as the lifetime of this call, and is passed back to the caller diff --git a/services/modals/Cargo.toml b/services/modals/Cargo.toml index 92f4a6e94..cce7acf99 100644 --- a/services/modals/Cargo.toml +++ b/services/modals/Cargo.toml @@ -28,6 +28,8 @@ bit_field = "0.9.0" cramium-hal = { path = "../../libs/cramium-hal", optional = true, features = [ "std", ] } +cramium-emu = { path = "../cramium-emu", optional = true } +blitstr2 = { path = "../../libs/blitstr2" } ux-api = { path = "../../libs/ux-api", optional = true, features = ["std"] } utralib = { version = "0.1.25", optional = true, default-features = false } @@ -35,12 +37,15 @@ utralib = { version = "0.1.25", optional = true, default-features = false } [features] precursor = ["utralib/precursor", "trng", "gam", "tts-frontend"] hosted = ["utralib/hosted", "trng", "gam", "tts-frontend"] -hosted-baosec = ["ux-api/hosted-baosec"] renode = ["utralib/renode", "trng", "gam", "tts-frontend"] + cramium-soc = ["utralib/cramium-soc", "cram-hal-service"] +hosted-baosec = ["ux-api/hosted-baosec", "cramium-emu"] board-baosec = ["ux-api/board-baosec"] +doc-deps = ["gam"] tts = [] ditherpunk = [] hazardous-debug = [] -default = ["trng"] +# "gam" is required for cargo doc to run successfully +default = [] diff --git a/services/modals/src/lib.rs b/services/modals/src/lib.rs index a004c233b..7750d9487 100644 --- a/services/modals/src/lib.rs +++ b/services/modals/src/lib.rs @@ -12,9 +12,15 @@ use std::cmp::max; use std::convert::TryInto; use bit_field::BitField; +#[cfg(feature = "cramium-soc")] +use cram_hal_service::trng::Trng; +#[cfg(feature = "hosted-baosec")] +use cramium_emu::trng::Trng; #[cfg(not(any(feature = "hosted-baosec", feature = "cramium-soc")))] use gam::*; use num_traits::*; +#[cfg(all(not(feature = "cramium-soc"), not(feature = "doc-deps"), not(feature = "hosted-baosec")))] +use trng::Trng; #[cfg(any(feature = "hosted-baosec", feature = "cramium-soc"))] use ux_api::widgets::*; use xous::{CID, Message, send_message}; @@ -183,11 +189,10 @@ impl Modals { REFCOUNT.fetch_add(1, Ordering::Relaxed); let conn = xns.request_connection_blocking(api::SERVER_NAME_MODALS).expect("Can't connect to Modals server"); - #[cfg(feature = "cramium-soc")] - let trng = cram_hal_service::trng::Trng::new(&xns).unwrap(); - #[cfg(not(feature = "cramium-soc"))] - let trng = trng::Trng::new(&xns).unwrap(); + let trng = Trng::new(&xns).unwrap(); + #[allow(unused_mut)] let mut token = [0u32; 4]; + #[cfg(not(feature = "doc-deps"))] trng.fill_buf(&mut token).unwrap(); Ok(Modals { conn, token, have_lock: Cell::new(false) }) } diff --git a/services/modals/src/main.rs b/services/modals/src/main.rs index 98ff69941..4d0b8c0fe 100644 --- a/services/modals/src/main.rs +++ b/services/modals/src/main.rs @@ -29,11 +29,18 @@ /// a `TextResponseValid` message which pumps the work queue. mod api; use api::*; +use blitstr2::GlyphStyle; +#[cfg(feature = "cramium-soc")] +use cram_hal_service::trng::Trng; +#[cfg(feature = "hosted-baosec")] +use cramium_emu::trng::Trng; #[cfg(feature = "ditherpunk")] use gam::Bitmap; #[cfg(not(any(feature = "hosted-baosec", feature = "cramium-soc")))] use gam::modal::*; use locales::t; +#[cfg(all(not(feature = "cramium-soc"), not(feature = "doc-deps"), not(feature = "hosted-baosec")))] +use trng::Trng; #[cfg(feature = "tts")] use tts_frontend::TtsFrontend; #[cfg(any(feature = "hosted-baosec", feature = "cramium-soc"))] @@ -42,6 +49,7 @@ use ux_api::minigfx::*; use ux_api::widgets::*; use xous::{Message, msg_blocking_scalar_unpack, msg_scalar_unpack, send_message}; use xous_ipc::Buffer; + #[cfg(feature = "tts")] const TICK_INTERVAL: u64 = 2500; @@ -126,6 +134,7 @@ fn wrapped_main() -> ! { let mut last_percentage = 0; let mut start_work: u32 = 0; let mut end_work: u32 = 100; + #[cfg(not(any(feature = "hosted-baosec", feature = "cramium-soc")))] let mut renderer_modal = Modal::new( gam::SHARED_MODAL_NAME, ActionType::TextEntry(text_action.clone()), @@ -134,6 +143,9 @@ fn wrapped_main() -> ! { DEFAULT_STYLE, 8, ); + #[cfg(any(feature = "hosted-baosec", feature = "cramium-soc"))] + let mut renderer_modal = + Modal::new(ActionType::TextEntry(text_action.clone()), Some("Placeholder"), None, DEFAULT_STYLE, 8); #[cfg(not(any(feature = "hosted-baosec", feature = "cramium-soc")))] renderer_modal.spawn_helper( modals_sid, @@ -147,10 +159,7 @@ fn wrapped_main() -> ! { let mut list_selected = 0u32; let mut token_lock: Option<[u32; 4]> = None; - #[cfg(feature = "cramium-soc")] - let trng = cram_hal_service::trng::Trng::new(&xns).unwrap(); - #[cfg(not(feature = "cramium-soc"))] - let trng = trng::Trng::new(&xns).unwrap(); + let trng = Trng::new(&xns).unwrap(); // this is a random number that serves as a "default" that cannot be guessed let default_nonce = [trng.get_u32().unwrap(), trng.get_u32().unwrap(), trng.get_u32().unwrap(), trng.get_u32().unwrap()]; @@ -799,7 +808,7 @@ fn wrapped_main() -> ! { renderer_modal.set_growable(false); // reset the growable state, it's assumed to be default false log::trace!("validating text entry modal"); let buf = unsafe { Buffer::from_memory_message(msg.body.memory_message().unwrap()) }; - let text = buf.to_original::().unwrap(); + let text = buf.to_original::().unwrap(); if let Some(mut origin) = dr.take() { let mut response = unsafe { Buffer::from_memory_message_mut(origin.body.memory_message_mut().unwrap()) @@ -856,7 +865,7 @@ fn wrapped_main() -> ! { Some(Opcode::Bip39Return) => match op { RendererState::RunBip39Input(_config) => { let buf = unsafe { Buffer::from_memory_message(msg.body.memory_message().unwrap()) }; - let b39 = buf.to_original::().unwrap(); + let b39 = buf.to_original::().unwrap(); if let Some(mut origin) = dr.take() { let mut response = unsafe { Buffer::from_memory_message_mut(origin.body.memory_message_mut().unwrap()) diff --git a/services/xous-log/Cargo.toml b/services/xous-log/Cargo.toml index 13a1fe8e5..99ee39adb 100644 --- a/services/xous-log/Cargo.toml +++ b/services/xous-log/Cargo.toml @@ -44,6 +44,7 @@ cramium-fpga = ["utralib/cramium-fpga"] board-baosec = ["cramium-hal/board-baosec"] board-baosor = ["cramium-hal/board-baosor"] board-dabao = ["cramium-hal/board-dabao"] +hosted-baosec = [] precursor = ["utralib/precursor"] hosted = ["utralib/hosted"] diff --git a/services/xous-names/Cargo.toml b/services/xous-names/Cargo.toml index 04746a739..bb2b24a46 100644 --- a/services/xous-names/Cargo.toml +++ b/services/xous-names/Cargo.toml @@ -29,8 +29,11 @@ utralib = { version = "0.1.25", optional = true, default-features = false } [features] cramium-soc = ["utralib/cramium-soc"] cramium-fpga = ["utralib/cramium-fpga"] +hosted-baosec = [] + precursor = ["utralib/precursor"] hosted = ["utralib/hosted"] renode = ["utralib/renode"] + debugprint = [] -default = [] # "debugprint" +default = [] # "debugprint" diff --git a/services/xous-swapper/Cargo.toml b/services/xous-swapper/Cargo.toml index 0367662df..0a0093747 100644 --- a/services/xous-swapper/Cargo.toml +++ b/services/xous-swapper/Cargo.toml @@ -22,13 +22,19 @@ aes-gcm-siv = { version = "0.11.1", default-features = false, features = [ "aes", ] } cramium-hal = { path = "../../libs/cramium-hal", optional = true, default-features = false } +cramium-api = { path = "../../libs/cramium-api", optional = true, default-features = false } utralib = { version = "0.1.25", optional = true, default-features = false } [target.'cfg(any(windows,unix))'.dependencies] [features] -cramium-soc = ["utralib/cramium-soc", "cramium-hal", "loader/cramium-soc"] +cramium-soc = [ + "utralib/cramium-soc", + "cramium-hal/std", + "loader/cramium-soc", + "cramium-api/std", +] cramium-fpga = ["utralib/cramium-fpga"] board-baosec = ["cramium-hal/board-baosec"] board-baosor = ["cramium-hal/board-baosor"] diff --git a/services/xous-swapper/src/platform/cramium/hw.rs b/services/xous-swapper/src/platform/cramium/hw.rs index b771c1555..c166d4f3e 100644 --- a/services/xous-swapper/src/platform/cramium/hw.rs +++ b/services/xous-swapper/src/platform/cramium/hw.rs @@ -2,6 +2,7 @@ use core::fmt::Write; use core::mem::size_of; use aes_gcm_siv::{AeadInPlace, Aes256GcmSiv, Error, KeyInit, Nonce, Tag}; +use cramium_api::*; use cramium_hal::board::SPIM_RAM_IFRAM_ADDR; use cramium_hal::ifram::IframRange; use cramium_hal::udma::*; diff --git a/services/xous-ticktimer/Cargo.toml b/services/xous-ticktimer/Cargo.toml index ec015e4d4..99cddcb40 100644 --- a/services/xous-ticktimer/Cargo.toml +++ b/services/xous-ticktimer/Cargo.toml @@ -41,6 +41,7 @@ renode = ["utralib/renode", "susres/renode", "watchdog"] cramium-soc = ["utralib/cramium-soc", "utralib/std"] cramium-fpga = ["utralib/cramium-fpga"] +hosted-baosec = [] susres = [] debug-print = [] diff --git a/utralib/Cargo.toml b/utralib/Cargo.toml index 91c36ad79..749b72a3d 100644 --- a/utralib/Cargo.toml +++ b/utralib/Cargo.toml @@ -50,6 +50,8 @@ atsama5d27 = [] # Cramium SoC & FPGA model cramium-soc = [] cramium-fpga = [] +# Emulation for cramium +hosted-baosec = [] # Specify a Precusor default so that we can pass packaging CI tests. default = ["hosted"] diff --git a/utralib/build.rs b/utralib/build.rs index 46b420778..87d7cc10d 100644 --- a/utralib/build.rs +++ b/utralib/build.rs @@ -1,6 +1,6 @@ use std::env; use std::fs::OpenOptions; -#[cfg(not(feature = "hosted"))] +#[cfg(not(any(feature = "hosted", feature = "hosted-baosec")))] use std::io::Read; use std::io::Write; use std::path::PathBuf; @@ -137,7 +137,7 @@ fn main() { // // Debug this using: // $env:CARGO_LOG="cargo::core::compiler::fingerprint=info" - #[cfg(not(feature = "hosted"))] + #[cfg(not(any(feature = "hosted", feature = "hosted-baosec")))] { let mut svd_files = Vec::new(); for svd in svd_filenames.iter() { @@ -180,7 +180,7 @@ fn main() { writeln!(svd_file, "utralib/{}", svd).unwrap(); } } - #[cfg(feature = "hosted")] + #[cfg(any(feature = "hosted", feature = "hosted-baosec"))] { let svd_path = out_dir().join("../../SVD_PATH"); let mut svd_file = OpenOptions::new().create(true).write(true).truncate(true).open(svd_path).unwrap(); diff --git a/xtask/src/builder.rs b/xtask/src/builder.rs index 3676f9cfe..5c36c176a 100644 --- a/xtask/src/builder.rs +++ b/xtask/src/builder.rs @@ -262,6 +262,17 @@ impl Builder { self } + /// Configure for hosted mode + pub fn target_hosted_baosec(&mut self) -> &mut Builder { + self.loader = CrateSpec::None; + self.target = None; + self.target_kernel = None; + self.stream = BuildStream::Release; + self.utra_target = "hosted-baosec".to_string(); + self.run_svd2repl = false; + self + } + /// Configure for renode targets pub fn target_renode(&mut self) -> &mut Builder { self.target = Some(crate::TARGET_TRIPLE_RISCV32.to_string()); @@ -668,7 +679,7 @@ impl Builder { self.features.push("renode".into()); self.loader_features.push("renode".into()); self.kernel_features.push("renode".into()); - } else if self.utra_target.contains("hosted") { + } else if self.utra_target == "hosted" { self.features.push("hosted".into()); // there is no loader in hosed mode self.kernel_features.push("hosted".into()); @@ -696,6 +707,9 @@ impl Builder { self.kernel_features.push(format!("utralib/{}", &self.utra_target)); self.loader_features.push("cramium-soc".into()); self.loader_features.push(format!("utralib/{}", &self.utra_target)); + } else if self.utra_target.contains("hosted-baosec") { + self.features.push("hosted-baosec".into()); + self.kernel_features.push("hosted".into()); } else { return Err("Target unknown: please check your UTRA target".into()); } diff --git a/xtask/src/main.rs b/xtask/src/main.rs index ffca337d4..bbcf15419 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -367,8 +367,8 @@ fn main() -> Result<(), Box> { Some("baosec-emu") => { let bao_pkgs = ["xous-ticktimer", "xous-log", "xous-names", "modals", "bao-video"]; builder - .target_hosted() - .add_feature("hosted-baosec") + // hosted-baosec feature added below + .target_hosted_baosec() .add_services(&bao_pkgs) .add_apps(&get_cratespecs()); }