blob: 1b410d6d9b37e1280c7f98a813c81bc3c1aed299 [file]
// Licensed under the Apache-2.0 license
//! I2C Server — IPC Dispatch Loop
//!
//! Userspace service that receives I2C requests over a Pigweed IPC channel,
//! dispatches them to the AST1060 backend, and responds with results.
//!
//! # Architecture
//!
//! ```text
//! ┌─ Client ──────────────────────────┐
//! │ channel_transact(request) │
//! └──────────────┬────────────────────┘
//! │ IPC channel
//! ▼
//! ┌─ This Server ─────────────────────┐
//! │ object_wait(READABLE) │
//! │ channel_read → I2cRequestHeader │
//! │ dispatch_i2c_op(op, backend) │
//! │ channel_respond ← I2cResponseHdr │
//! └──────────────┬────────────────────┘
//! │ from_initialized()
//! ▼
//! ┌─ AspeedI2cBackend ────────────────┐
//! │ Ast1060I2c::write/read/write_read │
//! └───────────────────────────────────┘
//! ```
//!
//! # WaitGroup + IRQ Pattern
//!
//! A WaitGroup multiplexes two event sources:
//!
//! - `user_data=0`: IPC from client — `channel_read`, dispatch, `channel_respond`
//! - `user_data=1`: I2C2 hardware interrupt — `drain_slave_rx`, `interrupt_ack`,
//! `raise_peer_user_signal`
//!
//! On the IPC path, `channel_read` returns immediately (channel is already
//! `READABLE` when the WaitGroup fires). No async syscalls needed.
//!
//! # Handle Binding
//!
//! The IPC handle is provided by the `app_package` Bazel rule, which generates
//! `app_i2c_server::handle::I2C` from the system configuration.
#![no_main]
#![no_std]
use i2c_api::wire::{I2cOp, I2cRequestHeader, I2cResponseHeader, MAX_REQUEST_SIZE, MAX_RESPONSE_SIZE};
use i2c_api::ResponseCode;
use i2c_backend_aspeed::AspeedI2cBackend;
use pw_status::Result;
use userspace::entry;
use userspace::syscall::{self, Signals};
use userspace::time::Instant;
use app_i2c_server::{handle};//, _signals};
// ---------------------------------------------------------------------------
// Server loop
// ---------------------------------------------------------------------------
fn i2c_server_loop() -> Result<()> {
pw_log::info!("I2C server starting");
// SAFETY: Called once at server startup, exclusive peripheral access.
let mut backend = unsafe { AspeedI2cBackend::new() };
// Per-controller hardware init (I2CC00, timing, interrupts).
// Platform init (entry.rs) already ran init_i2c_global() + pinmux.
// TODO: Initialize all buses this server owns. For now, just bus 2 (I2C1 in hardware).
pw_log::info!("I2C server: initializing bus 2");
backend.init_bus(2).map_err(|e| {
pw_log::error!("I2C server: init_bus(2) failed: 0x{:02x}", e as u32);
pw_status::Error::Internal
})?;
pw_log::info!("I2C server: bus 2 initialized successfully");
// Per-bus notification state (set/cleared via EnableSlaveNotification IPC).
let mut notification_enabled = [false; 14];
let mut request_buf = [0u8; MAX_REQUEST_SIZE];
let mut response_buf = [0u8; MAX_RESPONSE_SIZE];
// Register both event sources with the WaitGroup.
// user_data=0 → IPC request from client (I2C channel becomes READABLE).
// user_data=1 → hardware I2C2 interrupt (I2C2_IRQ fires signals::I2C2).
//syscall::wait_group_add(handle::WG, handle::I2C, Signals::READABLE, 0usize)?;
//syscall::wait_group_add(handle::WG, handle::I2C2_IRQ, signals::I2C2, 1usize)?;
loop {
//let wait_return = syscall::object_wait(handle::WG, Signals::READABLE, Instant::MAX)?;
syscall::object_wait(handle::I2C, Signals::READABLE, Instant::MAX)?;
// if wait_return.user_data == 1 {
// pw_log::info!("i2c irq get");
// // Hardware I2C2 slave interrupt: drain data into flat buffers and
// // wake the client. Re-enable the IRQ after draining.
// handle_i2c_interrupt(&mut backend, &notification_enabled);
// let _ = syscall::interrupt_ack(handle::I2C2_IRQ, signals::I2C2);
// }
{
// IPC request from client — channel_read returns immediately since
// the channel was already READABLE when the WaitGroup fired.
let len = syscall::channel_read(handle::I2C, 0, &mut request_buf)?;
if len < I2cRequestHeader::SIZE {
let resp = I2cResponseHeader::error(ResponseCode::ServerError);
response_buf[..I2cResponseHeader::SIZE].copy_from_slice(&resp.to_bytes());
syscall::channel_respond(handle::I2C, &response_buf[..I2cResponseHeader::SIZE])?;
continue;
}
let response_len = dispatch_i2c_op(
&request_buf[..len],
&mut response_buf,
&mut backend,
&mut notification_enabled,
);
syscall::channel_respond(handle::I2C, &response_buf[..response_len])?;
}
}
}
// ---------------------------------------------------------------------------
// Interrupt handler
// ---------------------------------------------------------------------------
/// Handle a hardware I2C slave interrupt.
///
/// Called once per interrupt event (no polling loop). Drains any received data
/// into the per-bus flat buffer for every notification-enabled bus, then raises
/// `Signals::USER` on the IPC channel to wake the client registered via
/// `EnableSlaveNotification`.
fn _handle_i2c_interrupt(backend: &mut AspeedI2cBackend, notification_enabled: &[bool; 14]) {
for bus in 0..14u8 {
if notification_enabled[bus as usize] {
let _ = backend.drain_slave_rx(bus);
}
}
// Signal the client — ORs USER onto the channel without disturbing READABLE.
let _ = syscall::object_raise_peer_user_signal(handle::I2C);
}
// ---------------------------------------------------------------------------
// Dispatch
// ---------------------------------------------------------------------------
/// Decode request header, dispatch to backend, encode response.
///
/// Read operations write their data directly into `response` after the
/// response header (offset [`I2cResponseHeader::SIZE`]), avoiding an
/// extra copy.
fn dispatch_i2c_op(
request: &[u8],
response: &mut [u8],
backend: &mut AspeedI2cBackend,
notification_enabled: &mut [bool; 14],
) -> usize {
// Parse header
let Some(header) = I2cRequestHeader::from_bytes(request) else {
return encode_error(response, ResponseCode::ServerError);
};
let Some(op) = header.operation() else {
return encode_error(response, ResponseCode::ServerError);
};
let payload = &request[I2cRequestHeader::SIZE..];
match op {
// ------------------------------------------------------------------
// Write: header.write_len bytes from payload → device
// ------------------------------------------------------------------
I2cOp::Write => {
pw_log::info!("I2C dispatch write");
let wlen = header.write_len as usize;
if payload.len() < wlen {
return encode_error(response, ResponseCode::BufferTooSmall);
}
match backend.write(header.bus, header.address, &payload[..wlen]) {
Ok(()) => {
pw_log::debug!("I2C write success");
encode_success(response, 0)
},
Err(code) => {
pw_log::error!("I2C backend write failed: code=0x{:02x}", code as u32);
encode_error(response, code)
},
}
}
// ------------------------------------------------------------------
// Read: header.read_len bytes from device → response payload
// ------------------------------------------------------------------
I2cOp::Read => {
pw_log::info!("I2C dispatch read");
let rlen = header.read_len as usize;
let avail = response.len().saturating_sub(I2cResponseHeader::SIZE);
if rlen > avail {
return encode_error(response, ResponseCode::BufferTooLarge);
}
let read_buf =
&mut response[I2cResponseHeader::SIZE..I2cResponseHeader::SIZE + rlen];
match backend.read(header.bus, header.address, read_buf) {
Ok(()) => encode_success(response, rlen),
Err(code) => encode_error(response, code),
}
}
// ------------------------------------------------------------------
// WriteRead: write then read with repeated START
// ------------------------------------------------------------------
I2cOp::WriteRead => {
pw_log::info!("I2C dispatch writeread");
let wlen = header.write_len as usize;
let rlen = header.read_len as usize;
if payload.len() < wlen {
return encode_error(response, ResponseCode::BufferTooSmall);
}
let avail = response.len().saturating_sub(I2cResponseHeader::SIZE);
if rlen > avail {
return encode_error(response, ResponseCode::BufferTooLarge);
}
let write_data = &payload[..wlen];
let read_buf =
&mut response[I2cResponseHeader::SIZE..I2cResponseHeader::SIZE + rlen];
match backend.write_read(header.bus, header.address, write_data, read_buf) {
Ok(()) => encode_success(response, rlen),
Err(code) => encode_error(response, code),
}
}
// ------------------------------------------------------------------
// Probe: write 0 bytes — ACK means device present
// ------------------------------------------------------------------
I2cOp::Probe => {
pw_log::info!("I2C dispatch probe");
match backend.write(header.bus, header.address, &[]) {
Ok(()) => encode_success(response, 0),
Err(code) => encode_error(response, code),
}
}
// ------------------------------------------------------------------
// RecoverBus: attempt to unstick SDA via clock pulses
// ------------------------------------------------------------------
I2cOp::RecoverBus => {
pw_log::info!("I2C dispatch recover bus");
match backend.recover_bus(header.bus) {
Ok(()) => encode_success(response, 0),
Err(code) => encode_error(response, code),
}
}
// ------------------------------------------------------------------
// ConfigureSlave: set slave address on a bus
// ------------------------------------------------------------------
I2cOp::ConfigureSlave => {
pw_log::info!("I2C dispatch configure slave");
match backend.configure_slave(header.bus, header.address) {
Ok(()) => encode_success(response, 0),
Err(code) => encode_error(response, code),
}
}
// ------------------------------------------------------------------
// EnableSlave: activate slave receive mode
// ------------------------------------------------------------------
I2cOp::EnableSlave => {
pw_log::info!("I2C dispatch enable slave");
match backend.enable_slave(header.bus) {
Ok(()) => encode_success(response, 0),
Err(code) => encode_error(response, code),
}
}
// ------------------------------------------------------------------
// DisableSlave: deactivate slave receive mode
// ------------------------------------------------------------------
I2cOp::DisableSlave => {
pw_log::info!("I2C dispatch disable slave");
match backend.disable_slave(header.bus) {
Ok(()) => encode_success(response, 0),
Err(code) => encode_error(response, code),
}
}
I2cOp::SlaveReceive => {
//pw_log::info!("I2C dispatch slave receive");
let rlen = header.read_len as usize;
let avail = response.len().saturating_sub(I2cResponseHeader::SIZE);
if rlen > avail {
return encode_error(response, ResponseCode::BufferTooLarge);
}
let buf = &mut response[I2cResponseHeader::SIZE..I2cResponseHeader::SIZE + rlen];
if notification_enabled[header.bus as usize] {
// Non-blocking: data was pre-drained into the flat buffer by the IRQ handler.
// Busy (buffer empty) maps to 0 bytes — not an error.
match backend.get_buffered_slave_message(header.bus, buf) {
Ok(n) => encode_success(response, n),
Err(ResponseCode::Busy) => encode_success(response, 0),
Err(code) => encode_error(response, code),
}
} else {
// Blocking poll: spins in the backend until DataReceived or timeout.
match backend.slave_receive(header.bus, buf) {
Ok(0) => {
pw_log::info!("slave_receive bus={}: Stop (0 bytes)", header.bus as u32);
encode_success(response, 0)
}
Ok(n) => {
pw_log::info!(
"slave_receive bus={}: received {} bytes",
header.bus as u32,
n as u32,
);
encode_success(response, n)
}
Err(ResponseCode::Timeout) => encode_success(response, 0),
Err(code) => encode_error(response, code),
}
}
}
// ------------------------------------------------------------------
// SlaveSetResponse: pre-load TX buffer for next master read
// ------------------------------------------------------------------
I2cOp::SlaveSetResponse => {
// pw_log::info!("I2C dispatch slave set response");
let wlen = header.write_len as usize;
if payload.len() < wlen {
return encode_error(response, ResponseCode::BufferTooSmall);
}
match backend.slave_set_response(header.bus, &payload[..wlen]) {
Ok(()) => encode_success(response, 0),
Err(code) => encode_error(response, code),
}
}
// ------------------------------------------------------------------
// SlaveWaitEvent: block until next slave event, return kind + data
//
// Response payload layout:
// byte 0: SlaveEventKind as u8
// bytes 1..: received data (only for DataReceived events)
// ------------------------------------------------------------------
I2cOp::SlaveWaitEvent => {
let max_rx = header.read_len as usize;
// Reserve space for event-kind byte + rx data.
let avail = response.len().saturating_sub(I2cResponseHeader::SIZE + 1);
let rx_cap = max_rx.min(avail);
let rx_buf = &mut response[I2cResponseHeader::SIZE + 1..I2cResponseHeader::SIZE + 1 + rx_cap];
match backend.slave_wait_event(header.bus, rx_buf) {
Ok((kind, rx_len)) => {
let total = 1 + rx_len;
response[I2cResponseHeader::SIZE] = kind as u8;
encode_success(response, total)
}
Err(code) => encode_error(response, code),
}
}
// ------------------------------------------------------------------
// EnableSlaveNotification: arm interrupt-driven receive for a bus
// ------------------------------------------------------------------
I2cOp::EnableSlaveNotification => {
pw_log::info!("I2C dispatch enable slave notification");
notification_enabled[header.bus as usize] = true;
match backend.enable_slave_notification(header.bus) {
Ok(()) => encode_success(response, 0),
Err(code) => encode_error(response, code),
}
}
// ------------------------------------------------------------------
// DisableSlaveNotification: disarm interrupt-driven receive for a bus
// ------------------------------------------------------------------
I2cOp::DisableSlaveNotification => {
pw_log::info!("I2C dispatch disable slave notification");
notification_enabled[header.bus as usize] = false;
match backend.disable_slave_notification(header.bus) {
Ok(()) => encode_success(response, 0),
Err(code) => encode_error(response, code),
}
}
// ------------------------------------------------------------------
// Not yet implemented
// ------------------------------------------------------------------
I2cOp::ConfigureSpeed | I2cOp::Transaction => {
encode_error(response, ResponseCode::ServerError)
}
}
}
// ---------------------------------------------------------------------------
// Response encoding
// ---------------------------------------------------------------------------
/// Encode an error response (header only, no payload).
fn encode_error(response: &mut [u8], code: ResponseCode) -> usize {
let header = I2cResponseHeader::error(code);
response[..I2cResponseHeader::SIZE].copy_from_slice(&header.to_bytes());
I2cResponseHeader::SIZE
}
/// Encode a success response.
///
/// For read operations, the caller has already written the data into
/// `response[I2cResponseHeader::SIZE..]` before calling this function.
fn encode_success(response: &mut [u8], data_len: usize) -> usize {
let header = I2cResponseHeader::success(data_len as u16);
response[..I2cResponseHeader::SIZE].copy_from_slice(&header.to_bytes());
I2cResponseHeader::SIZE + data_len
}
// ---------------------------------------------------------------------------
// Entry point
// ---------------------------------------------------------------------------
#[entry]
fn entry() -> ! {
if let Err(e) = i2c_server_loop() {
pw_log::error!("I2C server error: {}", e as u32);
let _ = syscall::debug_shutdown(Err(e));
}
loop {}
}
#[panic_handler]
fn panic(_info: &core::panic::PanicInfo) -> ! {
loop {}
}