From 0e979a9e099b9b4ca69cd3defee168e15f2ec192 Mon Sep 17 00:00:00 2001 From: Mark Poliakov Date: Wed, 4 Feb 2026 14:46:57 +0200 Subject: [PATCH] spi: initial spi device support --- Cargo.lock | 2 + etc/dtb/aarch64/bcm2711-rpi-4-b.dts | 42 ++++ kernel/driver/bsp/bcm283x/Cargo.toml | 1 + kernel/driver/bsp/bcm283x/src/cprman.rs | 21 +- kernel/driver/bsp/bcm283x/src/lib.rs | 1 + kernel/driver/bsp/bcm283x/src/mbox.rs | 55 ++++- kernel/driver/bsp/bcm283x/src/spi.rs | 202 ++++++++++++++++++ kernel/lib/device-api/Cargo.toml | 1 + kernel/lib/device-api/src/clock/freq.rs | 10 + kernel/lib/device-api/src/device.rs | 6 + kernel/lib/device-api/src/lib.rs | 1 + kernel/lib/device-api/src/spi.rs | 15 ++ kernel/lib/device-tree/src/driver/tree.rs | 5 + .../libk/src/device/{char.rs => char/i2c.rs} | 82 +------ kernel/libk/src/device/char/mod.rs | 79 +++++++ kernel/libk/src/device/char/spi.rs | 94 ++++++++ kernel/libk/src/device/manager.rs | 29 ++- lib/abi/src/io/device.rs | 41 +++- userspace/lib/cross/src/lib.rs | 1 + userspace/lib/cross/src/spi.rs | 21 ++ userspace/lib/cross/src/sys/mod.rs | 26 +++ userspace/lib/cross/src/sys/yggdrasil/mod.rs | 1 + userspace/lib/cross/src/sys/yggdrasil/spi.rs | 76 +++++++ userspace/sysutils/src/tst.rs | 79 ++++++- 24 files changed, 784 insertions(+), 107 deletions(-) create mode 100644 kernel/driver/bsp/bcm283x/src/spi.rs create mode 100644 kernel/lib/device-api/src/spi.rs rename kernel/libk/src/device/{char.rs => char/i2c.rs} (57%) create mode 100644 kernel/libk/src/device/char/mod.rs create mode 100644 kernel/libk/src/device/char/spi.rs create mode 100644 userspace/lib/cross/src/spi.rs create mode 100644 userspace/lib/cross/src/sys/yggdrasil/spi.rs diff --git a/Cargo.lock b/Cargo.lock index cb6b9f4d..f0fd3318 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -469,6 +469,7 @@ dependencies = [ name = "device-api" version = "0.1.0" dependencies = [ + "async-trait", "device-api-macros", "yggdrasil-abi", ] @@ -2676,6 +2677,7 @@ dependencies = [ name = "ygg_driver_bsp_bcm283x" version = "0.1.0" dependencies = [ + "async-trait", "bytemuck", "device-api", "device-tree", diff --git a/etc/dtb/aarch64/bcm2711-rpi-4-b.dts b/etc/dtb/aarch64/bcm2711-rpi-4-b.dts index fedfbf43..0f35a89a 100644 --- a/etc/dtb/aarch64/bcm2711-rpi-4-b.dts +++ b/etc/dtb/aarch64/bcm2711-rpi-4-b.dts @@ -190,6 +190,16 @@ brcm,function = <2>; brcm,pull = <2>; }; + + // SPI + spi0_gpio: spi0_pins { + brcm,pins = <9>, <10>, <11>; + brcm,function = <4>; + }; + spi0_cs_gpio: spi0_cs_pins { + brcm,pins = <8>, <7>; + brcm,function = <1>; + }; }; uart0: serial@7e201000 { @@ -322,6 +332,38 @@ pinctrl-names = "default"; }; + spi0: spi@7e204000 { + compatible = "brcm,bcm2835-spi"; + reg = <0x7e204000 0x200>; + interrupts = ; + clocks = <&cprman 0x14>; + #address-cells = <0x01>; + #size-cells = <0x00>; + status = "okay"; + dmas = <&dma 0x06>, + <&dma 0x07>; + dma-names = "tx", "rx"; + pinctrl-names = "default"; + pinctrl-0 = <&spi0_gpio>, <&spi0_cs_gpio>; + cs-gpios = <&gpio 8 1>, <&gpio 7 1>; + + spi0_0: spidev@0 { + compatible = "spidev"; + reg = <0x00>; + #address-cells = <0x01>; + #size-cells = <0x00>; + spi-max-frequency = <125000000>; + }; + + spi0_1: spidev@1 { + compatible = "spidev"; + reg = <0x01>; + #address-cells = <0x01>; + #size-cells = <0x00>; + spi-max-frequency = <125000000>; + }; + }; + l1_intc: local_intc@40000000 { compatible = "brcm,bcm2836-l1-intc"; reg = <0x40000000 0x100>; diff --git a/kernel/driver/bsp/bcm283x/Cargo.toml b/kernel/driver/bsp/bcm283x/Cargo.toml index a308f35b..15d49149 100644 --- a/kernel/driver/bsp/bcm283x/Cargo.toml +++ b/kernel/driver/bsp/bcm283x/Cargo.toml @@ -16,3 +16,4 @@ tock-registers.workspace = true log.workspace = true bytemuck.workspace = true futures-util.workspace = true +async-trait.workspace = true diff --git a/kernel/driver/bsp/bcm283x/src/cprman.rs b/kernel/driver/bsp/bcm283x/src/cprman.rs index dbaf418d..e3ae8962 100644 --- a/kernel/driver/bsp/bcm283x/src/cprman.rs +++ b/kernel/driver/bsp/bcm283x/src/cprman.rs @@ -9,8 +9,13 @@ use device_tree::{ }; use libk::error::Error; +use crate::mbox::{Bcm2835Mbox, Bcm2835VcClock}; + +const CLK_VPU: u32 = 0x14; + struct Cprman { clk_osc: ClockHandle, + mbox: Arc, } impl Device for Cprman { @@ -26,8 +31,15 @@ impl Device for Cprman { impl ClockController for Cprman { fn clock_rate(&self, clock: Option) -> Result { - let _ = clock; - todo!() + let vc_clock = match clock { + Some(CLK_VPU) => Bcm2835VcClock::Core, + Some(n) => todo!("clock_rate({n:#02x})"), + None => return Err(Error::InvalidArgument), + }; + + let rate = self.mbox.vc_clock_rate(vc_clock)?; + + Ok(rate) } fn set_clock_rate(&self, clock: Option, rate: Hertz) -> Result { @@ -65,10 +77,13 @@ device_tree_driver! { driver: { fn probe(&self, node: &Arc, context: &mut ProbeContext) -> Option> { let _base = node.map_base(context, 0)?; + let mbox = Node::by_compatible("brcm,bcm2835-mbox", true).unwrap().device().unwrap(); + let mbox = Arc::downcast::(mbox.as_any()).unwrap(); let clk_osc = node.clock(0)?; let cprman = Arc::new(Cprman { - clk_osc + clk_osc, + mbox, }); node.make_clock_controller(cprman.clone()); diff --git a/kernel/driver/bsp/bcm283x/src/lib.rs b/kernel/driver/bsp/bcm283x/src/lib.rs index 1712c336..a87a75d9 100644 --- a/kernel/driver/bsp/bcm283x/src/lib.rs +++ b/kernel/driver/bsp/bcm283x/src/lib.rs @@ -8,3 +8,4 @@ mod cprman; mod gpio; mod i2c; mod mbox; +mod spi; diff --git a/kernel/driver/bsp/bcm283x/src/mbox.rs b/kernel/driver/bsp/bcm283x/src/mbox.rs index 9ef66f37..2d907674 100644 --- a/kernel/driver/bsp/bcm283x/src/mbox.rs +++ b/kernel/driver/bsp/bcm283x/src/mbox.rs @@ -1,7 +1,10 @@ -use core::{cell::UnsafeCell, mem::MaybeUninit}; +use core::{any::Any, cell::UnsafeCell, mem::MaybeUninit}; use alloc::sync::Arc; -use device_api::device::{Device, DeviceInitContext}; +use device_api::{ + clock::Hertz, + device::{Device, DeviceInitContext}, +}; use device_tree::driver::{Node, ProbeContext, device_tree_driver}; use kernel_arch_aarch64::mem::table::L3; use libk::device::{ @@ -61,7 +64,13 @@ register_structs! { } } -struct Bcm2835Mbox { +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +#[repr(u32)] +pub(crate) enum Bcm2835VcClock { + Core = 4, +} + +pub(crate) struct Bcm2835Mbox { regs: IrqSafeSpinlock>, } @@ -85,13 +94,20 @@ impl Device for Bcm2835Mbox { fn display_name(&self) -> &str { "bcm2835-mbox" } + + fn as_any(self: Arc) -> Arc { + self + } } impl Bcm2835Mbox { const RAM_TO_GPU_OFFSET: u32 = 0x40000000; + const CHANNEL_FRAMEBUFFER: u32 = 1; + const CHANNEL_ARM2VC: u32 = 8; + pub unsafe fn call_raw(&self, buffer: PhysicalAddress, channel: u32) -> Result<(), Error> { - log::info!("bcm2835-mbox: mbox call buffer={buffer:#x}, channel={channel}"); + log::debug!("bcm2835-mbox: mbox call buffer={buffer:#x}, channel={channel}"); let address = buffer .try_into_u32() @@ -125,10 +141,31 @@ impl Bcm2835Mbox { } } - pub fn configure_framebuffer( - &self, - mode_info: &DisplayMode, - ) -> Result { + pub fn vc_clock_rate(&self, clock: Bcm2835VcClock) -> Result { + let mut buffer = PageBox::new_slice(0u32, 4096 / 4)?; + buffer[0] = 9 * 4; // buffer size + buffer[1] = 0; // process request + + // Tag 1 (Response) + buffer[2] = 0x00030002; // tag + buffer[3] = 0; // value buffer size + buffer[4] = 0; // request + buffer[5] = clock as u32; // value buffer[0] + buffer[6] = 0; // value buffer[1] + buffer[7] = 0; + + // End tag + buffer[8] = 0; + + unsafe { self.call_raw(buffer.as_physical_address(), Self::CHANNEL_ARM2VC) }?; + if buffer[4] == 0 || buffer[1] == 0 { + todo!(); + } + let rate = Hertz::from(buffer[6]); + Ok(rate) + } + + fn configure_framebuffer(&self, mode_info: &DisplayMode) -> Result { #[derive(Debug, Clone, Copy)] #[repr(C)] struct FramebufferRequest { @@ -158,7 +195,7 @@ impl Bcm2835Mbox { }))?; // TODO flush cache for buffer - unsafe { self.call_raw(buffer.as_physical_address(), 0x01) }?; + unsafe { self.call_raw(buffer.as_physical_address(), Self::CHANNEL_FRAMEBUFFER) }?; let buffer = buffer.get_mut(); diff --git a/kernel/driver/bsp/bcm283x/src/spi.rs b/kernel/driver/bsp/bcm283x/src/spi.rs new file mode 100644 index 00000000..644a1e91 --- /dev/null +++ b/kernel/driver/bsp/bcm283x/src/spi.rs @@ -0,0 +1,202 @@ +use core::iter; + +use alloc::{boxed::Box, sync::Arc}; +use async_trait::async_trait; +use device_api::{ + clock::{ClockHandle, Hertz}, + device::{Device, DeviceInitContext}, + interrupt::IrqHandle, + spi::SpiController, +}; +use device_tree::driver::{Node, ProbeContext, device_tree_driver}; +use libk::{device::manager::DEVICE_REGISTRY, error::Error}; +use libk_mm::device::DeviceMemoryIo; +use libk_util::sync::IrqSafeSpinlock; +use tock_registers::{ + interfaces::{Readable, Writeable}, + register_bitfields, register_structs, + registers::ReadWrite, +}; +use yggdrasil_abi::io::device::spi::{SpiClockPhase, SpiClockPolarity, SpiConfig}; + +register_bitfields! { + u32, + CS [ + LEN_LONG OFFSET(25) NUMBITS(1) [], + DMA_LEN OFFSET(24) NUMBITS(1) [], + CSPOL2 OFFSET(23) NUMBITS(1) [], + CSPOL1 OFFSET(22) NUMBITS(1) [], + CSPOL0 OFFSET(21) NUMBITS(1) [], + RXF OFFSET(20) NUMBITS(1) [], + RXR OFFSET(19) NUMBITS(1) [], + TXD OFFSET(18) NUMBITS(1) [], + RXD OFFSET(17) NUMBITS(1) [], + DONE OFFSET(16) NUMBITS(1) [], + LEN OFFSET(13) NUMBITS(1) [], + REN OFFSET(12) NUMBITS(1) [], + ADCS OFFSET(11) NUMBITS(1) [], + INTR OFFSET(10) NUMBITS(1) [], + INTD OFFSET(9) NUMBITS(1) [], + DMAEN OFFSET(8) NUMBITS(1) [], + TA OFFSET(7) NUMBITS(1) [], + CSPOL OFFSET(6) NUMBITS(1) [], + CLEAR OFFSET(4) NUMBITS(2) [], + CPOL OFFSET(3) NUMBITS(1) [ + IdleLow = 0, + IdleHigh = 1 + ], + CPHA OFFSET(2) NUMBITS(1) [ + CaptureOnFirstTransition = 0, + CaptureOnSecondTransition = 1 + ], + CS OFFSET(0) NUMBITS(2) [], + ] +} + +register_structs! { + #[allow(non_snake_case)] + Regs { + (0x00 => CS: ReadWrite), + (0x04 => FIFO: ReadWrite), + (0x08 => CLK: ReadWrite), + (0x0C => DLEN: ReadWrite), + (0x10 => LTOH: ReadWrite), + (0x14 => DC: ReadWrite), + (0x18 => @END), + } +} + +pub struct Spi { + name: &'static str, + clock: ClockHandle, + irq: IrqHandle, + regs: IrqSafeSpinlock>, +} + +impl Device for Spi { + unsafe fn init(self: Arc, _cx: DeviceInitContext) -> Result<(), Error> { + let _ = &self.irq; + let regs = self.regs.lock(); + regs.CS.set(0); + + DEVICE_REGISTRY.spi.register_bus(self.clone()).ok(); + Ok(()) + } + + fn display_name(&self) -> &str { + self.name + } +} + +impl Regs { + fn start_transfer(&self, core_clock: Hertz, config: &SpiConfig) -> Result<(), Error> { + let want_clock = Hertz::from(config.frequency.unwrap_or(100000)); + let divider = Hertz::divider(core_clock, want_clock).ok_or(Error::InvalidArgument)?; + let divider: u16 = divider.try_into().map_err(|_| Error::InvalidArgument)?; + let divider = divider.wrapping_add(1) & !1; + + let mut cs = CS::REN::SET + CS::TA::SET; + if let Some(cs_index) = config.chip_select_index + && cs_index < 3 + { + cs += CS::CS.val(cs_index); + } + if let Some(cpol) = config.clock_polarity { + match cpol { + SpiClockPolarity::IdleLow => cs += CS::CPOL::IdleLow, + SpiClockPolarity::IdleHigh => cs += CS::CPOL::IdleHigh, + } + } + if let Some(cpha) = config.clock_phase { + match cpha { + SpiClockPhase::CaptureOnFirstTransition => cs += CS::CPHA::CaptureOnFirstTransition, + SpiClockPhase::CaptureOnSecondTransition => { + cs += CS::CPHA::CaptureOnSecondTransition + } + } + } + + self.CLK.set(divider as u32); + self.CS.write(cs); + + Ok(()) + } + + fn tx_rx_byte(&self, tx: u8) -> Result, Error> { + // Wait for tx + loop { + let status = self.CS.extract(); + if status.matches_all(CS::TXD::SET) { + break; + } + if status.matches_all(CS::DONE::SET) { + return Ok(None); + } + } + self.FIFO.set(tx as u32); + // Wait for rx + loop { + let status = self.CS.extract(); + if status.matches_all(CS::RXD::SET) { + break; + } + if status.matches_all(CS::DONE::SET) { + return Ok(None); + } + } + Ok(Some(self.FIFO.get() as u8)) + } +} + +#[async_trait] +impl SpiController for Spi { + async fn spi_transfer( + &self, + tx_buffer: &[u8], + rx_buffer: &mut [u8], + config: &SpiConfig, + ) -> Result { + // TODO interrupt/DMA + log::debug!("{}: spi_transfer {} bytes", self.name, rx_buffer.len()); + let core_clock = self.clock.rate()?; + let regs = self.regs.lock(); + regs.start_transfer(core_clock, config)?; + + let mut len = 0; + for (&tx, rx) in iter::zip(tx_buffer, rx_buffer) { + match regs.tx_rx_byte(tx)? { + Some(b) => { + *rx = b; + len += 1; + } + None => { + break; + } + } + } + Ok(len) + } +} + +device_tree_driver! { + compatible: ["brcm,bcm2835-spi"], + driver: { + fn probe(&self, node: &Arc, context: &mut ProbeContext) -> Option> { + let name = node.name().unwrap_or("bcm2835-spi"); + let base = node.map_base(context, 0)?; + let irq = node.interrupt(0)?; + let clock = node.clock(0)?; + + let regs = unsafe { DeviceMemoryIo::map(base, Default::default()) }.ok()?; + + let spi = Arc::new(Spi { + regs: IrqSafeSpinlock::new(regs), + name, + clock, + irq + }); + + Some(spi) + } + } +} diff --git a/kernel/lib/device-api/Cargo.toml b/kernel/lib/device-api/Cargo.toml index fbfe56bb..a18e4b90 100644 --- a/kernel/lib/device-api/Cargo.toml +++ b/kernel/lib/device-api/Cargo.toml @@ -9,6 +9,7 @@ authors = ["Mark Poliakov "] [dependencies] yggdrasil-abi.workspace = true device-api-macros = { workspace = true, optional = true } +async-trait.workspace = true [features] default = [] diff --git a/kernel/lib/device-api/src/clock/freq.rs b/kernel/lib/device-api/src/clock/freq.rs index d666c711..a327293c 100644 --- a/kernel/lib/device-api/src/clock/freq.rs +++ b/kernel/lib/device-api/src/clock/freq.rs @@ -26,6 +26,16 @@ pub trait IntoHertz { } } +impl Hertz { + pub fn divider(base: Self, desired: Self) -> Option { + if desired > base || desired.0 == 0 { + return None; + } + let value = base.0 / desired.0; + value.try_into().ok() + } +} + impl From for Hertz { fn from(value: u32) -> Self { Self(value as u64) diff --git a/kernel/lib/device-api/src/device.rs b/kernel/lib/device-api/src/device.rs index 70e21115..395539ad 100644 --- a/kernel/lib/device-api/src/device.rs +++ b/kernel/lib/device-api/src/device.rs @@ -1,3 +1,5 @@ +use core::any::Any; + use alloc::sync::Arc; use yggdrasil_abi::error::Error; @@ -51,4 +53,8 @@ pub trait Device: Sync + Send { fn as_clock_controller(self: Arc) -> Option> { None } + + fn as_any(self: Arc) -> Arc { + unimplemented!() + } } diff --git a/kernel/lib/device-api/src/lib.rs b/kernel/lib/device-api/src/lib.rs index af3d34d2..ba6f9c20 100644 --- a/kernel/lib/device-api/src/lib.rs +++ b/kernel/lib/device-api/src/lib.rs @@ -11,6 +11,7 @@ pub mod gpio; pub mod i2c; pub mod interrupt; pub mod serial; +pub mod spi; pub mod timer; pub mod dma; diff --git a/kernel/lib/device-api/src/spi.rs b/kernel/lib/device-api/src/spi.rs new file mode 100644 index 00000000..ae962f98 --- /dev/null +++ b/kernel/lib/device-api/src/spi.rs @@ -0,0 +1,15 @@ +use alloc::boxed::Box; +use async_trait::async_trait; +use yggdrasil_abi::{error::Error, io::device::spi::SpiConfig}; + +use crate::device::Device; + +#[async_trait] +pub trait SpiController: Device { + async fn spi_transfer( + &self, + tx_buffer: &[u8], + rx_buffer: &mut [u8], + config: &SpiConfig, + ) -> Result; +} diff --git a/kernel/lib/device-tree/src/driver/tree.rs b/kernel/lib/device-tree/src/driver/tree.rs index 66f596b5..6644ea86 100644 --- a/kernel/lib/device-tree/src/driver/tree.rs +++ b/kernel/lib/device-tree/src/driver/tree.rs @@ -326,6 +326,11 @@ impl Node { } } + /// Returns the device associated with this node + pub fn device(self: Arc) -> Option> { + Some(self.probe()?.device.clone()) + } + /// Returns an iterator over the node's children pub fn children(&self) -> impl Iterator> { self.children.get().iter() diff --git a/kernel/libk/src/device/char.rs b/kernel/libk/src/device/char/i2c.rs similarity index 57% rename from kernel/libk/src/device/char.rs rename to kernel/libk/src/device/char/i2c.rs index 63c2aa0e..c3b59152 100644 --- a/kernel/libk/src/device/char.rs +++ b/kernel/libk/src/device/char/i2c.rs @@ -1,14 +1,9 @@ -use core::{ - any::Any, - ops::Deref, - task::{Context, Poll}, -}; +use core::task::{Context, Poll}; -use alloc::{boxed::Box, sync::Arc}; +use alloc::boxed::Box; use async_trait::async_trait; use device_api::{ clock::Hertz, - device::Device, i2c::{I2CAddress, I2CDevice}, }; use yggdrasil_abi::{ @@ -18,78 +13,7 @@ use yggdrasil_abi::{ process::ProcessId, }; -use crate::{ - task::thread::Thread, - vfs::{CommonImpl, FileReadiness, NodeRef}, -}; - -#[async_trait] -pub trait CharDevice: Device + FileReadiness { - async fn read(&self, buffer: &mut [u8]) -> Result { - self.read_nonblocking(buffer) - } - async fn write(&self, buffer: &[u8]) -> Result { - self.write_nonblocking(buffer) - } - - fn read_nonblocking(&self, buffer: &mut [u8]) -> Result { - let _ = buffer; - Err(Error::NotImplemented) - } - fn write_nonblocking(&self, buffer: &[u8]) -> Result { - let _ = buffer; - Err(Error::NotImplemented) - } - - fn open(&self, process: ProcessId) -> Result<(), Error> { - let _ = process; - Ok(()) - } - fn close(&self, process: ProcessId) -> Result<(), Error> { - let _ = process; - Ok(()) - } - - fn is_readable(&self) -> bool { - true - } - fn is_writeable(&self) -> bool { - true - } - fn is_terminal(&self) -> bool { - false - } - - fn device_request(&self, option: u32, buffer: &mut [u8], len: usize) -> Result { - let _ = option; - let _ = buffer; - let _ = len; - log::warn!("device_request unimplemented: {option:#x}"); - Err(Error::InvalidOperation) - } -} - -#[derive(Clone)] -pub struct CharDeviceFile(pub(crate) Arc); - -impl CommonImpl for CharDeviceFile { - fn size(&self, node: &NodeRef) -> Result { - let _ = node; - Ok(0) - } - - fn as_any(&self) -> &dyn Any { - self as _ - } -} - -impl Deref for CharDeviceFile { - type Target = dyn CharDevice; - - fn deref(&self) -> &Self::Target { - self.0.as_ref() - } -} +use crate::{device::char::CharDevice, task::thread::Thread, vfs::FileReadiness}; // TODO timeouts and better access #[async_trait] diff --git a/kernel/libk/src/device/char/mod.rs b/kernel/libk/src/device/char/mod.rs new file mode 100644 index 00000000..19298226 --- /dev/null +++ b/kernel/libk/src/device/char/mod.rs @@ -0,0 +1,79 @@ +use core::{any::Any, ops::Deref}; + +use alloc::{boxed::Box, sync::Arc}; +use async_trait::async_trait; +use device_api::device::Device; +use yggdrasil_abi::{error::Error, process::ProcessId}; + +use crate::vfs::{CommonImpl, FileReadiness, NodeRef}; + +pub mod i2c; +pub mod spi; + +#[async_trait] +pub trait CharDevice: Device + FileReadiness { + async fn read(&self, buffer: &mut [u8]) -> Result { + self.read_nonblocking(buffer) + } + async fn write(&self, buffer: &[u8]) -> Result { + self.write_nonblocking(buffer) + } + + fn read_nonblocking(&self, buffer: &mut [u8]) -> Result { + let _ = buffer; + Err(Error::NotImplemented) + } + fn write_nonblocking(&self, buffer: &[u8]) -> Result { + let _ = buffer; + Err(Error::NotImplemented) + } + + fn open(&self, process: ProcessId) -> Result<(), Error> { + let _ = process; + Ok(()) + } + fn close(&self, process: ProcessId) -> Result<(), Error> { + let _ = process; + Ok(()) + } + + fn is_readable(&self) -> bool { + true + } + fn is_writeable(&self) -> bool { + true + } + fn is_terminal(&self) -> bool { + false + } + + fn device_request(&self, option: u32, buffer: &mut [u8], len: usize) -> Result { + let _ = option; + let _ = buffer; + let _ = len; + log::warn!("device_request unimplemented: {option:#x}"); + Err(Error::InvalidOperation) + } +} + +#[derive(Clone)] +pub struct CharDeviceFile(pub(crate) Arc); + +impl CommonImpl for CharDeviceFile { + fn size(&self, node: &NodeRef) -> Result { + let _ = node; + Ok(0) + } + + fn as_any(&self) -> &dyn Any { + self as _ + } +} + +impl Deref for CharDeviceFile { + type Target = dyn CharDevice; + + fn deref(&self) -> &Self::Target { + self.0.as_ref() + } +} diff --git a/kernel/libk/src/device/char/spi.rs b/kernel/libk/src/device/char/spi.rs new file mode 100644 index 00000000..d2d40014 --- /dev/null +++ b/kernel/libk/src/device/char/spi.rs @@ -0,0 +1,94 @@ +use core::task::{Context, Poll}; + +use alloc::{boxed::Box, sync::Arc}; +use async_trait::async_trait; +use device_api::{device::Device, spi::SpiController}; +use libk_util::sync::spin_rwlock::IrqSafeRwLock; +use yggdrasil_abi::{ + error::Error, + io::device::spi::{self, SpiConfig, SpiRequestVariant}, + option::RequestValue, +}; + +use crate::{ + device::char::CharDevice, + task::{mem::ForeignPointer, thread::Thread}, + vfs::FileReadiness, +}; + +pub struct SpiControllerDevice { + device: Arc, + config: IrqSafeRwLock, +} + +impl From> for SpiControllerDevice { + fn from(value: Arc) -> Self { + Self { + device: value, + config: IrqSafeRwLock::new(Default::default()), + } + } +} + +impl Device for SpiControllerDevice { + fn display_name(&self) -> &str { + self.device.display_name() + } +} + +// TODO timeouts and better access +#[async_trait] +impl CharDevice for SpiControllerDevice { + async fn read(&self, _buffer: &mut [u8]) -> Result { + Err(Error::InvalidOperation) + } + async fn write(&self, _buffer: &[u8]) -> Result { + Err(Error::InvalidOperation) + } + fn read_nonblocking(&self, _buffer: &mut [u8]) -> Result { + Err(Error::InvalidOperation) + } + fn write_nonblocking(&self, _buffer: &[u8]) -> Result { + Err(Error::InvalidOperation) + } + fn device_request(&self, option: u32, buffer: &mut [u8], len: usize) -> Result { + if let Ok(request) = SpiRequestVariant::try_from(option) { + match request { + SpiRequestVariant::Transfer => { + let xfer = spi::Transfer::load_request(&buffer[..len])?; + let thread = Thread::current(); + let space = thread.address_space(); + let len = xfer.len; + let tx_buffer = unsafe { xfer.tx_buffer.validate_user_slice(len, space) }?; + let rx_buffer = unsafe { xfer.rx_buffer.validate_user_slice_mut(len, space) }?; + let config = self.config.read(); + + let len = block!( + self.device + .spi_transfer(tx_buffer, rx_buffer, &config) + .await + )??; + + let len = spi::Transfer::store_response(&len, buffer)?; + + Ok(len) + } + SpiRequestVariant::SetConfig => { + // TODO validate config against min/max controller capabilities + let config = spi::SetConfig::load_request(&buffer[..len])?; + *self.config.write() = config; + Ok(0) + } + } + } else { + todo!() + } + } +} + +impl FileReadiness for SpiControllerDevice { + fn poll_read(&self, cx: &mut Context<'_>) -> Poll> { + let _ = cx; + todo!() + } +} diff --git a/kernel/libk/src/device/manager.rs b/kernel/libk/src/device/manager.rs index e13a4a12..e25436ff 100644 --- a/kernel/libk/src/device/manager.rs +++ b/kernel/libk/src/device/manager.rs @@ -4,13 +4,17 @@ use core::{ }; use alloc::{collections::BTreeMap, format, sync::Arc, vec::Vec}; -use device_api::i2c::{I2CController, I2CDevice}; +use device_api::{ + i2c::{I2CController, I2CDevice}, + spi::SpiController, +}; use libk_util::{OneTimeInit, sync::spin_rwlock::IrqSafeRwLock}; use yggdrasil_abi::{error::Error, io::FileMode}; use crate::{ config, debug::{self, DebugSink}, + device::char::spi::SpiControllerDevice, fs::devfs, vfs::NodeRef, }; @@ -52,11 +56,17 @@ pub struct I2CDeviceRegistry { registry: GenericRegistry>, } +// Manages devices: spi. +pub struct SpiDeviceRegistry { + registry: GenericRegistry>, +} + pub struct DeviceRegistry { pub display: DisplayDeviceRegistry, pub terminal: TerminalRegistry, pub serial_terminal: SerialTerminalRegistry, pub i2c: I2CDeviceRegistry, + pub spi: SpiDeviceRegistry, } pub static DEVICE_REGISTRY: DeviceRegistry = DeviceRegistry { @@ -64,6 +74,7 @@ pub static DEVICE_REGISTRY: DeviceRegistry = DeviceRegistry { terminal: TerminalRegistry::new(), serial_terminal: SerialTerminalRegistry::new(), i2c: I2CDeviceRegistry::new(), + spi: SpiDeviceRegistry::new(), }; impl TerminalRegistry { @@ -141,6 +152,22 @@ impl I2CDeviceRegistry { } } +impl SpiDeviceRegistry { + pub const fn new() -> Self { + Self { + registry: GenericRegistry::new(), + } + } + + pub fn register_bus(&self, device: Arc) -> Result { + let wrapper = Arc::new(SpiControllerDevice::from(device)); + let id = self.registry.register(wrapper.clone())?; + let name = format!("spi{id}"); + devfs::add_named_char_device(wrapper.clone(), &name, FileMode::new(0o600))?; + Ok(id) + } +} + impl GenericRegistry { pub const fn new() -> Self { Self { diff --git a/lib/abi/src/io/device.rs b/lib/abi/src/io/device.rs index 5bf4858c..49fe3d87 100644 --- a/lib/abi/src/io/device.rs +++ b/lib/abi/src/io/device.rs @@ -88,7 +88,7 @@ pub mod i2c { pub mod spi { #![allow(missing_docs)] - use crate::bitflags; + use crate::{bitflags, primitive_enum}; /// Represents a single SPI transfer #[derive(Clone, Copy, Debug)] @@ -100,10 +100,31 @@ pub mod spi { pub rx_buffer: *mut u8, /// Transfer size pub len: usize, - /// SPI clock rate - pub speed_hz: Option, - /// Bits per SPI word - pub bits_per_word: u8, + } + + primitive_enum! { + pub enum SpiClockPolarity: u32 { + IdleLow = 0, + IdleHigh = 1, + } + [with_serde] + } + + primitive_enum! { + pub enum SpiClockPhase: u32 { + CaptureOnFirstTransition = 0, + CaptureOnSecondTransition = 1 + } + [with_serde] + } + + #[derive(Clone, Copy, Debug, Default)] + #[repr(C)] + pub struct SpiConfig { + pub clock_polarity: Option, + pub clock_phase: Option, + pub frequency: Option, + pub chip_select_index: Option, } bitflags! { @@ -130,12 +151,18 @@ pub mod spi { #[doc = "SPI device requests"] pub enum SpiRequestVariant<'de> { #[doc = "Perform a SPI transfer"] - 0x2000: Transfer(SpiTransfer, usize) + 0x2000: Transfer(SpiTransfer, usize), + #[doc = "Set SPI configuration"] + 0x2001: SetConfig(SpiConfig, ()), } ); abi_serde::impl_struct_serde!(SpiTransfer: [ - tx_buffer, rx_buffer, len, speed_hz, bits_per_word + tx_buffer, rx_buffer, len + ]); + + abi_serde::impl_struct_serde!(SpiConfig: [ + clock_polarity, clock_phase, frequency, chip_select_index ]); } diff --git a/userspace/lib/cross/src/lib.rs b/userspace/lib/cross/src/lib.rs index 4423ff13..839d9ff4 100644 --- a/userspace/lib/cross/src/lib.rs +++ b/userspace/lib/cross/src/lib.rs @@ -12,6 +12,7 @@ pub mod net; pub mod path; pub mod process; pub mod signal; +pub mod spi; pub mod term; pub mod time { diff --git a/userspace/lib/cross/src/spi.rs b/userspace/lib/cross/src/spi.rs new file mode 100644 index 00000000..04a3b86f --- /dev/null +++ b/userspace/lib/cross/src/spi.rs @@ -0,0 +1,21 @@ +use std::{io, path::Path}; + +use crate::sys::{SpiDevice as SysSpiDevice, spi::SpiDeviceImpl}; + +pub use crate::sys::{SpiClockPhase, SpiClockPolarity, SpiConfig}; + +pub struct SpiDevice(SpiDeviceImpl); + +impl SpiDevice { + pub fn open>(device: P, config: &SpiConfig) -> io::Result { + SpiDeviceImpl::open(device, config).map(Self) + } + + pub fn transfer(&mut self, tx: &[u8], rx: &mut [u8]) -> io::Result { + self.0.transfer(tx, rx) + } + + pub fn set_config(&mut self, config: &SpiConfig) -> io::Result<()> { + self.0.set_config(config) + } +} diff --git a/userspace/lib/cross/src/sys/mod.rs b/userspace/lib/cross/src/sys/mod.rs index 7df346a8..51bdc468 100644 --- a/userspace/lib/cross/src/sys/mod.rs +++ b/userspace/lib/cross/src/sys/mod.rs @@ -78,6 +78,32 @@ pub(crate) trait I2CMaster: Read + Write + AsRawFd + Sized { fn smbus_write(&mut self, reg: u8, data: &[u8]) -> io::Result; } +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum SpiClockPolarity { + IdleLow, + IdleHigh, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum SpiClockPhase { + CaptureOnFirstTransition, + CaptureOnSecondTransition, +} + +#[derive(Debug, Default)] +pub struct SpiConfig { + pub clock_polarity: Option, + pub clock_phase: Option, + pub frequency: Option, + pub chip_select_index: Option, +} + +pub(crate) trait SpiDevice: AsRawFd + Sized { + fn open>(device: P, config: &SpiConfig) -> io::Result; + fn set_config(&mut self, config: &SpiConfig) -> io::Result<()>; + fn transfer(&mut self, tx: &[u8], rx: &mut [u8]) -> io::Result; +} + pub trait TerminalOptions: Copy { fn normal() -> Self; fn raw() -> Self; diff --git a/userspace/lib/cross/src/sys/yggdrasil/mod.rs b/userspace/lib/cross/src/sys/yggdrasil/mod.rs index c85b4156..539a95d5 100644 --- a/userspace/lib/cross/src/sys/yggdrasil/mod.rs +++ b/userspace/lib/cross/src/sys/yggdrasil/mod.rs @@ -8,6 +8,7 @@ pub mod poll; pub mod pty; pub mod serial; pub mod socket; +pub mod spi; pub mod term; pub mod time; pub mod timer; diff --git a/userspace/lib/cross/src/sys/yggdrasil/spi.rs b/userspace/lib/cross/src/sys/yggdrasil/spi.rs new file mode 100644 index 00000000..6af659b1 --- /dev/null +++ b/userspace/lib/cross/src/sys/yggdrasil/spi.rs @@ -0,0 +1,76 @@ +use std::{ + cmp, + fs::{File, OpenOptions}, + io, + os::fd::{AsRawFd, RawFd}, + path::Path, +}; + +use runtime::{abi::io::device::spi as sys_spi, rt::io::device::device_request}; + +use crate::sys::{SpiClockPhase, SpiClockPolarity, SpiConfig, SpiDevice}; + +pub struct SpiDeviceImpl { + file: File, +} + +impl SpiDevice for SpiDeviceImpl { + fn open>(device: P, config: &SpiConfig) -> io::Result { + let file = OpenOptions::new().read(true).write(true).open(device)?; + let mut this = Self { file }; + this.set_config(config)?; + Ok(this) + } + + fn set_config(&mut self, config: &SpiConfig) -> io::Result<()> { + let clock_polarity = config.clock_polarity.map(Into::into); + let clock_phase = config.clock_phase.map(Into::into); + + let sys_config = sys_spi::SpiConfig { + clock_polarity, + clock_phase, + frequency: config.frequency, + chip_select_index: config.chip_select_index, + }; + + let mut buffer = [0; 256]; + device_request::(self.file.as_raw_fd(), &mut buffer, &sys_config)?; + + Ok(()) + } + + fn transfer(&mut self, tx: &[u8], rx: &mut [u8]) -> io::Result { + let xfer = sys_spi::SpiTransfer { + tx_buffer: tx.as_ptr(), + rx_buffer: rx.as_mut_ptr(), + len: cmp::min(tx.len(), rx.len()), + }; + let mut buffer = [0; 32]; + let len = device_request::(self.file.as_raw_fd(), &mut buffer, &xfer)?; + Ok(len) + } +} + +impl AsRawFd for SpiDeviceImpl { + fn as_raw_fd(&self) -> RawFd { + self.file.as_raw_fd() + } +} + +impl From for sys_spi::SpiClockPolarity { + fn from(value: SpiClockPolarity) -> Self { + match value { + SpiClockPolarity::IdleLow => Self::IdleLow, + SpiClockPolarity::IdleHigh => Self::IdleHigh, + } + } +} + +impl From for sys_spi::SpiClockPhase { + fn from(value: SpiClockPhase) -> Self { + match value { + SpiClockPhase::CaptureOnFirstTransition => Self::CaptureOnFirstTransition, + SpiClockPhase::CaptureOnSecondTransition => Self::CaptureOnSecondTransition, + } + } +} diff --git a/userspace/sysutils/src/tst.rs b/userspace/sysutils/src/tst.rs index 622260df..f2112357 100644 --- a/userspace/sysutils/src/tst.rs +++ b/userspace/sysutils/src/tst.rs @@ -7,14 +7,31 @@ use std::{ }; use clap::Parser; -use cross::i2c::{I2CMaster, I2CMasterConfig}; +use cross::{ + i2c::{I2CMaster, I2CMasterConfig}, + spi::{SpiConfig, SpiDevice}, +}; #[derive(Debug, Parser)] struct Args { - #[clap(short, long, default_value_t = 0x40, help = "I²C device address")] - address: u8, - #[clap(help = "I²C controller path")] - device: PathBuf, + #[clap(subcommand)] + command: Subcommand, +} + +#[derive(Debug, clap::Subcommand)] +enum Subcommand { + M41T80 { + #[clap(short, long, default_value_t = 0x40, help = "I²C device address")] + address: u8, + #[clap(help = "I²C controller path")] + device: PathBuf, + }, + JedecFlash { + #[clap(short, long, default_value_t = 4 * 1024 * 1024, help = "SPI flash capacity")] + capacity: usize, + #[clap(help = "SPI device path")] + device: PathBuf, + }, } struct M41T80 { @@ -65,13 +82,13 @@ impl M41T80 { } } -fn run(args: Args) -> io::Result<()> { +fn run_m41t80(device: PathBuf, address: u8) -> io::Result<()> { // let device = "/dev/i2c0"; let i2c_config = I2CMasterConfig { - slave_address: Some(args.address as _), + slave_address: Some(address as _), speed_hz: None, }; - let mut m41t80 = M41T80::open(args.device, &i2c_config)?; + let mut m41t80 = M41T80::open(device, &i2c_config)?; loop { let time = m41t80.read_time()?; print!( @@ -83,6 +100,52 @@ fn run(args: Args) -> io::Result<()> { } } +fn run_jedec(device: PathBuf, capacity: usize) -> io::Result<()> { + let config = SpiConfig::default(); + let mut spi = SpiDevice::open(device, &config)?; + + let tx = [0x13, 0x00, 0x00, 0x00, 0x00]; + let mut rx = [0; 5]; + spi.transfer(&tx, &mut rx).unwrap(); + + let mut memory = vec![0xFF; capacity]; + + const READ_BUFFER: usize = 1024; + let tx_word = [0xFF; READ_BUFFER]; + let mut rx_word = [0; READ_BUFFER]; + let mut bytes_read = 0; + for i in 0..capacity / READ_BUFFER { + if spi.transfer(&tx_word, &mut rx_word)? != READ_BUFFER { + break; + } + memory[i * READ_BUFFER..i * READ_BUFFER + READ_BUFFER].copy_from_slice(&rx_word); + bytes_read += READ_BUFFER; + } + + for (i, &byte) in memory.iter().enumerate() { + if i >= bytes_read { + print!("??"); + } else { + print!("{:02x}", byte); + } + if (i + 1) % 2 == 0 { + print!(" "); + } + if (i + 1) % 32 == 0 { + println!(); + } + } + + Ok(()) +} + +fn run(args: Args) -> io::Result<()> { + match args.command { + Subcommand::M41T80 { address, device } => run_m41t80(device, address), + Subcommand::JedecFlash { capacity, device } => run_jedec(device, capacity), + } +} + fn main() -> ExitCode { logsink::setup_logging(true); let args = Args::parse();