i2c: improve i2c architecture, add sifive i2c driver
This commit is contained in:
Generated
+1
@@ -2727,6 +2727,7 @@ dependencies = [
|
||||
name = "ygg_driver_bsp_sifive"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"bytemuck",
|
||||
"device-api",
|
||||
"device-tree",
|
||||
|
||||
@@ -235,6 +235,31 @@
|
||||
status = "disabled";
|
||||
};
|
||||
|
||||
i2c0: i2c@10030000 {
|
||||
compatible = "sifive,fu740-c000-i2c", "sifive,i2c0";
|
||||
reg = <0x00 0x10030000 0x00 0x1000>;
|
||||
interrupt-parent = <&plic>;
|
||||
interrupts = <52>;
|
||||
clocks = <&prci CLK_PCLK>;
|
||||
reg-shift = <2>;
|
||||
reg-io-width = <1>;
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
status = "okay";
|
||||
};
|
||||
|
||||
i2c1: i2c@10031000 {
|
||||
compatible = "sifive,fu740-c000-i2c", "sifive,i2c0";
|
||||
reg = <0x00 0x10031000 0x00 0x1000>;
|
||||
interrupt-parent = <&plic>;
|
||||
interrupts = <53>;
|
||||
clocks = <&prci CLK_PCLK>;
|
||||
reg-shift = <2>;
|
||||
reg-io-width = <1>;
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
status = "disabled";
|
||||
};
|
||||
};
|
||||
|
||||
memory@80000000 {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use device_api::{ResetDevice, device::Device};
|
||||
use yggdrasil_abi::{error::Error, primitive_enum};
|
||||
|
||||
const EXT_HSM: u64 = 0x48534D;
|
||||
@@ -6,6 +7,7 @@ const EXT_DBCN: u64 = 0x4442434E;
|
||||
const EXT_SPI: u64 = 0x735049;
|
||||
const EXT_SYSTEM_SHUTDOWN: u64 = 0x53525354;
|
||||
const EXT_SYSTEM_SHUTDOWN_LEGACY: u64 = 0x08;
|
||||
const EXT_SYSTEM_RESET: u64 = 0x53525354;
|
||||
|
||||
primitive_enum! {
|
||||
pub enum Status: i64 {
|
||||
@@ -52,6 +54,20 @@ impl From<i64> for SbiError {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SbiResetMethod;
|
||||
|
||||
impl Device for SbiResetMethod {
|
||||
fn display_name(&self) -> &str {
|
||||
"SBI reset"
|
||||
}
|
||||
}
|
||||
|
||||
impl ResetDevice for SbiResetMethod {
|
||||
unsafe fn reset(&self) -> ! {
|
||||
sbi_system_reset()
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
#[inline(always)]
|
||||
unsafe fn sbi_do_call(
|
||||
@@ -107,6 +123,11 @@ pub fn sbi_set_timer(next_event: u64) {
|
||||
unsafe { sbi_do_call(EXT_TIME, 0x00, next_event, 0, 0, 0, 0, 0) }.ok();
|
||||
}
|
||||
|
||||
pub fn sbi_system_reset() -> ! {
|
||||
unsafe { sbi_do_call(EXT_SYSTEM_RESET, 0x00, 0x01, 0x00, 0, 0, 0, 0) }.ok();
|
||||
unreachable!()
|
||||
}
|
||||
|
||||
pub fn sbi_system_shutdown() -> ! {
|
||||
unsafe { sbi_do_call(EXT_SYSTEM_SHUTDOWN, 0x00, 0, 0, 0, 0, 0, 0) }.ok();
|
||||
unsafe { sbi_do_call(EXT_SYSTEM_SHUTDOWN_LEGACY, 0x00, 0, 0, 0, 0, 0, 0) }.ok();
|
||||
|
||||
@@ -1,15 +1,19 @@
|
||||
use alloc::sync::Arc;
|
||||
use alloc::{boxed::Box, sync::Arc};
|
||||
use async_trait::async_trait;
|
||||
use device_api::{
|
||||
clock::{ClockHandle, Hertz},
|
||||
device::{Device, DeviceInitContext},
|
||||
i2c::{I2CAddress, I2CController},
|
||||
i2c::{I2CAddress, I2CController, I2CMessage, I2CTransfer},
|
||||
interrupt::{InterruptHandler, IrqHandle, IrqVector},
|
||||
};
|
||||
use device_tree::driver::{Node, ProbeContext, device_tree_driver};
|
||||
use futures_util::task::AtomicWaker;
|
||||
use libk::{device::manager::DEVICE_REGISTRY, error::Error};
|
||||
use libk_mm::device::DeviceMemoryIo;
|
||||
use libk_util::{OneTimeInit, sync::spin_rwlock::IrqSafeRwLock};
|
||||
use libk_util::{OneTimeInit, event::BitmapEvent, sync::spin_rwlock::IrqSafeRwLock};
|
||||
use tock_registers::{
|
||||
LocalRegisterCopy,
|
||||
fields::FieldValue,
|
||||
interfaces::{ReadWriteable, Readable, Writeable},
|
||||
register_bitfields, register_structs,
|
||||
registers::ReadWrite,
|
||||
@@ -56,6 +60,10 @@ register_structs! {
|
||||
}
|
||||
}
|
||||
|
||||
struct I2CState {
|
||||
status: BitmapEvent<AtomicWaker>,
|
||||
}
|
||||
|
||||
struct I2C {
|
||||
name: &'static str,
|
||||
clock_frequency: Option<Hertz>,
|
||||
@@ -63,87 +71,22 @@ struct I2C {
|
||||
clock: Option<ClockHandle>,
|
||||
regs: IrqSafeRwLock<DeviceMemoryIo<'static, Regs>>,
|
||||
index: OneTimeInit<u32>,
|
||||
state: I2CState,
|
||||
}
|
||||
|
||||
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();
|
||||
impl I2CState {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
status: BitmapEvent::new(AtomicWaker::new()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
// All of these are thread-unsafe, but I2C subsystem locks the peripheral by process
|
||||
impl I2C {
|
||||
async fn wait_for_event(&self) -> LocalRegisterCopy<u32, S::Register> {
|
||||
let event = self.state.status.wait().await;
|
||||
LocalRegisterCopy::new(event as u32)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -158,16 +101,13 @@ impl Device for I2C {
|
||||
|
||||
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);
|
||||
self.irq.register(self.clone())?;
|
||||
self.irq.enable()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -178,10 +118,26 @@ impl Device for I2C {
|
||||
|
||||
impl InterruptHandler for I2C {
|
||||
fn handle_irq(self: Arc<Self>, _vector: IrqVector) -> bool {
|
||||
todo!()
|
||||
let regs = self.regs.write();
|
||||
let event = regs.S.extract();
|
||||
// log::info!("I2C irq {:#x}", event.get());
|
||||
let mut clear = FieldValue::none();
|
||||
if event.matches_all(S::RXR::SET) {
|
||||
clear += C::INTR::CLEAR;
|
||||
}
|
||||
if event.matches_all(S::TXW::SET) {
|
||||
clear += C::INTT::CLEAR;
|
||||
}
|
||||
if event.matches_all(S::DONE::SET) {
|
||||
clear += C::INTD::CLEAR;
|
||||
}
|
||||
regs.C.modify(clear);
|
||||
self.state.status.signal(event.get() as u64);
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl I2CController for I2C {
|
||||
fn bus_number(&self) -> u32 {
|
||||
*self.index.get()
|
||||
@@ -217,47 +173,98 @@ impl I2CController for I2C {
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
async fn i2c_transfer(
|
||||
&self,
|
||||
address: I2CAddress,
|
||||
transfer: &mut I2CTransfer,
|
||||
) -> Result<usize, Error> {
|
||||
// TODO validate transfer
|
||||
let address = address.as_8_bit().ok_or(Error::NotImplemented)?;
|
||||
let repeat_start = transfer.repeat_start;
|
||||
let mut nbytes = 0;
|
||||
let mut error = None;
|
||||
for (i, message) in transfer.messages.iter_mut().enumerate() {
|
||||
if i == 0 || repeat_start {
|
||||
let (read, len) = match message {
|
||||
I2CMessage::Read(buffer) => (true, buffer.len()),
|
||||
I2CMessage::Write(buffer) => (false, buffer.len()),
|
||||
};
|
||||
// log::info!("{}: START {:#x} read={}", self.name, address, read);
|
||||
let read = if read { C::READ::SET } else { C::READ::CLEAR };
|
||||
let regs = self.regs.write();
|
||||
regs.S.write(S::ERR::SET + S::DONE::SET);
|
||||
regs.DLEN.set(len as u32);
|
||||
regs.C.modify(C::ST::CLEAR);
|
||||
regs.A.set(address as u32);
|
||||
regs.C.modify(
|
||||
read + C::ST::SET + C::I2CEN::SET + C::INTD::SET + C::INTR::SET + C::INTT::SET,
|
||||
);
|
||||
}
|
||||
}
|
||||
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 {
|
||||
let mut pos = 0;
|
||||
let status = loop {
|
||||
let s = self.wait_for_event().await;
|
||||
let regs = self.regs.write();
|
||||
if s.matches_all(S::ERR::SET) || s.matches_all(S::DONE::SET) {
|
||||
break s;
|
||||
}
|
||||
match message {
|
||||
I2CMessage::Write(buffer) => {
|
||||
if s.matches_all(S::TXW::SET) {
|
||||
while regs.S.matches_all(S::TXD::SET) && pos < buffer.len() {
|
||||
let byte = buffer[pos];
|
||||
// log::info!("{}: tx {:#x}", self.name, byte);
|
||||
regs.FIFO.set(byte as u32);
|
||||
pos += 1;
|
||||
nbytes += 1;
|
||||
}
|
||||
}
|
||||
regs.C.modify(C::INTT::SET);
|
||||
}
|
||||
I2CMessage::Read(buffer) => {
|
||||
if s.matches_all(S::RXR::SET) {
|
||||
while regs.S.matches_all(S::RXD::SET) && pos < buffer.len() {
|
||||
let byte = regs.FIFO.get() as u8;
|
||||
// log::info!("{}: rx {:#x}", self.name, byte);
|
||||
buffer[pos] = byte;
|
||||
pos += 1;
|
||||
nbytes += 1;
|
||||
}
|
||||
}
|
||||
regs.C.modify(C::INTR::SET);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Finalize message
|
||||
let regs = self.regs.write();
|
||||
if status.matches_all(S::ERR::SET) {
|
||||
regs.S.write(S::DONE::SET + S::ERR::SET);
|
||||
error = Some(Error::HostUnreachable);
|
||||
break;
|
||||
} else if status.matches_all(S::DONE::SET) {
|
||||
regs.S.write(S::DONE::SET);
|
||||
} else {
|
||||
log::error!("{}: transfer finished without DONE or ERR set", self.name);
|
||||
log::error!("{}: S = {:#x}", self.name, status.get());
|
||||
todo!();
|
||||
}
|
||||
}
|
||||
regs.finish_transfer(self.name)?;
|
||||
Ok(bytes_read)
|
||||
|
||||
// Finish transfer
|
||||
// log::info!(
|
||||
// "{}: finish xfer, error={:?}, nbytes={}",
|
||||
// self.name,
|
||||
// error,
|
||||
// nbytes
|
||||
// );
|
||||
let regs = self.regs.write();
|
||||
regs.C.set(0);
|
||||
|
||||
match error {
|
||||
Some(error) => Err(error),
|
||||
None => Ok(nbytes),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -279,7 +286,8 @@ device_tree_driver! {
|
||||
irq,
|
||||
clock,
|
||||
index: OneTimeInit::new(),
|
||||
regs: IrqSafeRwLock::new(regs)
|
||||
regs: IrqSafeRwLock::new(regs),
|
||||
state: I2CState::new()
|
||||
});
|
||||
|
||||
node.make_i2c_controller(i2c.clone());
|
||||
|
||||
@@ -17,3 +17,4 @@ tock-registers.workspace = true
|
||||
log.workspace = true
|
||||
bytemuck.workspace = true
|
||||
futures-util.workspace = true
|
||||
async-trait.workspace = true
|
||||
|
||||
@@ -0,0 +1,523 @@
|
||||
use alloc::{boxed::Box, sync::Arc};
|
||||
use async_trait::async_trait;
|
||||
use device_api::{
|
||||
clock::{ClockHandle, Hertz, IntoHertz},
|
||||
device::{Device, DeviceInitContext},
|
||||
i2c::{I2CAddress, I2CController, I2CMessage, I2CTransfer},
|
||||
interrupt::{InterruptHandler, IrqHandle, IrqVector},
|
||||
};
|
||||
use device_tree::driver::{Node, ProbeContext, device_tree_driver};
|
||||
use futures_util::task::AtomicWaker;
|
||||
use libk::{block, device::manager::DEVICE_REGISTRY, error::Error};
|
||||
use libk_mm::device::DeviceMemoryIo;
|
||||
use libk_util::{OneTimeInit, event::BitmapEvent, sync::IrqSafeSpinlock};
|
||||
use tock_registers::{
|
||||
LocalRegisterCopy,
|
||||
interfaces::{ReadWriteable, Readable, Writeable},
|
||||
register_bitfields, register_structs,
|
||||
registers::{Aliased, ReadWrite},
|
||||
};
|
||||
use yggdrasil_abi::io::device::i2c::I2CMasterInfo;
|
||||
|
||||
const CMD_START: u32 = 0x91;
|
||||
const CMD_STOP: u32 = 0x41;
|
||||
const CMD_WRITE: u32 = 0x11;
|
||||
const CMD_READ_ACK: u32 = 0x21;
|
||||
const CMD_READ_NACK: u32 = 0x29;
|
||||
const CMD_IACK: u32 = 0x01;
|
||||
|
||||
register_bitfields! {
|
||||
u32,
|
||||
CTR [
|
||||
EN OFFSET(7) NUMBITS(1) [],
|
||||
IEN OFFSET(6) NUMBITS(1) [],
|
||||
],
|
||||
SR [
|
||||
RXACK OFFSET(7) NUMBITS(1) [],
|
||||
BUSY OFFSET(6) NUMBITS(1) [],
|
||||
AL OFFSET(5) NUMBITS(1) [],
|
||||
TIP OFFSET(1) NUMBITS(1) [],
|
||||
IF OFFSET(0) NUMBITS(1) [],
|
||||
],
|
||||
}
|
||||
|
||||
register_structs! {
|
||||
#[allow(non_snake_case)]
|
||||
Regs {
|
||||
(0x000 => PRER_LO: ReadWrite<u32>),
|
||||
(0x004 => PRER_HI: ReadWrite<u32>),
|
||||
(0x008 => CTR: ReadWrite<u32, CTR::Register>),
|
||||
(0x00C => TXR_RXR: ReadWrite<u32>),
|
||||
(0x010 => CR_SR: Aliased<u32, SR::Register, ()>),
|
||||
(0x014 => _0),
|
||||
(0x020 => @END),
|
||||
}
|
||||
}
|
||||
|
||||
struct AbortGuard<'a> {
|
||||
i2c: &'a I2C,
|
||||
abort: bool,
|
||||
}
|
||||
|
||||
impl<'a> AbortGuard<'a> {
|
||||
pub fn new(i2c: &'a I2C) -> Self {
|
||||
Self { i2c, abort: true }
|
||||
}
|
||||
|
||||
pub fn consume(mut self) {
|
||||
self.abort = false;
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for AbortGuard<'_> {
|
||||
fn drop(&mut self) {
|
||||
if self.abort {
|
||||
block!(self.i2c.finish_transfer(Err(Error::Interrupted)).await).ok();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct I2C {
|
||||
name: &'static str,
|
||||
clock: ClockHandle,
|
||||
irq: IrqHandle,
|
||||
regs: IrqSafeSpinlock<DeviceMemoryIo<'static, Regs>>,
|
||||
index: OneTimeInit<u32>,
|
||||
sr: BitmapEvent<AtomicWaker>,
|
||||
}
|
||||
|
||||
impl Regs {
|
||||
fn set_clock_rate(&self, input_rate: Hertz, clock_rate: Hertz) -> Result<Hertz, Error> {
|
||||
let divider = Hertz::divider(input_rate / 5, clock_rate).ok_or(Error::InvalidArgument)?;
|
||||
if divider == 0 || divider > 0xFFFF {
|
||||
return Err(Error::InvalidArgument);
|
||||
}
|
||||
log::info!("input_rate = {input_rate}, desired = {clock_rate}, divider = {divider}");
|
||||
let divider = divider - 1;
|
||||
self.PRER_LO.set(divider & 0xFF);
|
||||
self.PRER_HI.set(divider >> 8);
|
||||
let real_rate = input_rate / (5 * (divider + 1));
|
||||
Ok(real_rate)
|
||||
}
|
||||
}
|
||||
|
||||
impl I2C {
|
||||
async fn wait_for_event(&self) -> LocalRegisterCopy<u32, SR::Register> {
|
||||
let value = self.sr.wait().await;
|
||||
LocalRegisterCopy::new(value as u32)
|
||||
}
|
||||
|
||||
async fn finish_transfer(&self, status: Result<usize, Error>) -> Result<usize, Error> {
|
||||
if status == Err(Error::Interrupted) {
|
||||
log::warn!("{}: transfer aborted", self.name);
|
||||
|
||||
let regs = self.regs.lock();
|
||||
let stopped = {
|
||||
if regs.CR_SR.matches_all(SR::TIP::CLEAR) {
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
};
|
||||
|
||||
if !stopped {
|
||||
regs.CTR.modify(CTR::IEN::SET);
|
||||
drop(regs);
|
||||
|
||||
loop {
|
||||
let sr = self.wait_for_event().await;
|
||||
if sr.matches_all(SR::TIP::CLEAR) {
|
||||
break;
|
||||
}
|
||||
let regs = self.regs.lock();
|
||||
regs.CTR.modify(CTR::IEN::SET); // Reenable IRQs
|
||||
}
|
||||
}
|
||||
}
|
||||
// Generate stop condition
|
||||
{
|
||||
let regs = self.regs.lock();
|
||||
regs.CR_SR.set(CMD_STOP);
|
||||
regs.CTR.modify(CTR::IEN::SET); // Reenable IRQs
|
||||
}
|
||||
|
||||
loop {
|
||||
let sr = self.wait_for_event().await;
|
||||
if sr.matches_all(SR::BUSY::CLEAR) {
|
||||
break;
|
||||
}
|
||||
let regs = self.regs.lock();
|
||||
regs.CTR.modify(CTR::IEN::SET); // Reenable IRQs
|
||||
}
|
||||
|
||||
let regs = self.regs.lock();
|
||||
regs.CTR.modify(CTR::IEN::CLEAR); // Mask IRQs
|
||||
regs.CR_SR.set(CMD_IACK);
|
||||
|
||||
match status {
|
||||
Ok(count) => Ok(count),
|
||||
Err(err) => Err(err),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Device for I2C {
|
||||
unsafe fn init(self: Arc<Self>, _cx: DeviceInitContext) -> Result<(), Error> {
|
||||
let input_clk = self.clock.rate()?;
|
||||
let regs = self.regs.lock();
|
||||
regs.CTR.set(0);
|
||||
regs.set_clock_rate(input_clk, 100u64.khz())?;
|
||||
regs.CTR.write(CTR::EN::SET);
|
||||
regs.CR_SR.set(CMD_IACK);
|
||||
let index = DEVICE_REGISTRY.i2c.register_bus(self.clone())?;
|
||||
self.index.init(index);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
unsafe fn init_irq(self: Arc<Self>) -> Result<(), Error> {
|
||||
self.irq.register(self.clone())?;
|
||||
self.irq.enable()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn display_name(&self) -> &str {
|
||||
self.name
|
||||
}
|
||||
}
|
||||
|
||||
impl InterruptHandler for I2C {
|
||||
fn handle_irq(self: Arc<Self>, _vector: IrqVector) -> bool {
|
||||
let regs = self.regs.lock();
|
||||
let status = regs.CR_SR.extract();
|
||||
self.sr.signal(status.get() as u64);
|
||||
regs.CTR.modify(CTR::IEN::CLEAR);
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl I2CController for I2C {
|
||||
fn bus_number(&self) -> u32 {
|
||||
*self.index.get()
|
||||
}
|
||||
|
||||
fn child_number(&self) -> Option<u32> {
|
||||
None
|
||||
}
|
||||
|
||||
fn capabilities(&self) -> I2CMasterInfo {
|
||||
let scl_max = self.clock.rate().expect("Couldn't get input clock rate") / 5;
|
||||
I2CMasterInfo {
|
||||
max_speed_hz: scl_max.0 as u32,
|
||||
supports_10bit_addresses: true,
|
||||
}
|
||||
}
|
||||
|
||||
async fn i2c_transfer(
|
||||
&self,
|
||||
address: I2CAddress,
|
||||
transfer: &mut I2CTransfer,
|
||||
) -> Result<usize, Error> {
|
||||
// TODO validate transfer
|
||||
let address = address.as_8_bit().ok_or(Error::NotImplemented)?;
|
||||
let repeat_start = transfer.repeat_start;
|
||||
let mut nbytes = 0;
|
||||
let mut error = None;
|
||||
let mut abort = None;
|
||||
for (i, message) in transfer.messages.iter_mut().enumerate() {
|
||||
let (read, buffer_len) = match message {
|
||||
I2CMessage::Read(buffer) => (true, buffer.len()),
|
||||
I2CMessage::Write(buffer) => (false, buffer.len()),
|
||||
};
|
||||
|
||||
if i == 0 || repeat_start {
|
||||
//log::info!("{}: START {:#x} read={}", self.name, address, read);
|
||||
let read_bit = if read { 1 << 0 } else { 0 << 0 };
|
||||
let regs = self.regs.lock();
|
||||
regs.TXR_RXR.set(((address as u32) << 1) | read_bit);
|
||||
regs.CR_SR.set(CMD_START);
|
||||
regs.CTR.modify(CTR::IEN::SET);
|
||||
if abort.is_none() {
|
||||
abort = Some(AbortGuard::new(self));
|
||||
}
|
||||
}
|
||||
|
||||
let mut pos = 0;
|
||||
let mut start = true;
|
||||
loop {
|
||||
if pos == buffer_len {
|
||||
break;
|
||||
}
|
||||
let sr = self.wait_for_event().await;
|
||||
let regs = self.regs.lock();
|
||||
if sr.matches_all(SR::AL::SET) {
|
||||
error = Some(Error::HostUnreachable);
|
||||
break;
|
||||
}
|
||||
|
||||
match message {
|
||||
I2CMessage::Read(buffer) => {
|
||||
if pos == 0 && sr.matches_all(SR::RXACK::SET) {
|
||||
error = Some(Error::HostUnreachable);
|
||||
break;
|
||||
}
|
||||
if start {
|
||||
regs.CR_SR.set(CMD_READ_ACK);
|
||||
start = false;
|
||||
} else if sr.matches_all(SR::TIP::CLEAR) {
|
||||
let byte = regs.TXR_RXR.get() as u8;
|
||||
//log::info!("{}: rx {:#x}", self.name, byte);
|
||||
buffer[pos] = byte;
|
||||
pos += 1;
|
||||
nbytes += 1;
|
||||
if pos >= buffer.len() {
|
||||
regs.CR_SR.set(CMD_READ_NACK);
|
||||
} else {
|
||||
regs.CR_SR.set(CMD_READ_ACK);
|
||||
}
|
||||
}
|
||||
}
|
||||
I2CMessage::Write(buffer) => {
|
||||
if sr.matches_all(SR::RXACK::SET) {
|
||||
error = Some(Error::HostUnreachable);
|
||||
break;
|
||||
}
|
||||
regs.TXR_RXR.set(buffer[pos] as u32);
|
||||
//log::info!("{}: tx {:#x}", self.name, buffer[pos]);
|
||||
pos += 1;
|
||||
nbytes += 1;
|
||||
regs.CR_SR.set(CMD_WRITE);
|
||||
}
|
||||
}
|
||||
regs.CTR.modify(CTR::IEN::SET);
|
||||
}
|
||||
|
||||
if error.is_some() {
|
||||
break;
|
||||
}
|
||||
|
||||
{
|
||||
let regs = self.regs.lock();
|
||||
if regs.CR_SR.matches_all(SR::TIP::CLEAR) {
|
||||
regs.CTR.modify(CTR::IEN::SET); // Reenable IRQs
|
||||
continue;
|
||||
}
|
||||
|
||||
regs.CTR.modify(CTR::IEN::SET); // Reenable IRQs
|
||||
}
|
||||
|
||||
loop {
|
||||
let sr = self.wait_for_event().await;
|
||||
if sr.matches_all(SR::TIP::CLEAR) {
|
||||
break;
|
||||
}
|
||||
let regs = self.regs.lock();
|
||||
regs.CTR.modify(CTR::IEN::SET); // Reenable IRQs
|
||||
}
|
||||
}
|
||||
|
||||
// Generate stop condition
|
||||
{
|
||||
//log::info!("{}: STOP", self.name);
|
||||
let regs = self.regs.lock();
|
||||
regs.CR_SR.set(CMD_STOP);
|
||||
regs.CTR.modify(CTR::IEN::SET); // Reenable IRQs
|
||||
}
|
||||
|
||||
loop {
|
||||
let sr = self.wait_for_event().await;
|
||||
if sr.matches_all(SR::BUSY::CLEAR) {
|
||||
break;
|
||||
}
|
||||
let regs = self.regs.lock();
|
||||
regs.CTR.modify(CTR::IEN::SET); // Reenable IRQs
|
||||
}
|
||||
|
||||
if let Some(abort) = abort.take() {
|
||||
abort.consume();
|
||||
}
|
||||
|
||||
let regs = self.regs.lock();
|
||||
regs.CTR.modify(CTR::IEN::CLEAR); // Mask IRQs
|
||||
regs.CR_SR.set(CMD_IACK);
|
||||
|
||||
// log::info!(
|
||||
// "{}: finish xfer, error={:?}, nbytes={}",
|
||||
// self.name,
|
||||
// error,
|
||||
// nbytes
|
||||
// );
|
||||
match error {
|
||||
Some(err) => Err(err),
|
||||
None => Ok(nbytes),
|
||||
}
|
||||
}
|
||||
// async fn i2c_read(&self, address: I2CAddress, buffer: &mut [u8]) -> Result<usize, Error> {
|
||||
// self.start_transfer(address, true)?;
|
||||
// let mut pos = 0;
|
||||
// let status = loop {
|
||||
// if pos >= buffer.len() {
|
||||
// break Ok(pos);
|
||||
// }
|
||||
// let sr = self.wait_for_event().await;
|
||||
// let regs = self.regs.lock();
|
||||
// if sr.matches_all(SR::RXACK::SET) || sr.matches_all(SR::AL::SET) {
|
||||
// break Err(sr);
|
||||
// }
|
||||
// };
|
||||
// self.finish_transfer(status).await
|
||||
// }
|
||||
|
||||
// async fn i2c_write(&self, address: I2CAddress, buffer: &[u8]) -> Result<usize, Error> {
|
||||
// self.start_transfer(address, false)?;
|
||||
// let mut pos = 0;
|
||||
// let status = loop {
|
||||
// if pos >= buffer.len() {
|
||||
// break Ok(pos);
|
||||
// }
|
||||
// let sr = self.wait_for_event().await;
|
||||
// let regs = self.regs.lock();
|
||||
// if sr.matches_all(SR::RXACK::SET) || sr.matches_all(SR::AL::SET) {
|
||||
// break Err(sr);
|
||||
// }
|
||||
// };
|
||||
// self.finish_transfer(status).await
|
||||
// }
|
||||
|
||||
// fn i2c_read(&self, address: I2CAddress, buffer: &mut [u8]) -> Result<usize, Error> {
|
||||
// let address = address.as_8_bit().ok_or(Error::NotImplemented)?;
|
||||
// let regs = self.regs.lock();
|
||||
// log::info!(":::: start {:#x} read", address);
|
||||
// regs.poll_wait(SR::BUSY::SET);
|
||||
// regs.TXR_RXR.set(((address as u32) << 1) | 1);
|
||||
// regs.CR_SR.set(CMD_START);
|
||||
|
||||
// let mut read = 0;
|
||||
// let mut error = None;
|
||||
// let mut start = true;
|
||||
// loop {
|
||||
// let sr = regs.poll_wait(SR::TIP::SET);
|
||||
// if read == buffer.len() {
|
||||
// break;
|
||||
// }
|
||||
// if sr.matches_all(SR::AL::SET) {
|
||||
// error = Some(Error::WouldBlock);
|
||||
// break;
|
||||
// }
|
||||
// if sr.matches_all(SR::RXACK::SET) {
|
||||
// error = Some(Error::DoesNotExist);
|
||||
// break;
|
||||
// }
|
||||
|
||||
// if !start {
|
||||
// let val = regs.TXR_RXR.get() as u8;
|
||||
// buffer[read] = val;
|
||||
// read += 1;
|
||||
// if read >= buffer.len() {
|
||||
// log::info!(":::: read_ack {:02x}", val);
|
||||
// regs.CR_SR.set(CMD_READ_NACK);
|
||||
// } else {
|
||||
// log::info!(":::: read {:02x}", val);
|
||||
// regs.CR_SR.set(CMD_READ_ACK);
|
||||
// }
|
||||
// } else {
|
||||
// log::info!(":::: begin read");
|
||||
// regs.CR_SR.set(CMD_READ);
|
||||
// start = false;
|
||||
// }
|
||||
// }
|
||||
|
||||
// log::info!(":::: stop {:#x}", address);
|
||||
// // Generate stop condition
|
||||
// regs.CR_SR.set(CMD_STOP);
|
||||
// regs.poll_wait(SR::BUSY::SET);
|
||||
// regs.CR_SR.set(CMD_IACK);
|
||||
|
||||
// log::info!(":::: xfer finish {error:?}, read {read}");
|
||||
// if let Some(error) = error {
|
||||
// Err(error)
|
||||
// } else {
|
||||
// Ok(read)
|
||||
// }
|
||||
// }
|
||||
|
||||
// fn i2c_write(&self, address: I2CAddress, buffer: &[u8]) -> Result<usize, Error> {
|
||||
// let address = address.as_8_bit().ok_or(Error::NotImplemented)?;
|
||||
// let regs = self.regs.lock();
|
||||
// log::info!(":::: start {:#x} write", address);
|
||||
// regs.poll_wait(SR::BUSY::SET);
|
||||
// regs.TXR_RXR.set((address as u32) << 1);
|
||||
// regs.CR_SR.set(CMD_START);
|
||||
|
||||
// let mut sent = 0;
|
||||
// let mut error = None;
|
||||
// loop {
|
||||
// let sr = regs.poll_wait(SR::TIP::SET);
|
||||
// if sent == buffer.len() {
|
||||
// // Generate stop condition
|
||||
// regs.CR_SR.set(CMD_STOP);
|
||||
// break;
|
||||
// }
|
||||
// if sr.matches_all(SR::AL::SET) {
|
||||
// regs.CR_SR.set(CMD_STOP);
|
||||
// error = Some(Error::WouldBlock);
|
||||
// break;
|
||||
// }
|
||||
// if sr.matches_all(SR::RXACK::SET) {
|
||||
// // Generate stop condition
|
||||
// regs.CR_SR.set(CMD_STOP);
|
||||
// error = Some(Error::DoesNotExist);
|
||||
// break;
|
||||
// }
|
||||
|
||||
// log::info!(":::: send {:#x}", buffer[sent]);
|
||||
// regs.TXR_RXR.set(buffer[sent] as u32);
|
||||
// regs.CR_SR.set(CMD_WRITE);
|
||||
// sent += 1;
|
||||
// }
|
||||
|
||||
// log::info!(":::: stop {:#x}", address);
|
||||
// regs.poll_wait(SR::BUSY::SET);
|
||||
// regs.CR_SR.set(CMD_IACK);
|
||||
|
||||
// log::info!(":::: xfer finish {error:?}, sent {sent}");
|
||||
// if let Some(error) = error {
|
||||
// Err(error)
|
||||
// } else {
|
||||
// Ok(sent)
|
||||
// }
|
||||
// }
|
||||
|
||||
fn set_speed(&self, speed: Hertz) -> Result<Hertz, Error> {
|
||||
let input_rate = self.clock.rate()?;
|
||||
let regs = self.regs.lock();
|
||||
regs.set_clock_rate(input_rate, speed)
|
||||
}
|
||||
}
|
||||
|
||||
device_tree_driver! {
|
||||
compatible: ["sifive,i2c0"],
|
||||
driver: {
|
||||
fn probe(&self, node: &Arc<Node>, context: &mut ProbeContext) -> Option<Arc<dyn Device>> {
|
||||
let name = node.name().unwrap_or("i2c");
|
||||
let base = node.map_base(context, 0)?;
|
||||
let clock = node.clock(0)?;
|
||||
let irq = node.interrupt(0)?;
|
||||
|
||||
let regs = unsafe { DeviceMemoryIo::map(base, Default::default()) }.ok()?;
|
||||
|
||||
let i2c = Arc::new(I2C {
|
||||
name,
|
||||
regs: IrqSafeSpinlock::new(regs),
|
||||
clock,
|
||||
irq,
|
||||
index: OneTimeInit::new(),
|
||||
sr: BitmapEvent::new(AtomicWaker::new()),
|
||||
});
|
||||
|
||||
node.make_i2c_controller(i2c.clone());
|
||||
|
||||
Some(i2c)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ extern crate alloc;
|
||||
mod clock_fu540;
|
||||
mod clock_fu740;
|
||||
mod ethernet;
|
||||
mod i2c;
|
||||
mod pll;
|
||||
mod pwm;
|
||||
mod uart;
|
||||
|
||||
@@ -4,7 +4,8 @@ use core::{
|
||||
sync::atomic::{AtomicU16, AtomicU32, Ordering},
|
||||
};
|
||||
|
||||
use alloc::sync::Arc;
|
||||
use alloc::{boxed::Box, sync::Arc};
|
||||
use async_trait::async_trait;
|
||||
use yggdrasil_abi::{error::Error, io::device::i2c::I2CMasterInfo, process::ProcessId};
|
||||
|
||||
use crate::{
|
||||
@@ -20,6 +21,17 @@ pub struct I2CDevice {
|
||||
user: AtomicU32,
|
||||
}
|
||||
|
||||
pub enum I2CMessage<'a> {
|
||||
Read(&'a mut [u8]),
|
||||
Write(&'a [u8]),
|
||||
}
|
||||
|
||||
pub struct I2CTransfer<'a> {
|
||||
pub repeat_start: bool,
|
||||
pub messages: &'a mut [I2CMessage<'a>],
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
pub trait I2CController: Device {
|
||||
fn bus_number(&self) -> u32;
|
||||
fn child_number(&self) -> Option<u32>;
|
||||
@@ -27,8 +39,13 @@ pub trait I2CController: Device {
|
||||
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>;
|
||||
async fn i2c_transfer(
|
||||
&self,
|
||||
address: I2CAddress,
|
||||
transfer: &mut I2CTransfer,
|
||||
) -> Result<usize, Error>;
|
||||
// async fn i2c_write(&self, address: I2CAddress, buffer: &[u8]) -> Result<usize, Error>;
|
||||
// async fn i2c_read(&self, address: I2CAddress, buffer: &mut [u8]) -> Result<usize, Error>;
|
||||
}
|
||||
|
||||
impl I2CAddress {
|
||||
|
||||
@@ -4,7 +4,7 @@ use alloc::boxed::Box;
|
||||
use async_trait::async_trait;
|
||||
use device_api::{
|
||||
clock::Hertz,
|
||||
i2c::{I2CAddress, I2CDevice},
|
||||
i2c::{I2CAddress, I2CDevice, I2CMessage, I2CTransfer},
|
||||
};
|
||||
use yggdrasil_abi::{
|
||||
error::Error,
|
||||
@@ -13,26 +13,42 @@ use yggdrasil_abi::{
|
||||
process::ProcessId,
|
||||
};
|
||||
|
||||
use crate::{device::char::CharDevice, task::thread::Thread, vfs::FileReadiness};
|
||||
use crate::{
|
||||
device::char::CharDevice,
|
||||
task::{mem::ForeignPointer, thread::Thread},
|
||||
vfs::FileReadiness,
|
||||
};
|
||||
|
||||
// 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)
|
||||
let pid = Thread::current().process_id();
|
||||
self.check_lock(pid)?;
|
||||
let mut message = [I2CMessage::Read(buffer)];
|
||||
let mut xfer = I2CTransfer {
|
||||
messages: &mut message,
|
||||
repeat_start: false,
|
||||
};
|
||||
self.i2c_transfer(self.slave_address(), &mut xfer).await
|
||||
}
|
||||
async fn write(&self, buffer: &[u8]) -> Result<usize, Error> {
|
||||
self.write_nonblocking(buffer)
|
||||
let pid = Thread::current().process_id();
|
||||
self.check_lock(pid)?;
|
||||
let mut message = [I2CMessage::Write(buffer)];
|
||||
let mut xfer = I2CTransfer {
|
||||
messages: &mut message,
|
||||
repeat_start: false,
|
||||
};
|
||||
self.i2c_transfer(self.slave_address(), &mut xfer).await
|
||||
}
|
||||
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)
|
||||
let _ = buffer;
|
||||
Err(Error::InvalidOperation)
|
||||
}
|
||||
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)
|
||||
let _ = buffer;
|
||||
Err(Error::InvalidOperation)
|
||||
}
|
||||
fn device_request(&self, option: u32, buffer: &mut [u8], len: usize) -> Result<usize, Error> {
|
||||
if let Ok(req) = I2CRequestVariant::try_from(option) {
|
||||
@@ -55,6 +71,27 @@ impl CharDevice for I2CDevice {
|
||||
let len = i2c::GetMasterInfo::store_response(&info, buffer)?;
|
||||
Ok(len)
|
||||
}
|
||||
I2CRequestVariant::SmbusReadReg => {
|
||||
let thread = Thread::current();
|
||||
let pid = thread.process_id();
|
||||
self.check_lock(pid)?;
|
||||
let read_reg = i2c::SmbusReadReg::load_request(&buffer[..len])?;
|
||||
let space = thread.address_space();
|
||||
let rx_buffer = unsafe {
|
||||
read_reg
|
||||
.value_buffer
|
||||
.validate_user_slice_mut(read_reg.value_buffer_len, space)
|
||||
}?;
|
||||
let tx_buffer = [read_reg.reg];
|
||||
let mut messages = [I2CMessage::Write(&tx_buffer), I2CMessage::Read(rx_buffer)];
|
||||
let mut xfer = I2CTransfer {
|
||||
messages: &mut messages,
|
||||
repeat_start: true,
|
||||
};
|
||||
let len = block!(self.i2c_transfer(self.slave_address(), &mut xfer).await)??;
|
||||
let len = i2c::SmbusReadReg::store_response(&len.saturating_sub(1), buffer)?;
|
||||
Ok(len)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Err(Error::InvalidArgument)
|
||||
|
||||
@@ -7,7 +7,6 @@ use device_tree::{
|
||||
DeviceTree, DeviceTreeNodeExt,
|
||||
driver::{InitSequence, unflatten_device_tree},
|
||||
};
|
||||
use kernel_arch::{Architecture, ArchitectureImpl};
|
||||
use kernel_arch_riscv64::{
|
||||
PerCpuData, mem,
|
||||
registers::{SIE, SSTATUS},
|
||||
@@ -48,10 +47,12 @@ pub struct Riscv64 {
|
||||
|
||||
impl Platform for Riscv64 {
|
||||
unsafe fn reset(&self) -> ! {
|
||||
ArchitectureImpl::halt();
|
||||
log::warn!("Performing system reset via SBI");
|
||||
sbi::sbi_system_reset()
|
||||
}
|
||||
|
||||
unsafe fn power_off(&self) -> Result<!, Error> {
|
||||
log::warn!("Performing system shutdown via SBI");
|
||||
sbi::sbi_system_shutdown()
|
||||
}
|
||||
|
||||
|
||||
@@ -4,11 +4,12 @@ use abi::{
|
||||
error::Error,
|
||||
io::{FileMode, device::i2c::I2CMasterInfo},
|
||||
};
|
||||
use alloc::{string::String, sync::Arc};
|
||||
use alloc::{boxed::Box, string::String, sync::Arc};
|
||||
use async_trait::async_trait;
|
||||
use device_api::{
|
||||
clock::Hertz,
|
||||
device::{Device, DeviceInitContext},
|
||||
i2c::{I2CAddress, I2CController, I2CDevice},
|
||||
i2c::{I2CAddress, I2CController, I2CDevice, I2CTransfer},
|
||||
};
|
||||
use device_tree::driver::{Node, ProbeContext, device_tree_driver, lookup_phandle};
|
||||
use libk::fs::devfs;
|
||||
@@ -71,6 +72,7 @@ impl Device for I2CMuxChild {
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl I2CController for I2CMuxChild {
|
||||
fn bus_number(&self) -> u32 {
|
||||
self.parent.bus_number()
|
||||
@@ -88,14 +90,12 @@ impl I2CController for I2CMuxChild {
|
||||
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)
|
||||
async fn i2c_transfer(
|
||||
&self,
|
||||
address: I2CAddress,
|
||||
transfer: &mut I2CTransfer,
|
||||
) -> Result<usize, Error> {
|
||||
self.parent.i2c_transfer(address, transfer).await
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -56,6 +56,7 @@ fn dump_panic_info(cpu: &LocalCpu, pi: &core::panic::PanicInfo) {
|
||||
debug::panic_log!(sink, "--- END PANIC ---\n");
|
||||
}
|
||||
|
||||
#[cfg_attr(any(rust_analyzer, target_arch = "riscv64"), allow(unreachable_code))]
|
||||
pub(crate) fn panic_handler(pi: &core::panic::PanicInfo) -> ! {
|
||||
let cpu = Cpu::local();
|
||||
|
||||
@@ -80,6 +81,11 @@ pub(crate) fn panic_handler(pi: &core::panic::PanicInfo) -> ! {
|
||||
|
||||
dump_panic_info(&cpu, pi);
|
||||
|
||||
#[cfg(any(rust_analyzer, target_arch = "riscv64"))]
|
||||
unsafe {
|
||||
PLATFORM.reset();
|
||||
}
|
||||
|
||||
PANIC_FINISHED_FENCE.signal();
|
||||
while PANIC_SEQUENCE.load(Ordering::Acquire) != cpu.id() {
|
||||
core::hint::spin_loop();
|
||||
|
||||
@@ -96,6 +96,7 @@ pub(crate) fn system_control(option: u32, value: &mut [u8], size: usize) -> Resu
|
||||
SystemControlVariant::PowerOff => {
|
||||
unsafe { PLATFORM.power_off() }?;
|
||||
}
|
||||
SystemControlVariant::Reboot => unsafe { PLATFORM.reset() },
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -67,6 +67,18 @@ pub mod i2c {
|
||||
pub supports_10bit_addresses: bool,
|
||||
}
|
||||
|
||||
/// I²C SMBus read operation
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
#[repr(C)]
|
||||
pub struct SmbusRead {
|
||||
/// Register to be read from
|
||||
pub reg: u8,
|
||||
/// Buffer pointer
|
||||
pub value_buffer: *mut u8,
|
||||
/// Buffer length
|
||||
pub value_buffer_len: usize,
|
||||
}
|
||||
|
||||
request_group!(
|
||||
#[doc = "I²C device requests"]
|
||||
pub enum I2CRequestVariant<'de> {
|
||||
@@ -74,11 +86,17 @@ pub mod i2c {
|
||||
0x2000: SetAddress(u16, ()),
|
||||
#[doc = "Configures device speed (in Hertz)"]
|
||||
0x2001: SetSpeed(u32, u32),
|
||||
#[doc = "Performs a SMBus register read"]
|
||||
0x2002: SmbusReadReg(SmbusRead, usize),
|
||||
#[doc = "Queries I²C master capabilities"]
|
||||
0x2010: GetMasterInfo((), I2CMasterInfo),
|
||||
}
|
||||
);
|
||||
|
||||
abi_serde::impl_struct_serde!(SmbusRead: [
|
||||
reg, value_buffer, value_buffer_len
|
||||
]);
|
||||
|
||||
abi_serde::impl_struct_serde!(I2CMasterInfo: [
|
||||
max_speed_hz, supports_10bit_addresses
|
||||
]);
|
||||
|
||||
@@ -26,6 +26,8 @@ request_group!(
|
||||
pub enum SystemControlVariant<'de> {
|
||||
#[doc = "Power down the system"]
|
||||
0x1000: PowerOff((), ()),
|
||||
#[doc = "Reboot the system"]
|
||||
0x1001: Reboot((), ()),
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#!/bin/sh
|
||||
|
||||
/bin/echo info >/sys/debug/0/level
|
||||
/bin/echo info >/sys/debug/1/level
|
||||
|
||||
@@ -38,6 +38,20 @@ impl I2CMaster {
|
||||
self.0.set_speed_hz(speed_hz)
|
||||
}
|
||||
|
||||
pub fn smbus_read_byte(&mut self, reg: u8) -> io::Result<u8> {
|
||||
let mut buffer = [0; 1];
|
||||
let len = self.0.smbus_read(reg, &mut buffer)?;
|
||||
if len != 1 {
|
||||
println!("smbus_read_byte {len:?}");
|
||||
Err(io::Error::new(
|
||||
io::ErrorKind::UnexpectedEof,
|
||||
"Incomplete message received",
|
||||
))
|
||||
} else {
|
||||
Ok(buffer[0])
|
||||
}
|
||||
}
|
||||
|
||||
pub fn smbus_read(&mut self, reg: u8, data: &mut [u8]) -> io::Result<usize> {
|
||||
self.0.smbus_read(reg, data)
|
||||
}
|
||||
|
||||
@@ -5,7 +5,10 @@ use std::{
|
||||
path::Path,
|
||||
};
|
||||
|
||||
use runtime::{abi::io::device::i2c, rt::io::device::device_request};
|
||||
use runtime::{
|
||||
abi::io::device::i2c,
|
||||
rt::io::device::{device_request, i2c::SmbusRead},
|
||||
};
|
||||
|
||||
use crate::sys::{I2CMaster, I2CMasterConfig};
|
||||
|
||||
@@ -55,11 +58,18 @@ impl I2CMaster for I2CMasterImpl {
|
||||
}
|
||||
|
||||
fn smbus_read(&mut self, reg: u8, data: &mut [u8]) -> io::Result<usize> {
|
||||
self.write_all(&[reg])?;
|
||||
self.read(data)
|
||||
let mut buffer = [0; 32];
|
||||
let xfer = SmbusRead {
|
||||
reg,
|
||||
value_buffer: data.as_mut_ptr(),
|
||||
value_buffer_len: data.len(),
|
||||
};
|
||||
let len = device_request::<i2c::SmbusReadReg>(self.file.as_raw_fd(), &mut buffer, &xfer)?;
|
||||
Ok(len)
|
||||
}
|
||||
|
||||
fn smbus_write(&mut self, reg: u8, data: &[u8]) -> io::Result<usize> {
|
||||
// TODO incorrect
|
||||
self.write_all(&[reg])?;
|
||||
self.write(data)
|
||||
}
|
||||
|
||||
@@ -98,6 +98,10 @@ path = "src/mv.rs"
|
||||
name = "ln"
|
||||
path = "src/ln.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "i2c"
|
||||
path = "src/i2c.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "mkdir"
|
||||
path = "src/mkdir.rs"
|
||||
|
||||
@@ -0,0 +1,270 @@
|
||||
#![feature(yggdrasil_os)]
|
||||
use std::{
|
||||
io,
|
||||
marker::PhantomData,
|
||||
os::fd::AsRawFd,
|
||||
path::{Path, PathBuf},
|
||||
process::ExitCode,
|
||||
str::FromStr,
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
use clap::Parser;
|
||||
use cross::{
|
||||
i2c::{I2CMaster, I2CMasterConfig},
|
||||
io::{Poll, PollEvent},
|
||||
};
|
||||
use libterm::{Term, TermKey};
|
||||
use tui::{
|
||||
Terminal,
|
||||
layout::Constraint,
|
||||
style::{Color, Style},
|
||||
widgets::{Cell, Row, Table},
|
||||
};
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
struct Args {
|
||||
#[clap(subcommand)]
|
||||
command: Subcommand,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
struct Address(u8);
|
||||
|
||||
#[derive(Debug, clap::Subcommand)]
|
||||
enum Subcommand {
|
||||
TMP421 {
|
||||
#[clap(help = "I²C controller path")]
|
||||
device: PathBuf,
|
||||
#[clap(default_value = "0x40", help = "I²C device address")]
|
||||
address: Address,
|
||||
},
|
||||
TMP451 {
|
||||
#[clap(help = "I²C controller path")]
|
||||
device: PathBuf,
|
||||
#[clap(default_value = "0x4C", help = "I²C device address")]
|
||||
address: Address,
|
||||
},
|
||||
Scan {
|
||||
#[clap(help = "I²C controller path")]
|
||||
device: PathBuf,
|
||||
},
|
||||
Dump {
|
||||
#[clap(help = "I²C controller path")]
|
||||
device: PathBuf,
|
||||
#[clap(help = "I²C device address")]
|
||||
address: Address,
|
||||
},
|
||||
}
|
||||
|
||||
trait TmpSensorDefinition {
|
||||
const REG_LOCAL_TEMP_MSB: u8;
|
||||
const REG_LOCAL_TEMP_LSB: u8;
|
||||
const REG_REMOTE_TEMP_MSB: u8;
|
||||
const REG_REMOTE_TEMP_LSB: u8;
|
||||
|
||||
fn process_temperature(lsb: u8, msb: u8) -> f64;
|
||||
}
|
||||
|
||||
struct Tmp451;
|
||||
struct Tmp421;
|
||||
|
||||
struct TmpSensor<T: TmpSensorDefinition> {
|
||||
master: I2CMaster,
|
||||
_definition: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl FromStr for Address {
|
||||
type Err = String;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
eprintln!("Parse {s:?}");
|
||||
let value = if let Some(v) = s.strip_prefix("0x") {
|
||||
u8::from_str_radix(v, 16)
|
||||
} else {
|
||||
s.parse::<u8>()
|
||||
}
|
||||
.map_err(|_| format!("Invalid address: {s:?}"))?;
|
||||
if value >= 0x80 {
|
||||
Err(format!("Invalid address: {s:?}"))
|
||||
} else {
|
||||
Ok(Self(value))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: TmpSensorDefinition> TmpSensor<T> {
|
||||
pub fn open<P: AsRef<Path>>(path: P, address: Address) -> io::Result<Self> {
|
||||
let i2c_config = I2CMasterConfig {
|
||||
slave_address: Some(address.0 as _),
|
||||
speed_hz: None,
|
||||
};
|
||||
let master = I2CMaster::open(path, &i2c_config)?;
|
||||
Ok(Self {
|
||||
master,
|
||||
_definition: PhantomData,
|
||||
})
|
||||
}
|
||||
|
||||
fn read_temperature(&mut self, msb_reg: u8, lsb_reg: u8) -> io::Result<f64> {
|
||||
let msb = self.master.smbus_read_byte(msb_reg)?;
|
||||
let lsb = self.master.smbus_read_byte(lsb_reg)?;
|
||||
Ok(T::process_temperature(lsb, msb))
|
||||
}
|
||||
|
||||
pub fn read_local_temperature(&mut self) -> io::Result<f64> {
|
||||
self.read_temperature(T::REG_LOCAL_TEMP_MSB, T::REG_LOCAL_TEMP_LSB)
|
||||
}
|
||||
|
||||
pub fn read_remote_temperature(&mut self) -> io::Result<f64> {
|
||||
self.read_temperature(T::REG_REMOTE_TEMP_MSB, T::REG_REMOTE_TEMP_LSB)
|
||||
}
|
||||
}
|
||||
|
||||
impl TmpSensorDefinition for Tmp421 {
|
||||
const REG_LOCAL_TEMP_MSB: u8 = 0x00;
|
||||
const REG_LOCAL_TEMP_LSB: u8 = 0x10;
|
||||
const REG_REMOTE_TEMP_MSB: u8 = 0x01;
|
||||
const REG_REMOTE_TEMP_LSB: u8 = 0x11;
|
||||
|
||||
fn process_temperature(lsb: u8, msb: u8) -> f64 {
|
||||
(msb as i8) as f64 + (lsb >> 4) as f64 * 0.0625
|
||||
}
|
||||
}
|
||||
|
||||
impl TmpSensorDefinition for Tmp451 {
|
||||
const REG_LOCAL_TEMP_MSB: u8 = 0x00;
|
||||
const REG_LOCAL_TEMP_LSB: u8 = 0x15;
|
||||
const REG_REMOTE_TEMP_MSB: u8 = 0x01;
|
||||
const REG_REMOTE_TEMP_LSB: u8 = 0x10;
|
||||
|
||||
fn process_temperature(lsb: u8, msb: u8) -> f64 {
|
||||
msb as f64 + (lsb >> 4) as f64 * 0.0625
|
||||
}
|
||||
}
|
||||
|
||||
fn run_dump<P: AsRef<Path>>(device: P, address: Address) -> io::Result<()> {
|
||||
let i2c_config = I2CMasterConfig {
|
||||
slave_address: Some(address.0 as _),
|
||||
speed_hz: None,
|
||||
};
|
||||
let mut master = I2CMaster::open(device, &i2c_config)?;
|
||||
for i in 0..16 {
|
||||
print!("{:02x}: ", i * 16);
|
||||
for j in 0..16 {
|
||||
match master.smbus_read_byte(i * 16 + j) {
|
||||
Ok(byte) => print!("{byte:02X}"),
|
||||
Err(_) => print!("??"),
|
||||
}
|
||||
if j % 2 == 1 {
|
||||
print!(" ");
|
||||
}
|
||||
}
|
||||
println!();
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn run_scan<P: AsRef<Path>>(device: P) -> io::Result<()> {
|
||||
for i in 0..16 {
|
||||
print!("{:02x}: ", i * 16);
|
||||
for j in 0..16 {
|
||||
let address = i * 16 + j;
|
||||
let i2c_config = I2CMasterConfig {
|
||||
slave_address: Some(address),
|
||||
speed_hz: None,
|
||||
};
|
||||
let Ok(mut master) = I2CMaster::open(&device, &i2c_config) else {
|
||||
print!(" ");
|
||||
continue;
|
||||
};
|
||||
match master.smbus_read_byte(0xFF) {
|
||||
Ok(_) => print!("+"),
|
||||
Err(e) if e.kind() == io::ErrorKind::HostUnreachable => print!("-"),
|
||||
_ => print!("X"),
|
||||
}
|
||||
}
|
||||
println!();
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn run_tmp<T: TmpSensorDefinition, P: AsRef<Path>>(device: P, address: Address) -> io::Result<()> {
|
||||
let mut poll = Poll::new()?;
|
||||
let term = Term::open()?;
|
||||
let term_fd = term.as_raw_fd();
|
||||
let mut term = Terminal::new(term)?;
|
||||
|
||||
poll.add(&term_fd)?;
|
||||
|
||||
let mut tmp = TmpSensor::<T>::open(device, address)?;
|
||||
let mut local_temp = 0.0;
|
||||
let mut remote_temp = 0.0;
|
||||
let mut last_poll = Instant::now();
|
||||
|
||||
loop {
|
||||
if last_poll.elapsed() >= Duration::from_millis(500) {
|
||||
local_temp = tmp.read_local_temperature().unwrap_or_default();
|
||||
remote_temp = tmp.read_remote_temperature().unwrap_or_default();
|
||||
last_poll = Instant::now();
|
||||
}
|
||||
|
||||
term.draw(|frame| {
|
||||
let rect = frame.size();
|
||||
let hint_style = Style::default().bg(Color::Blue).fg(Color::White);
|
||||
let header = Row::new(vec![Cell::from("Name"), Cell::from("Value")])
|
||||
.height(1)
|
||||
.style(hint_style);
|
||||
let rows = vec![
|
||||
Row::new(vec![
|
||||
Cell::from("Local"),
|
||||
Cell::from(format!("{local_temp:.04}")),
|
||||
]),
|
||||
Row::new(vec![
|
||||
Cell::from("Remote"),
|
||||
Cell::from(format!("{remote_temp:.04}")),
|
||||
]),
|
||||
];
|
||||
let table = Table::new(rows)
|
||||
.widths(&[Constraint::Min(6), Constraint::Percentage(100)])
|
||||
.header(header);
|
||||
frame.render_widget(table, rect);
|
||||
})?;
|
||||
|
||||
let p = poll.wait(Some(Duration::from_millis(100)))?;
|
||||
match p {
|
||||
PollEvent::Ready(_) => {
|
||||
let key = term.backend_mut().read_key()?;
|
||||
if key == TermKey::Char('q') {
|
||||
break;
|
||||
}
|
||||
}
|
||||
PollEvent::Error(_, error) => {
|
||||
return Err(error);
|
||||
}
|
||||
PollEvent::None => (),
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn run(action: Subcommand) -> io::Result<()> {
|
||||
match action {
|
||||
Subcommand::Scan { device } => run_scan(device),
|
||||
Subcommand::Dump { address, device } => run_dump(device, address),
|
||||
Subcommand::TMP421 { address, device } => run_tmp::<Tmp421, _>(device, address),
|
||||
Subcommand::TMP451 { address, device } => run_tmp::<Tmp451, _>(device, address),
|
||||
}
|
||||
}
|
||||
|
||||
fn main() -> ExitCode {
|
||||
let args = Args::parse();
|
||||
match run(args.command) {
|
||||
Ok(()) => ExitCode::SUCCESS,
|
||||
Err(error) => {
|
||||
eprintln!("{error}");
|
||||
ExitCode::FAILURE
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1 +1,15 @@
|
||||
fn main() {}
|
||||
#![feature(rustc_private)]
|
||||
|
||||
use std::{io, process::ExitCode};
|
||||
|
||||
use runtime::{abi::system, rt::system::system_control};
|
||||
|
||||
fn main() -> ExitCode {
|
||||
if let Err(error) = system_control::<system::Reboot>(&mut [], &()) {
|
||||
let error = io::Error::from(error);
|
||||
eprintln!("{error}");
|
||||
ExitCode::FAILURE
|
||||
} else {
|
||||
ExitCode::SUCCESS
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,160 +1 @@
|
||||
use std::{
|
||||
io::{self, Write, stdout},
|
||||
path::{Path, PathBuf},
|
||||
process::ExitCode,
|
||||
thread,
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use clap::Parser;
|
||||
use cross::{
|
||||
i2c::{I2CMaster, I2CMasterConfig},
|
||||
spi::{SpiConfig, SpiDevice},
|
||||
};
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
struct Args {
|
||||
#[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 {
|
||||
master: I2CMaster,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
struct Time {
|
||||
year: u16,
|
||||
mon: u8,
|
||||
day: u8,
|
||||
|
||||
hour: u8,
|
||||
min: u8,
|
||||
sec: u8,
|
||||
millis: u16,
|
||||
}
|
||||
|
||||
impl M41T80 {
|
||||
fn open<P: AsRef<Path>>(path: P, config: &I2CMasterConfig) -> io::Result<Self> {
|
||||
let master = I2CMaster::open(path, config)?;
|
||||
Ok(Self { master })
|
||||
}
|
||||
|
||||
fn read_time(&mut self) -> io::Result<Time> {
|
||||
let mut buffer = [0; 9];
|
||||
self.master.smbus_read(0x00, &mut buffer)?;
|
||||
|
||||
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 run_m41t80(device: PathBuf, address: u8) -> io::Result<()> {
|
||||
// let device = "/dev/i2c0";
|
||||
let i2c_config = I2CMasterConfig {
|
||||
slave_address: Some(address as _),
|
||||
speed_hz: None,
|
||||
};
|
||||
let mut m41t80 = M41T80::open(device, &i2c_config)?;
|
||||
loop {
|
||||
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 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();
|
||||
|
||||
match run(args) {
|
||||
Ok(()) => ExitCode::SUCCESS,
|
||||
Err(error) => {
|
||||
eprintln!("{error}");
|
||||
ExitCode::FAILURE
|
||||
}
|
||||
}
|
||||
}
|
||||
fn main() {}
|
||||
|
||||
@@ -38,6 +38,7 @@ const PROGRAMS: &[(&str, &str)] = &[
|
||||
("env", "bin/env"),
|
||||
("grep", "bin/grep"),
|
||||
("hexd", "bin/hexd"),
|
||||
("i2c", "bin/i2c"),
|
||||
("ln", "bin/ln"),
|
||||
("login", "sbin/login"),
|
||||
("ls", "bin/ls"),
|
||||
|
||||
Reference in New Issue
Block a user