blob: 3a757d66f034533a946a3343dbae32647f0aac11 [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 │
//! └───────────────────────────────────┘
//! ```
//!
//! # IPC Pattern
//!
//! Follows the same loop as `services/crypto/server/src/main.rs`:
//!
//! 1. `object_wait(handle, READABLE)` — block until a client sends a request
//! 2. `channel_read(handle)` — read the raw request bytes
//! 3. Parse `I2cRequestHeader`, extract op + payload
//! 4. Dispatch to backend (write / read / write_read / probe / recover)
//! 5. `channel_respond(handle)` — send response header + data
//!
//! # 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;
// ---------------------------------------------------------------------------
// 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 0 (I2C1).
backend.init_bus(0).map_err(|_| pw_status::Error::Internal)?;
let mut request_buf = [0u8; MAX_REQUEST_SIZE];
let mut response_buf = [0u8; MAX_RESPONSE_SIZE];
loop {
// Block until a client sends a request
syscall::object_wait(handle::I2C, Signals::READABLE, Instant::MAX)?;
// Read the request
let len = syscall::channel_read(handle::I2C, 0, &mut request_buf)?;
if len < I2cRequestHeader::SIZE {
// Truncated request — respond with error
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;
}
// Dispatch and respond
let response_len =
dispatch_i2c_op(&request_buf[..len], &mut response_buf, &mut backend);
syscall::channel_respond(handle::I2C, &response_buf[..response_len])?;
}
}
// ---------------------------------------------------------------------------
// 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,
) -> usize {
pw_log::info!("I2C server dispatch");
// 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(()) => encode_success(response, 0),
Err(code) => 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),
}
}
// ------------------------------------------------------------------
// 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 {}
}