spi: initial spi device support

This commit is contained in:
2026-02-04 14:46:57 +02:00
parent ed9d7a7145
commit 0e979a9e09
24 changed files with 784 additions and 107 deletions
+1
View File
@@ -16,3 +16,4 @@ tock-registers.workspace = true
log.workspace = true
bytemuck.workspace = true
futures-util.workspace = true
async-trait.workspace = true
+18 -3
View File
@@ -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<Bcm2835Mbox>,
}
impl Device for Cprman {
@@ -26,8 +31,15 @@ impl Device for Cprman {
impl ClockController for Cprman {
fn clock_rate(&self, clock: Option<u32>) -> Result<Hertz, Error> {
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<u32>, rate: Hertz) -> Result<Hertz, Error> {
@@ -65,10 +77,13 @@ device_tree_driver! {
driver: {
fn probe(&self, node: &Arc<Node>, context: &mut ProbeContext) -> Option<Arc<dyn Device>> {
let _base = node.map_base(context, 0)?;
let mbox = Node::by_compatible("brcm,bcm2835-mbox", true).unwrap().device().unwrap();
let mbox = Arc::downcast::<Bcm2835Mbox>(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());
+1
View File
@@ -8,3 +8,4 @@ mod cprman;
mod gpio;
mod i2c;
mod mbox;
mod spi;
+46 -9
View File
@@ -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<DeviceMemoryIo<'static, Regs>>,
}
@@ -85,13 +94,20 @@ impl Device for Bcm2835Mbox {
fn display_name(&self) -> &str {
"bcm2835-mbox"
}
fn as_any(self: Arc<Self>) -> Arc<dyn Any + Send + Sync> {
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<FramebufferResponse, Error> {
pub fn vc_clock_rate(&self, clock: Bcm2835VcClock) -> Result<Hertz, Error> {
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<FramebufferResponse, Error> {
#[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();
+202
View File
@@ -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<u32, CS::Register>),
(0x04 => FIFO: ReadWrite<u32>),
(0x08 => CLK: ReadWrite<u32>),
(0x0C => DLEN: ReadWrite<u32>),
(0x10 => LTOH: ReadWrite<u32>),
(0x14 => DC: ReadWrite<u32>),
(0x18 => @END),
}
}
pub struct Spi {
name: &'static str,
clock: ClockHandle,
irq: IrqHandle,
regs: IrqSafeSpinlock<DeviceMemoryIo<'static, Regs>>,
}
impl Device for Spi {
unsafe fn init(self: Arc<Self>, _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<Option<u8>, 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<usize, Error> {
// 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<Node>, context: &mut ProbeContext) -> Option<Arc<dyn Device>> {
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)
}
}
}
+1
View File
@@ -9,6 +9,7 @@ authors = ["Mark Poliakov <mark@alnyan.me>"]
[dependencies]
yggdrasil-abi.workspace = true
device-api-macros = { workspace = true, optional = true }
async-trait.workspace = true
[features]
default = []
+10
View File
@@ -26,6 +26,16 @@ pub trait IntoHertz {
}
}
impl Hertz {
pub fn divider(base: Self, desired: Self) -> Option<u32> {
if desired > base || desired.0 == 0 {
return None;
}
let value = base.0 / desired.0;
value.try_into().ok()
}
}
impl From<u32> for Hertz {
fn from(value: u32) -> Self {
Self(value as u64)
+6
View File
@@ -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<Self>) -> Option<Arc<dyn ClockController>> {
None
}
fn as_any(self: Arc<Self>) -> Arc<dyn Any + Send + Sync> {
unimplemented!()
}
}
+1
View File
@@ -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;
+15
View File
@@ -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<usize, Error>;
}
@@ -326,6 +326,11 @@ impl Node {
}
}
/// Returns the device associated with this node
pub fn device(self: Arc<Self>) -> Option<Arc<dyn Device>> {
Some(self.probe()?.device.clone())
}
/// Returns an iterator over the node's children
pub fn children(&self) -> impl Iterator<Item = &Arc<Node>> {
self.children.get().iter()
@@ -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<usize, Error> {
self.read_nonblocking(buffer)
}
async fn write(&self, buffer: &[u8]) -> Result<usize, Error> {
self.write_nonblocking(buffer)
}
fn read_nonblocking(&self, buffer: &mut [u8]) -> Result<usize, Error> {
let _ = buffer;
Err(Error::NotImplemented)
}
fn write_nonblocking(&self, buffer: &[u8]) -> Result<usize, Error> {
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<usize, Error> {
let _ = option;
let _ = buffer;
let _ = len;
log::warn!("device_request unimplemented: {option:#x}");
Err(Error::InvalidOperation)
}
}
#[derive(Clone)]
pub struct CharDeviceFile(pub(crate) Arc<dyn CharDevice>);
impl CommonImpl for CharDeviceFile {
fn size(&self, node: &NodeRef) -> Result<u64, Error> {
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]
+79
View File
@@ -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<usize, Error> {
self.read_nonblocking(buffer)
}
async fn write(&self, buffer: &[u8]) -> Result<usize, Error> {
self.write_nonblocking(buffer)
}
fn read_nonblocking(&self, buffer: &mut [u8]) -> Result<usize, Error> {
let _ = buffer;
Err(Error::NotImplemented)
}
fn write_nonblocking(&self, buffer: &[u8]) -> Result<usize, Error> {
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<usize, Error> {
let _ = option;
let _ = buffer;
let _ = len;
log::warn!("device_request unimplemented: {option:#x}");
Err(Error::InvalidOperation)
}
}
#[derive(Clone)]
pub struct CharDeviceFile(pub(crate) Arc<dyn CharDevice>);
impl CommonImpl for CharDeviceFile {
fn size(&self, node: &NodeRef) -> Result<u64, Error> {
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()
}
}
+94
View File
@@ -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<dyn SpiController>,
config: IrqSafeRwLock<SpiConfig>,
}
impl From<Arc<dyn SpiController>> for SpiControllerDevice {
fn from(value: Arc<dyn SpiController>) -> 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<usize, Error> {
Err(Error::InvalidOperation)
}
async fn write(&self, _buffer: &[u8]) -> Result<usize, Error> {
Err(Error::InvalidOperation)
}
fn read_nonblocking(&self, _buffer: &mut [u8]) -> Result<usize, Error> {
Err(Error::InvalidOperation)
}
fn write_nonblocking(&self, _buffer: &[u8]) -> Result<usize, Error> {
Err(Error::InvalidOperation)
}
fn device_request(&self, option: u32, buffer: &mut [u8], len: usize) -> Result<usize, Error> {
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<Result<(), Error>> {
let _ = cx;
todo!()
}
}
+28 -1
View File
@@ -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<Arc<I2CDevice>>,
}
// Manages devices: spi<X>.<Y>
pub struct SpiDeviceRegistry {
registry: GenericRegistry<Arc<SpiControllerDevice>>,
}
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<dyn SpiController>) -> Result<u32, Error> {
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<T> GenericRegistry<T> {
pub const fn new() -> Self {
Self {