i2c: implement initial support for i2c devices

This commit is contained in:
2026-02-03 12:08:15 +02:00
parent 218e391505
commit 58dbaddf11
27 changed files with 4026 additions and 73 deletions
+131 -3
View File
@@ -134,23 +134,57 @@
pinctrl-names = "default";
bootph-all;
// UART
uart0_ctsrts_gpio30: uart0_ctsrts_gpio30 {
brcm,pins = <30>, <31>;
brcm,pull = <2>, <0>;
brcm,function = <7>;
};
uart0_gpio32: uart0_gpio32 {
brcm,pins = <32>, <33>;
brcm,pull = <0>, <2>;
brcm,function = <8>;
};
uart1_gpio14: uart1_gpio14 {
brcm,pins = <14>, <15>;
brcm,function = <2>;
bootph-all;
};
// I²C
i2c0if_gpio0: i2c0if-gpio0 {
brcm,pins = <0>, <1>;
brcm,function = <4>;
};
i2c0if_gpio44: i2c0if-gpio44 {
brcm,pins = <44>, <45>;
brcm,function = <5>;
};
i2c1_gpio: i2c1 {
brcm,pins = <2>, <3>;
brcm,function = <4>;
brcm,pull = <2>;
};
i2c3_gpio: i2c3 {
brcm,pins = <4>, <5>;
brcm,function = <2>;
brcm,pull = <2>;
};
i2c4_gpio: i2c4 {
brcm,pins = <8>, <9>;
brcm,function = <2>;
brcm,pull = <2>;
};
i2c5_gpio: i2c5 {
brcm,pins = <12>, <13>;
brcm,function = <2>;
brcm,pull = <2>;
};
i2c6_gpio: i2c6 {
brcm,pins = <22>, <23>;
brcm,function = <2>;
brcm,pull = <2>;
};
};
uart0: serial@7e201000 {
@@ -163,7 +197,7 @@
"apb_pclk";
arm,primecell-periphid = <0x241011>;
pinctrl-names = "default";
pinctrl-0 = <&uart0_ctsrts_gpio30 &uart0_gpio32>;
pinctrl-0 = <&uart0_ctsrts_gpio30>, <&uart0_gpio32>;
uart-has-rtscts;
status = "okay";
skip-init;
@@ -189,6 +223,100 @@
bootph-all;
};
i2c0if: i2c@7e205000 {
compatible = "brcm,bcm2711-i2c", "brcm,bcm2835-i2c";
reg = <0x7e205000 0x200>;
interrupts = <GIC_SPI 117 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&cprman 0x14>;
#address-cells = <0x01>;
#size-cells = <0x00>;
status = "okay";
clock-frequency = <0x186a0>;
};
i2c0mux: i2c0mux {
compatible = "i2c-mux-pinctrl";
#address-cells = <0x01>;
#size-cells = <0x00>;
i2c-parent = <&i2c0if>;
status = "okay";
pinctrl-names = "i2c0", "i2c_csi_dsi";
pinctrl-0 = <&i2c0if_gpio0>;
pinctrl-1 = <&i2c0if_gpio44>;
i2c0: i2c@0 {
reg = <0x00>;
#address-cells = <0x01>;
#size-cells = <0x00>;
};
i2c_csi_dsi: i2c@1 {
reg = <0x01>;
#address-cells = <0x01>;
#size-cells = <0x00>;
};
};
i2c1: i2c@7e804000 {
compatible = "brcm,bcm2711-i2c", "brcm,bcm2835-i2c";
reg = <0x7e804000 0x1000>;
interrupts = <GIC_SPI 117 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&cprman 0x14>;
#address-cells = <0x01>;
#size-cells = <0x00>;
status = "okay";
pinctrl-names = "default";
pinctrl-0 = <&i2c1_gpio>;
clock-frequency = <0x186a0>;
};
// TODO: memory access crashes on qemu (not implemented?)
i2c3: i2c@7e205600 {
compatible = "brcm,bcm2711-i2c", "brcm,bcm2835-i2c";
reg = <0x7e205600 0x200>;
interrupts = <GIC_SPI 117 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&cprman 0x14>;
#address-cells = <0x01>;
#size-cells = <0x00>;
status = "disabled";
pinctrl-0 = <&i2c3_gpio>;
pinctrl-names = "default";
};
// TODO: memory access crashes on qemu (not implemented?)
i2c4: i2c@7e205800 {
compatible = "brcm,bcm2711-i2c", "brcm,bcm2835-i2c";
reg = <0x7e205800 0x200>;
interrupts = <GIC_SPI 117 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&cprman 0x14>;
#address-cells = <0x01>;
#size-cells = <0x00>;
status = "disabled";
pinctrl-0 = <&i2c4_gpio>;
pinctrl-names = "default";
};
// TODO: memory access crashes on qemu (not implemented?)
i2c5: i2c@7e205a00 {
compatible = "brcm,bcm2711-i2c", "brcm,bcm2835-i2c";
reg = <0x7e205a00 0x200>;
interrupts = <GIC_SPI 117 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&cprman 0x14>;
#address-cells = <0x01>;
#size-cells = <0x00>;
status = "disabled";
pinctrl-0 = <&i2c5_gpio>;
pinctrl-names = "default";
};
// TODO: memory access crashes on qemu (not implemented?)
i2c6: i2c@7e205c00 {
compatible = "brcm,bcm2711-i2c", "brcm,bcm2835-i2c";
reg = <0x7e205c00 0x200>;
interrupts = <GIC_SPI 117 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&cprman 0x14>;
#address-cells = <0x01>;
#size-cells = <0x00>;
status = "disabled";
pinctrl-0 = <&i2c6_gpio>;
pinctrl-names = "default";
};
l1_intc: local_intc@40000000 {
compatible = "brcm,bcm2836-l1-intc";
reg = <0x40000000 0x100>;
Binary file not shown.
File diff suppressed because it is too large Load Diff
+78
View File
@@ -0,0 +1,78 @@
use alloc::sync::Arc;
use device_api::{
clock::{ClockController, ClockHandle, Hertz},
device::{Device, DeviceInitContext},
};
use device_tree::{
DeviceTreePropertyRead, TProp,
driver::{DeviceTreeClockController, Node, ProbeContext, device_tree_driver},
};
use libk::error::Error;
struct Cprman {
clk_osc: ClockHandle,
}
impl Device for Cprman {
unsafe fn init(self: Arc<Self>, _cx: DeviceInitContext) -> Result<(), Error> {
let _ = &self.clk_osc;
Ok(())
}
fn display_name(&self) -> &str {
"bcm2711-cprman"
}
}
impl ClockController for Cprman {
fn clock_rate(&self, clock: Option<u32>) -> Result<Hertz, Error> {
let _ = clock;
todo!()
}
fn set_clock_rate(&self, clock: Option<u32>, rate: Hertz) -> Result<Hertz, Error> {
let _ = clock;
let _ = rate;
todo!()
}
fn enable_clock(&self, clock: Option<u32>) -> Result<(), Error> {
let _ = clock;
todo!()
}
fn disable_clock(&self, clock: Option<u32>) -> Result<(), Error> {
let _ = clock;
todo!()
}
}
impl DeviceTreeClockController for Cprman {
fn map_clock(self: Arc<Self>, property: &TProp, offset: usize) -> Option<(ClockHandle, usize)> {
let clock = property.read_cell(offset, 1)? as u32;
Some((
ClockHandle {
parent: self.clone(),
clock: Some(clock),
},
1,
))
}
}
device_tree_driver! {
compatible: ["brcm,bcm2711-cprman"],
driver: {
fn probe(&self, node: &Arc<Node>, context: &mut ProbeContext) -> Option<Arc<dyn Device>> {
let _base = node.map_base(context, 0)?;
let clk_osc = node.clock(0)?;
let cprman = Arc::new(Cprman {
clk_osc
});
node.make_clock_controller(cprman.clone());
Some(cprman)
}
}
}
+290
View File
@@ -0,0 +1,290 @@
use alloc::sync::Arc;
use device_api::{
clock::{ClockHandle, Hertz},
device::{Device, DeviceInitContext},
i2c::{I2CAddress, I2CController},
interrupt::{InterruptHandler, IrqHandle, IrqVector},
};
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::{OneTimeInit, sync::spin_rwlock::IrqSafeRwLock};
use tock_registers::{
interfaces::{ReadWriteable, Readable, Writeable},
register_bitfields, register_structs,
registers::ReadWrite,
};
use yggdrasil_abi::io::device::i2c::I2CMasterInfo;
register_bitfields! {
u32,
C [
I2CEN OFFSET(15) NUMBITS(1) [],
INTR OFFSET(10) NUMBITS(1) [],
INTT OFFSET(9) NUMBITS(1) [],
INTD OFFSET(8) NUMBITS(1) [],
ST OFFSET(7) NUMBITS(1) [],
CLEAR OFFSET(4) NUMBITS(2) [],
READ OFFSET(0) NUMBITS(1) [],
],
S [
CLKT OFFSET(9) NUMBITS(1) [],
ERR OFFSET(8) NUMBITS(1) [],
RXF OFFSET(7) NUMBITS(1) [],
TXE OFFSET(6) NUMBITS(1) [],
RXD OFFSET(5) NUMBITS(1) [],
TXD OFFSET(4) NUMBITS(1) [],
RXR OFFSET(3) NUMBITS(1) [],
TXW OFFSET(2) NUMBITS(1) [],
DONE OFFSET(1) NUMBITS(1) [],
TA OFFSET(0) NUMBITS(1) [],
],
}
register_structs! {
#[allow(non_snake_case)]
Regs {
(0x00 => C: ReadWrite<u32, C::Register>),
(0x04 => S: ReadWrite<u32, S::Register>),
(0x08 => DLEN: ReadWrite<u32>),
(0x0C => A: ReadWrite<u32>),
(0x10 => FIFO: ReadWrite<u32>),
(0x14 => DIV: ReadWrite<u32>),
(0x18 => DEL: ReadWrite<u32>),
(0x1C => CLKT: ReadWrite<u32>),
(0x20 => @END),
}
}
struct I2C {
name: &'static str,
clock_frequency: Option<Hertz>,
irq: IrqHandle,
clock: Option<ClockHandle>,
regs: IrqSafeRwLock<DeviceMemoryIo<'static, Regs>>,
index: OneTimeInit<u32>,
}
impl Regs {
fn start_transfer(&self, name: &str, buflen: u16, address: I2CAddress, read: bool) {
log::debug!(
"{}: start address={}, read={}, len={}",
name,
address,
read,
buflen
);
let address = address.as_8_bit().unwrap();
let read = if read { C::READ::SET } else { C::READ::CLEAR };
self.S.write(S::ERR::SET + S::DONE::SET);
self.DLEN.set(buflen as u32);
self.C.modify(C::ST::CLEAR + C::I2CEN::CLEAR);
self.A.set(address as u32);
self.C.modify(read + C::ST::SET + C::I2CEN::SET);
}
fn finish_transfer(&self, name: &str) -> Result<(), Error> {
log::debug!("{}: finish transfer", name);
let status = self.S.extract();
self.C.set(0);
if status.matches_all(S::ERR::SET) {
self.S.write(S::DONE::SET + S::ERR::SET);
return Err(Error::HostUnreachable);
}
if status.matches_all(S::DONE::SET) {
self.S.write(S::DONE::SET);
}
Ok(())
}
fn write_byte(&self, byte: u8) -> Result<bool, Error> {
loop {
let status = self.S.extract();
if status.matches_all(S::ERR::SET) {
self.C.write(C::CLEAR.val(1));
self.S.write(S::ERR::SET + S::DONE::SET);
// TODO better code
return Err(Error::HostUnreachable);
}
if status.matches_all(S::DONE::SET) {
self.C.set(0);
self.S.write(S::DONE::SET);
return Ok(false);
}
if status.matches_all(S::TXD::SET) {
self.FIFO.set(byte as u32);
return Ok(true);
}
core::hint::spin_loop();
}
}
fn read_byte(&self) -> Result<Option<u8>, Error> {
loop {
let status = self.S.extract();
if status.matches_all(S::ERR::SET) {
self.C.write(C::CLEAR.val(1));
self.S.write(S::ERR::SET + S::DONE::SET);
// TODO better code
return Err(Error::HostUnreachable);
}
if status.matches_all(S::DONE::SET) {
self.C.set(0);
self.S.write(S::DONE::SET);
return Ok(None);
}
if status.matches_all(S::RXD::SET) {
let val = self.FIFO.get() as u8;
return Ok(Some(val));
}
core::hint::spin_loop();
}
}
}
impl Device for I2C {
unsafe fn init(self: Arc<Self>, _cx: DeviceInitContext) -> Result<(), Error> {
// TODO
let _ = &self.clock;
log::info!("{}: initialize", self.name);
let regs = self.regs.write();
regs.C.set(0);
let index = DEVICE_REGISTRY.i2c.register_bus(self.clone())?;
self.index.init(index);
Ok(())
}
unsafe fn init_irq(self: Arc<Self>) -> Result<(), Error> {
// TODO
let _ = &self.irq;
// self.irq.register(self.clone())?;
// self.irq.enable()?;
// let regs = self.regs.write();
// regs.C.modify(C::INTD::SET);
Ok(())
}
fn display_name(&self) -> &str {
self.name
}
}
impl InterruptHandler for I2C {
fn handle_irq(self: Arc<Self>, _vector: IrqVector) -> bool {
todo!()
}
}
impl I2CController for I2C {
fn bus_number(&self) -> u32 {
*self.index.get()
}
fn child_number(&self) -> Option<u32> {
None
}
fn set_speed(&self, speed: Hertz) -> Result<Hertz, Error> {
if let Some(frequency) = self.clock_frequency {
// Fixed frequency
if speed == frequency {
Ok(speed)
} else {
Err(Error::InvalidArgument)
}
} else {
todo!("Set i2c speed = {speed}")
}
}
fn capabilities(&self) -> I2CMasterInfo {
let max_speed_hz = if let Some(frequency) = self.clock_frequency {
frequency
} else {
todo!()
}
.0 as u32;
I2CMasterInfo {
max_speed_hz,
supports_10bit_addresses: true,
}
}
fn i2c_write(&self, address: I2CAddress, buffer: &[u8]) -> Result<usize, Error> {
let buflen: u16 = buffer
.len()
.try_into()
.map_err(|_| Error::InvalidArgument)?;
let regs = self.regs.write();
// TODO DMA/interrupts
regs.start_transfer(self.name, buflen, address, false);
let mut bytes_written = 0;
// let mut done = false;
for &byte in buffer {
if regs.write_byte(byte)? {
bytes_written += 1;
} else {
break;
}
}
regs.finish_transfer(self.name)?;
Ok(bytes_written)
}
fn i2c_read(&self, address: I2CAddress, buffer: &mut [u8]) -> Result<usize, Error> {
let buflen: u16 = buffer
.len()
.try_into()
.map_err(|_| Error::InvalidArgument)?;
let regs = self.regs.write();
// TODO DMA/interrupts
regs.start_transfer(self.name, buflen, address, true);
let mut bytes_read = 0;
// let mut done = false;
for byte in buffer {
if let Some(val) = regs.read_byte()? {
*byte = val;
bytes_read += 1;
} else {
break;
}
}
regs.finish_transfer(self.name)?;
Ok(bytes_read)
}
}
device_tree_driver! {
compatible: ["brcm,bcm2711-i2c", "brcm,bcm2835-i2c"],
driver: {
fn probe(&self, node: &Arc<Node>, context: &mut ProbeContext) -> Option<Arc<dyn Device>> {
let clock_frequency = node.prop_usize("clock-frequency").map(|p| Hertz(p as u64));
let base = node.map_base(context, 0)?;
let name = node.name().unwrap_or("bcm2835-i2c");
let irq = node.interrupt(0)?;
let clock = node.clock(0);
let regs = unsafe { DeviceMemoryIo::map(base, Default::default()) }.ok()?;
let i2c = Arc::new(I2C {
name,
clock_frequency,
irq,
clock,
index: OneTimeInit::new(),
regs: IrqSafeRwLock::new(regs)
});
node.make_i2c_controller(i2c.clone());
Some(i2c)
}
}
}
+2
View File
@@ -4,5 +4,7 @@ extern crate alloc;
mod aux;
// mod aux_uart;
mod cprman;
mod gpio;
mod i2c;
mod mbox;
+12
View File
@@ -26,6 +26,18 @@ pub trait IntoHertz {
}
}
impl From<u32> for Hertz {
fn from(value: u32) -> Self {
Self(value as u64)
}
}
impl From<u64> for Hertz {
fn from(value: u64) -> Self {
Self(value)
}
}
impl Mul<u32> for Hertz {
type Output = Hertz;
+147
View File
@@ -0,0 +1,147 @@
use core::{
fmt,
ops::Deref,
sync::atomic::{AtomicU16, AtomicU32, Ordering},
};
use alloc::sync::Arc;
use yggdrasil_abi::{error::Error, io::device::i2c::I2CMasterInfo, process::ProcessId};
use crate::{
clock::Hertz,
device::{Device, DeviceInitContext},
};
pub struct I2CAddress(u16);
pub struct I2CDevice {
device: Arc<dyn I2CController>,
address: AtomicU16,
user: AtomicU32,
}
pub trait I2CController: Device {
fn bus_number(&self) -> u32;
fn child_number(&self) -> Option<u32>;
fn set_speed(&self, speed: Hertz) -> Result<Hertz, Error>;
fn capabilities(&self) -> I2CMasterInfo;
fn i2c_write(&self, address: I2CAddress, buffer: &[u8]) -> Result<usize, Error>;
fn i2c_read(&self, address: I2CAddress, buffer: &mut [u8]) -> Result<usize, Error>;
}
impl I2CAddress {
pub fn as_8_bit(&self) -> Option<u8> {
self.0.try_into().ok()
}
pub fn as_10_bit(&self) -> u16 {
self.0
}
pub fn is_8_bit(&self) -> bool {
self.0 <= 0xFF
}
}
impl fmt::Display for I2CAddress {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(&self.0, f)
}
}
impl From<u8> for I2CAddress {
fn from(value: u8) -> Self {
Self(value as u16)
}
}
impl TryFrom<u16> for I2CAddress {
type Error = Error;
fn try_from(value: u16) -> Result<Self, Self::Error> {
if value >= (1 << 10) {
return Err(Error::InvalidArgument);
}
Ok(Self(value))
}
}
impl Deref for I2CDevice {
type Target = dyn I2CController;
fn deref(&self) -> &Self::Target {
&*self.device
}
}
impl From<Arc<dyn I2CController>> for I2CDevice {
fn from(value: Arc<dyn I2CController>) -> Self {
Self {
device: value,
address: AtomicU16::new(0),
user: AtomicU32::new(0),
}
}
}
impl I2CDevice {
pub fn set_slave_address(&self, address: I2CAddress) {
self.address.store(address.0, Ordering::Release);
}
pub fn set_speed(&self, speed: Hertz) -> Result<Hertz, Error> {
self.device.set_speed(speed)
}
pub fn slave_address(&self) -> I2CAddress {
I2CAddress(self.address.load(Ordering::Acquire))
}
pub fn check_lock(&self, id: ProcessId) -> Result<(), Error> {
if self.user.load(Ordering::Acquire) == id.bits() {
Ok(())
} else {
Err(Error::PermissionDenied)
}
}
pub fn lock(&self, process: ProcessId) -> Result<(), Error> {
if self
.user
.compare_exchange(0, process.bits(), Ordering::Acquire, Ordering::Relaxed)
.is_ok()
{
Ok(())
} else {
Err(Error::WouldBlock)
}
}
pub fn release(&self, process: ProcessId) -> Result<(), Error> {
if self
.user
.compare_exchange(process.bits(), 0, Ordering::Release, Ordering::Relaxed)
.is_ok()
{
Ok(())
} else {
Err(Error::InvalidOperation)
}
}
}
impl Device for I2CDevice {
unsafe fn init(self: Arc<Self>, cx: DeviceInitContext) -> Result<(), Error> {
self.device.clone().init(cx)
}
unsafe fn init_irq(self: Arc<Self>) -> Result<(), Error> {
self.device.clone().init_irq()
}
fn display_name(&self) -> &str {
self.device.display_name()
}
}
+1
View File
@@ -8,6 +8,7 @@ pub mod bus;
pub mod clock;
pub mod device;
pub mod gpio;
pub mod i2c;
pub mod interrupt;
pub mod serial;
pub mod timer;
@@ -9,6 +9,10 @@
/// `.init_array`-based mechanism.
pub macro device_tree_driver(
compatible: [$($compatible:literal),+ $(,)?],
$(config: {
$($config_field:ident : $config_value:expr),+
$(,)?
},)?
driver: $driver:tt
) {
struct __DtDriver;
@@ -21,8 +25,15 @@ pub macro device_tree_driver(
static __REGISTER_FN: extern "C" fn() = __register_fn;
extern "C" fn __register_fn() {
let config = $crate::driver::DriverConfig {
$(
$($config_field: $config_value,)+
)?
..Default::default()
};
$crate::driver::register_driver(
&[$($compatible),+],
config,
&__DT_DRIVER
)
}
+1 -1
View File
@@ -15,7 +15,7 @@ pub mod util;
pub use controller::{map_interrupt, map_interrupt_at};
pub use macros::device_tree_driver;
pub use registry::{lookup_phandle, register_driver};
pub use registry::{DriverConfig, lookup_phandle, register_driver};
pub use syscon::DeviceTreeSyscon;
pub use traits::{
DeviceTreeClockController, DeviceTreeInterruptController, DeviceTreePinController,
+20 -1
View File
@@ -7,8 +7,22 @@ use crate::DeviceTree;
use super::{Driver, Node};
/// Device tree driver configuration
#[derive(Debug)]
pub struct DriverConfig {
/// If `false`, pinctrl setup for the node needs to be performed manually
pub auto_pinctrl: bool,
}
impl Default for DriverConfig {
fn default() -> Self {
Self { auto_pinctrl: true }
}
}
pub(crate) struct DriverRegistration {
pub(crate) compatible: &'static [&'static str],
pub(crate) config: DriverConfig,
pub(crate) imp: &'static dyn Driver,
}
@@ -25,9 +39,14 @@ impl DriverRegistration {
}
#[doc(hidden)]
pub fn register_driver(compatible: &'static [&'static str], driver: &'static dyn Driver) {
pub fn register_driver(
compatible: &'static [&'static str],
config: DriverConfig,
driver: &'static dyn Driver,
) {
DRIVERS.write().push(DriverRegistration {
compatible,
config,
imp: driver,
});
}
+34 -17
View File
@@ -10,6 +10,7 @@ use device_api::{
clock::{ClockController, ClockHandle, ResetHandle},
device::{Device, DeviceInitContext},
gpio::PinHandle,
i2c::I2CController,
interrupt::{ExternalInterruptController, IrqHandle, MessageInterruptController},
};
use fdt_rs::spec::Phandle;
@@ -61,6 +62,7 @@ pub struct Node {
pub(crate) reset_controller: OneTimeInit<Arc<dyn DeviceTreeResetController>>,
pub(crate) system_controller: OneTimeInit<Arc<dyn DeviceTreeSyscon>>,
pub(crate) pin_controller: OneTimeInit<Arc<dyn DeviceTreePinController>>,
pub(crate) i2c_controller: OneTimeInit<Arc<dyn I2CController>>,
}
pub(crate) struct ProbedDevice {
@@ -105,24 +107,26 @@ impl Node {
.as_str_list()
.find_map(|c| drivers.iter().find(|d| d.matches(c)));
// if libk::config::get().device_tree.log_missing && driver.is_none() {
// // FIXME don't spam virtio missing stuff
// if !name.is_some_and(|n| n.starts_with("virtio_mmio")) {
// for (i, compatible) in compatible.as_str_list().enumerate() {
// if i == 0 {
// log::warn!("No driver for {name:?} ({compatible:?})");
// } else {
// log::warn!(" also {compatible:?}");
// }
// }
// }
// }
if libk::config::get().device_tree.log_missing && driver.is_none() {
// FIXME don't spam virtio missing stuff
if !name.is_some_and(|n| n.starts_with("virtio_mmio")) {
for (i, compatible) in compatible.as_str_list().enumerate() {
if i == 0 {
log::warn!("No driver for {name:?} ({compatible:?})");
} else {
log::warn!(" also {compatible:?}");
}
}
}
}
let driver = driver?;
// Initialize default pinctrl before probing
node.setup_pins()
.inspect_err(|e| log::error!("{name:?}: pinctrl init error {e:?}"))
.ok()?;
if driver.config.auto_pinctrl {
node.setup_pins(0)
.inspect_err(|e| log::error!("{name:?}: pinctrl init error {e:?}"))
.ok()?;
}
let device = driver.imp.probe(node, cx);
@@ -198,6 +202,11 @@ impl Node {
self.interrupt_controller.init(intc);
}
/// Makes node an I²C controller node
pub fn make_i2c_controller(&self, i2c: Arc<dyn I2CController>) {
self.i2c_controller.init(i2c);
}
/// When called from an msi-controller driver, informs the node of its capability as an MSI
/// controller.
pub fn make_msi_controller(&self, intc: Arc<dyn MessageInterruptController>) {
@@ -451,6 +460,11 @@ impl Node {
self.pin_controller.try_get().cloned()
}
/// Attempts to get an I2C controller represented by this node, if any
pub fn as_i2c_controller(&self) -> Option<Arc<dyn I2CController>> {
self.i2c_controller.try_get().cloned()
}
/// Returns the `#address-cells` value of the node's parent bus
pub fn bus_address_cells(&self) -> usize {
self.bus_address_cells
@@ -527,9 +541,11 @@ impl Node {
tree::dump(self.dt_node.clone(), 0);
}
fn setup_pins(&self) -> Result<(), Error> {
/// Configure the `index`th pinctrl mode for the peripheral node
pub fn setup_pins(&self, index: usize) -> Result<(), Error> {
// TODO lookup pin state by name
let Some(pinctrl0) = self.prop_usize("pinctrl-0") else {
let field_name = alloc::format!("pinctrl-{index}");
let Some(pinctrl0) = self.prop_usize(&field_name) else {
return Ok(());
};
let pinctrl0 = lookup_phandle(pinctrl0 as Phandle, false).ok_or(Error::DoesNotExist)?;
@@ -596,6 +612,7 @@ fn unflatten_node(
reset_controller: OneTimeInit::new(),
system_controller: OneTimeInit::new(),
pin_controller: OneTimeInit::new(),
i2c_controller: OneTimeInit::new(),
});
if let Some(phandle) = phandle {
+89 -4
View File
@@ -1,11 +1,27 @@
use core::{any::Any, ops::Deref};
use core::{
any::Any,
ops::Deref,
task::{Context, Poll},
};
use alloc::{boxed::Box, sync::Arc};
use async_trait::async_trait;
use device_api::device::Device;
use yggdrasil_abi::error::Error;
use device_api::{
clock::Hertz,
device::Device,
i2c::{I2CAddress, I2CDevice},
};
use yggdrasil_abi::{
error::Error,
io::device::i2c::{self, I2CRequestVariant},
option::RequestValue,
process::ProcessId,
};
use crate::vfs::{CommonImpl, FileReadiness, NodeRef};
use crate::{
task::thread::Thread,
vfs::{CommonImpl, FileReadiness, NodeRef},
};
#[async_trait]
pub trait CharDevice: Device + FileReadiness {
@@ -25,6 +41,15 @@ pub trait CharDevice: Device + FileReadiness {
Err(Error::NotImplemented)
}
fn lock(&self, process: ProcessId) -> Result<(), Error> {
let _ = process;
Ok(())
}
fn release(&self, process: ProcessId) -> Result<(), Error> {
let _ = process;
Ok(())
}
fn is_readable(&self) -> bool {
true
}
@@ -65,3 +90,63 @@ impl Deref for CharDeviceFile {
self.0.as_ref()
}
}
// TODO timeouts and better access
#[async_trait]
impl CharDevice for I2CDevice {
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 pid = Thread::current().process_id();
self.check_lock(pid)?;
self.i2c_read(self.slave_address(), buffer)
}
fn write_nonblocking(&self, buffer: &[u8]) -> Result<usize, Error> {
let pid = Thread::current().process_id();
self.check_lock(pid)?;
self.i2c_write(self.slave_address(), buffer)
}
fn device_request(&self, option: u32, buffer: &mut [u8], len: usize) -> Result<usize, Error> {
if let Ok(req) = I2CRequestVariant::try_from(option) {
match req {
I2CRequestVariant::SetAddress => {
let address = i2c::SetAddress::load_request(&buffer[..len])?;
let address = I2CAddress::try_from(address)?;
self.set_slave_address(address);
Ok(0)
}
I2CRequestVariant::SetSpeed => {
let speed = i2c::SetSpeed::load_request(&buffer[..len])?;
let speed = Hertz::from(speed);
let actual = self.set_speed(speed)?.0 as u32;
let len = i2c::SetSpeed::store_response(&actual, buffer)?;
Ok(len)
}
I2CRequestVariant::GetMasterInfo => {
let info = self.capabilities();
let len = i2c::GetMasterInfo::store_response(&info, buffer)?;
Ok(len)
}
}
} else {
Err(Error::InvalidArgument)
}
}
fn lock(&self, process: ProcessId) -> Result<(), Error> {
I2CDevice::lock(self, process)
}
fn release(&self, process: ProcessId) -> Result<(), Error> {
I2CDevice::release(self, process)
}
}
impl FileReadiness for I2CDevice {
fn poll_read(&self, cx: &mut Context<'_>) -> Poll<Result<(), Error>> {
let _ = cx;
todo!()
}
}
+24
View File
@@ -4,6 +4,7 @@ use core::{
};
use alloc::{collections::BTreeMap, format, sync::Arc, vec::Vec};
use device_api::i2c::{I2CController, I2CDevice};
use libk_util::{OneTimeInit, sync::spin_rwlock::IrqSafeRwLock};
use yggdrasil_abi::{error::Error, io::FileMode};
@@ -46,16 +47,23 @@ pub struct SerialTerminalRegistry {
registry: GenericRegistry<Arc<dyn CharDevice>>,
}
// Manages devices: i2c<N>, i2c<N>-<M>
pub struct I2CDeviceRegistry {
registry: GenericRegistry<Arc<I2CDevice>>,
}
pub struct DeviceRegistry {
pub display: DisplayDeviceRegistry,
pub terminal: TerminalRegistry,
pub serial_terminal: SerialTerminalRegistry,
pub i2c: I2CDeviceRegistry,
}
pub static DEVICE_REGISTRY: DeviceRegistry = DeviceRegistry {
display: DisplayDeviceRegistry::new(),
terminal: TerminalRegistry::new(),
serial_terminal: SerialTerminalRegistry::new(),
i2c: I2CDeviceRegistry::new(),
};
impl TerminalRegistry {
@@ -117,6 +125,22 @@ impl DisplayDeviceRegistry {
}
}
impl I2CDeviceRegistry {
pub const fn new() -> Self {
Self {
registry: GenericRegistry::new(),
}
}
pub fn register_bus(&self, device: Arc<dyn I2CController>) -> Result<u32, Error> {
let wrapper = Arc::new(I2CDevice::from(device));
let id = self.registry.register(wrapper.clone())?;
let name = format!("i2c{id}");
devfs::add_named_char_device(wrapper.clone(), &name, FileMode::new(0o600))?;
Ok(id)
}
}
impl<T> GenericRegistry<T> {
pub const fn new() -> Self {
Self {
+9 -1
View File
@@ -1,5 +1,5 @@
use libk_util::sync::IrqSafeSpinlock;
use yggdrasil_abi::{error::Error, io::SeekFrom};
use yggdrasil_abi::{error::Error, io::SeekFrom, process::ProcessId};
use crate::{
device::{block::BlockDeviceFile, char::CharDeviceFile},
@@ -20,6 +20,7 @@ pub struct CharFile {
pub(super) node: NodeRef,
pub(super) read: bool,
pub(super) write: bool,
pub(super) pid: ProcessId,
}
impl BlockFile {
@@ -103,3 +104,10 @@ impl CharFile {
self.device.0.is_terminal()
}
}
impl Drop for CharFile {
fn drop(&mut self) {
// TODO doesn't work with fork
self.device.release(self.pid).ok();
}
}
+6 -1
View File
@@ -33,7 +33,7 @@ use yggdrasil_abi::{
use crate::{
device::{block::BlockDeviceFile, char::CharDeviceFile},
task::process::Process,
task::{process::Process, thread::Thread},
vfs::{
FdPoll, FileReadiness, Node, SharedMemory, TimerFile,
node::NodeRef,
@@ -254,6 +254,10 @@ impl File {
node: NodeRef,
opts: OpenOptions,
) -> Result<Arc<Self>, Error> {
let pid = Thread::current().process_id();
device.lock(pid)?;
let read = opts.contains(OpenOptions::READ);
let write = opts.contains(OpenOptions::WRITE);
@@ -269,6 +273,7 @@ impl File {
node,
read,
write,
pid,
})))
}
+112
View File
@@ -0,0 +1,112 @@
#![allow(missing_docs)]
use abi::{
error::Error,
io::{FileMode, device::i2c::I2CMasterInfo},
};
use alloc::{string::String, sync::Arc};
use device_api::{
clock::Hertz,
device::{Device, DeviceInitContext},
i2c::{I2CAddress, I2CController, I2CDevice},
};
use device_tree::driver::{Node, ProbeContext, device_tree_driver, lookup_phandle};
use libk::fs::devfs;
pub struct I2CMuxChild {
name: String,
channel: u32,
parent: Arc<dyn I2CController>,
}
pub struct I2CMux {
name: &'static str,
}
impl I2CMux {
pub fn from_fdt(node: &Arc<Node>, _context: &mut ProbeContext) -> Option<Arc<dyn Device>> {
let i2c_parent = node.prop_usize("i2c-parent")? as u32;
let i2c_parent = lookup_phandle(i2c_parent, true)?;
let i2c_parent = i2c_parent.as_i2c_controller()?;
let name = node.name().unwrap_or("i2c-mux-pinctrl");
let bus_index = i2c_parent.bus_number();
for (index, child) in node.children().enumerate() {
node.setup_pins(index).unwrap();
let channel_index = child.prop_usize("reg")? as u32;
let name = alloc::format!("{name}@{channel_index}");
let channel: Arc<dyn I2CController> = Arc::new(I2CMuxChild {
name,
channel: channel_index,
parent: i2c_parent.clone(),
});
let device = Arc::new(I2CDevice::from(channel));
let fs_name = alloc::format!("i2c{bus_index}-{channel_index}");
devfs::add_named_char_device(device, fs_name, FileMode::new(0o600)).ok();
}
Some(Arc::new(I2CMux { name }))
}
}
impl Device for I2CMux {
unsafe fn init(self: Arc<Self>, _cx: DeviceInitContext) -> Result<(), Error> {
Ok(())
}
fn display_name(&self) -> &str {
&self.name
}
}
impl Device for I2CMuxChild {
unsafe fn init(self: Arc<Self>, _cx: DeviceInitContext) -> Result<(), Error> {
unreachable!()
}
fn display_name(&self) -> &str {
&self.name
}
}
impl I2CController for I2CMuxChild {
fn bus_number(&self) -> u32 {
self.parent.bus_number()
}
fn child_number(&self) -> Option<u32> {
Some(self.channel)
}
fn set_speed(&self, speed: Hertz) -> Result<Hertz, Error> {
self.parent.set_speed(speed)
}
fn capabilities(&self) -> I2CMasterInfo {
self.parent.capabilities()
}
fn i2c_write(&self, address: I2CAddress, buffer: &[u8]) -> Result<usize, Error> {
// TODO channel select?
self.parent.i2c_write(address, buffer)
}
fn i2c_read(&self, address: I2CAddress, buffer: &mut [u8]) -> Result<usize, Error> {
// TODO channel select?
self.parent.i2c_read(address, buffer)
}
}
device_tree_driver! {
compatible: ["i2c-mux-pinctrl"],
config: {
auto_pinctrl: false,
},
driver: {
fn probe(&self, node: &Arc<Node>, context: &mut ProbeContext) -> Option<Arc<dyn Device>> {
I2CMux::from_fdt(node, context)
}
}
}
+1
View File
@@ -6,6 +6,7 @@ use libk_util::OneTimeInit;
pub mod bus;
pub mod clock;
pub mod display;
pub mod i2c;
pub mod power;
// pub mod timer;
+28
View File
@@ -39,6 +39,34 @@ impl_primitive_serde!(
usize: [read_usize, write_usize]
);
impl<T> Serialize for *const T {
fn serialize<S: Serializer>(&self, serializer: &mut S) -> Result<(), S::Error> {
serializer.write_usize(self.addr())
}
}
impl<T> Serialize for *mut T {
fn serialize<S: Serializer>(&self, serializer: &mut S) -> Result<(), S::Error> {
serializer.write_usize(self.addr())
}
}
impl<'de, T: 'de> Deserialize<'de> for *const T {
fn deserialize<D: Deserializer<'de>>(deserializer: &mut D) -> Result<Self, D::Error> {
deserializer
.read_usize()
.map(core::ptr::with_exposed_provenance)
}
}
impl<'de, T: 'de> Deserialize<'de> for *mut T {
fn deserialize<D: Deserializer<'de>>(deserializer: &mut D) -> Result<Self, D::Error> {
deserializer
.read_usize()
.map(core::ptr::with_exposed_provenance_mut)
}
}
impl Serialize for () {
fn serialize<S: Serializer>(&self, serializer: &mut S) -> Result<(), S::Error> {
let _ = serializer;
+84
View File
@@ -55,6 +55,90 @@ request_group!(
}
);
/// I²C device requests
pub mod i2c {
/// I²C master device information
#[derive(Debug, Clone, Copy)]
#[repr(C)]
pub struct I2CMasterInfo {
/// Maximum clock rate supported by the controller
pub max_speed_hz: u32,
/// `true` if the controller supports 10-bit addressing mode
pub supports_10bit_addresses: bool,
}
request_group!(
#[doc = "I²C device requests"]
pub enum I2CRequestVariant<'de> {
#[doc = "Selects the address on the I²C bus"]
0x2000: SetAddress(u16, ()),
#[doc = "Configures device speed (in Hertz)"]
0x2001: SetSpeed(u32, u32),
#[doc = "Queries I²C master capabilities"]
0x2010: GetMasterInfo((), I2CMasterInfo),
}
);
abi_serde::impl_struct_serde!(I2CMasterInfo: [
max_speed_hz, supports_10bit_addresses
]);
}
/// SPI device requests
pub mod spi {
#![allow(missing_docs)]
use crate::bitflags;
/// Represents a single SPI transfer
#[derive(Clone, Copy, Debug)]
#[repr(C)]
pub struct SpiTransfer {
/// Tx buffer base pointer
pub tx_buffer: *const u8,
/// Rx buffer base pointer
pub rx_buffer: *mut u8,
/// Transfer size
pub len: usize,
/// SPI clock rate
pub speed_hz: Option<u32>,
/// Bits per SPI word
pub bits_per_word: u8,
}
bitflags! {
#[doc = "SPI supported bits-per-word settings"]
pub struct SpiSupportedBitsPerWord: u32 {
#[doc = "8-bit words support"]
const BITS_8: bit 0;
}
}
/// SPI controller capabilities
pub struct SpiCapabilities {
/// Maximum clock rate supported
pub max_speed_hz: u32,
/// Supports SPI master mode
pub supports_master_mode: bool,
/// Supports SPI slave mode
pub supports_slave_mode: bool,
/// Mask
pub supported_bits_per_word: u32,
}
request_group!(
#[doc = "SPI device requests"]
pub enum SpiRequestVariant<'de> {
#[doc = "Perform a SPI transfer"]
0x2000: Transfer(SpiTransfer, usize)
}
);
abi_serde::impl_struct_serde!(SpiTransfer: [
tx_buffer, rx_buffer, len, speed_hz, bits_per_word
]);
}
abi_serde::impl_struct_serde!(Framebuffer: [
width, height, stride, size
]);
+48
View File
@@ -0,0 +1,48 @@
use std::{
io::{self, Read, Write},
path::Path,
};
use crate::sys::{I2CMaster as SysI2CMaster, i2c::I2CMasterImpl};
pub use crate::sys::I2CMasterConfig;
pub struct I2CMaster(I2CMasterImpl);
impl Read for I2CMaster {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
self.0.read(buf)
}
}
impl Write for I2CMaster {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.0.write(buf)
}
fn flush(&mut self) -> io::Result<()> {
self.0.flush()
}
}
impl I2CMaster {
pub fn open<P: AsRef<Path>>(device: P, config: &I2CMasterConfig) -> io::Result<Self> {
I2CMasterImpl::open(device, config).map(Self)
}
pub fn set_slave_address(&mut self, address: u16) -> io::Result<()> {
self.0.set_slave_address(address)
}
pub fn set_speed_hz(&mut self, speed_hz: u32) -> io::Result<()> {
self.0.set_speed_hz(speed_hz)
}
pub fn smbus_read(&mut self, reg: u8, data: &mut [u8]) -> io::Result<usize> {
self.0.smbus_read(reg, data)
}
pub fn smbus_write(&mut self, reg: u8, data: &[u8]) -> io::Result<usize> {
self.0.smbus_write(reg, data)
}
}
+1
View File
@@ -5,6 +5,7 @@
pub(crate) mod sys;
pub mod fs;
pub mod i2c;
pub mod io;
pub mod mem;
pub mod net;
+14
View File
@@ -64,6 +64,20 @@ pub(crate) trait SerialPort: Read + Write + AsRawFd + IntoRawFd + Sized {
fn open<P: AsRef<Path>>(device: P, options: &SerialPortSettings) -> io::Result<Self>;
}
#[derive(Debug, Default)]
pub struct I2CMasterConfig {
pub slave_address: Option<u16>,
pub speed_hz: Option<u32>,
}
pub(crate) trait I2CMaster: Read + Write + AsRawFd + Sized {
fn open<P: AsRef<Path>>(device: P, config: &I2CMasterConfig) -> io::Result<Self>;
fn set_slave_address(&mut self, address: u16) -> io::Result<()>;
fn set_speed_hz(&mut self, speed_hz: u32) -> io::Result<()>;
fn smbus_read(&mut self, reg: u8, data: &mut [u8]) -> io::Result<usize>;
fn smbus_write(&mut self, reg: u8, data: &[u8]) -> io::Result<usize>;
}
pub trait TerminalOptions: Copy {
fn normal() -> Self;
fn raw() -> Self;
@@ -0,0 +1,72 @@
use std::{
fs::{File, OpenOptions},
io::{self, Read, Write},
os::fd::{AsRawFd, RawFd},
path::Path,
};
use runtime::{abi::io::device::i2c, rt::io::device::device_request};
use crate::sys::{I2CMaster, I2CMasterConfig};
pub struct I2CMasterImpl {
file: File,
}
impl Read for I2CMasterImpl {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
self.file.read(buf)
}
}
impl Write for I2CMasterImpl {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
self.file.write(buf)
}
fn flush(&mut self) -> io::Result<()> {
self.file.flush()
}
}
impl I2CMaster for I2CMasterImpl {
fn open<P: AsRef<Path>>(device: P, config: &I2CMasterConfig) -> io::Result<Self> {
let file = OpenOptions::new().read(true).write(true).open(device)?;
let mut this = Self { file };
if let Some(address) = config.slave_address {
this.set_slave_address(address)?;
}
if let Some(speed_hz) = config.speed_hz {
this.set_speed_hz(speed_hz)?;
}
Ok(this)
}
fn set_slave_address(&mut self, address: u16) -> io::Result<()> {
let mut buffer = [0; 4];
device_request::<i2c::SetAddress>(self.file.as_raw_fd(), &mut buffer, &address)?;
Ok(())
}
fn set_speed_hz(&mut self, speed_hz: u32) -> io::Result<()> {
let mut buffer = [0; 4];
device_request::<i2c::SetSpeed>(self.file.as_raw_fd(), &mut buffer, &speed_hz)?;
Ok(())
}
fn smbus_read(&mut self, reg: u8, data: &mut [u8]) -> io::Result<usize> {
self.write_all(&[reg])?;
self.read(data)
}
fn smbus_write(&mut self, reg: u8, data: &[u8]) -> io::Result<usize> {
self.write_all(&[reg])?;
self.write(data)
}
}
impl AsRawFd for I2CMasterImpl {
fn as_raw_fd(&self) -> RawFd {
self.file.as_raw_fd()
}
}
@@ -1,4 +1,5 @@
pub mod fs;
pub mod i2c;
pub mod mem;
pub mod path;
pub mod pid;
+80 -45
View File
@@ -1,62 +1,97 @@
#![feature(let_chains)]
use std::collections::{HashSet, VecDeque};
use stuff::{
autocomplete,
readline::{Buffer, NonEmptyVec, Readline, ReadlineProvider},
use std::{
io::{self, Write, stdout},
path::{Path, PathBuf},
process::ExitCode,
thread,
time::Duration,
};
#[derive(Default)]
struct P {
history: VecDeque<String>,
use clap::Parser;
use cross::i2c::{I2CMaster, I2CMasterConfig};
#[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,
}
impl ReadlineProvider for P {
fn completions(&mut self, buffer: &Buffer) -> Option<NonEmptyVec<String>> {
let word_before_cursor = buffer.word_before_cursor();
let is_first = buffer.is_first_word();
struct M41T80 {
master: I2CMaster,
}
let mut entries = HashSet::new();
if let Some(word) = word_before_cursor
&& !is_first
{
autocomplete::complete_from_cwd(&mut entries, word, &Default::default());
} else {
let prefix = word_before_cursor.unwrap_or("");
autocomplete::complete_binary_from_env(&mut entries, prefix);
}
#[derive(Debug, PartialEq)]
struct Time {
year: u16,
mon: u8,
day: u8,
let mut entries = NonEmptyVec::try_from_iter(entries.drain()).ok()?;
entries.sort();
hour: u8,
min: u8,
sec: u8,
millis: u16,
}
Some(entries)
impl M41T80 {
fn open<P: AsRef<Path>>(path: P, config: &I2CMasterConfig) -> io::Result<Self> {
let master = I2CMaster::open(path, config)?;
Ok(Self { master })
}
fn push_history(&mut self, entry: &str) {
self.history.push_back(entry.into());
}
fn read_time(&mut self) -> io::Result<Time> {
let mut buffer = [0; 9];
self.master.smbus_read(0x00, &mut buffer)?;
fn lookup_history(&mut self, depth: usize) -> Option<String> {
if depth >= self.history.len() {
return None;
}
let index = self.history.len() - depth - 1;
Some(self.history[index].clone())
let millis = ((buffer[0] >> 4) as u16 * 100) | ((buffer[0] & 0xF) as u16 * 10);
let sec = ((buffer[1] >> 4) & 0x7) * 10 + (buffer[1] & 0xF);
let min = ((buffer[2] >> 4) & 0x7) * 10 + (buffer[2] & 0xF);
let hour = ((buffer[3] >> 4) & 0x3) * 10 + (buffer[3] & 0xF);
let day = ((buffer[5] >> 4) & 0x3) * 10 + (buffer[5] & 0xF);
let mon = ((buffer[6] >> 4) & 0x1) * 10 + (buffer[6] & 0xF);
let year = ((buffer[7] >> 4) as u16 * 10) + ((buffer[7] & 0xF) as u16) + 2000;
Ok(Time {
millis,
sec,
min,
hour,
year,
day,
mon,
})
}
}
fn main() {
logsink::setup_logging(true);
let mut readline = Readline::new(P::default()).unwrap();
fn run(args: Args) -> io::Result<()> {
// let device = "/dev/i2c0";
let i2c_config = I2CMasterConfig {
slave_address: Some(args.address as _),
speed_hz: None,
};
let mut m41t80 = M41T80::open(args.device, &i2c_config)?;
loop {
let Some(line) = readline.readline().unwrap() else {
break;
};
println!("{line:?}");
if line == "exit" {
break;
let time = m41t80.read_time()?;
print!(
"\r{:04}/{:02}/{:02} {:02}:{:02}:{:02}",
time.year, time.mon, time.day, time.hour, time.min, time.sec,
);
stdout().flush().ok();
thread::sleep(Duration::from_millis(100));
}
}
fn main() -> ExitCode {
logsink::setup_logging(true);
let args = Args::parse();
match run(args) {
Ok(()) => ExitCode::SUCCESS,
Err(error) => {
eprintln!("{error}");
ExitCode::FAILURE
}
}
}