f505d18a89
Bug fix: endpoint numbers were computed as per-interface positions, but xhcid keys endpoints by GLOBAL index across all interfaces of the configuration. On two-interface ACM devices (comm interrupt-IN first), the driver opened (interrupt-IN, bulk-IN) as (bulk_in, bulk_out) — read from the interrupt endpoint and wrote to the IN endpoint. Endpoints are now counted across all interfaces in configuration order, exactly as xhcid's PortState::get_endp_desc does. P6-A expansion (Linux 7.1 cdc-acm.c reference): - SEND_BREAK (0x23) control request - SERIAL_STATE monitoring: poll the comm interface's interrupt-IN endpoint on a dedicated thread, parse the 8-byte header + 2-byte UART state bitmap (CDC 1.1 6.3.5): DCD/DSR/break/RI/framing/parity/overrun, log transitions - scheme gains a read-only 'state' file reporting the current line state (dcd=.. dsr=.. ...) for getty/terminal consumers Verified: cargo check -Z build-std --target x86_64-unknown-redox clean, no new warnings. Runtime validation needs an ACM device (QEMU has no CDC ACM emulation; FTDI/Arduino on bare metal or passed through).
77 lines
3.2 KiB
Rust
77 lines
3.2 KiB
Rust
use std::sync::{Arc, Mutex};
|
|
|
|
use redox_scheme::scheme::SchemeSync;
|
|
use redox_scheme::{CallerCtx, OpenResult};
|
|
use syscall::error::{Error, Result, EBADF, EINVAL};
|
|
use syscall::flag::{O_RDWR, O_STAT};
|
|
use syscall::schemev2::NewFdFlags;
|
|
|
|
use xhcid_interface::XhciEndpHandle;
|
|
|
|
use crate::SerialState;
|
|
|
|
/// Scheme fd number for the read-only "state" file (serial line state).
|
|
const STATE_FD: usize = 2;
|
|
|
|
pub struct AcmScheme {
|
|
bulk_in: Mutex<XhciEndpHandle>,
|
|
bulk_out: Mutex<XhciEndpHandle>,
|
|
serial_state: Arc<Mutex<SerialState>>,
|
|
open_count: Mutex<usize>,
|
|
}
|
|
|
|
impl AcmScheme {
|
|
pub fn new(bulk_in: XhciEndpHandle, bulk_out: XhciEndpHandle, serial_state: Arc<Mutex<SerialState>>) -> Self {
|
|
Self {
|
|
bulk_in: Mutex::new(bulk_in),
|
|
bulk_out: Mutex::new(bulk_out),
|
|
serial_state,
|
|
open_count: Mutex::new(0),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl SchemeSync for AcmScheme {
|
|
fn openat(&mut self, fd: usize, path: &str, _flags: usize, _fcntl_flags: u32, _ctx: &CallerCtx) -> Result<OpenResult> {
|
|
if fd != 1 { return Err(Error::new(EBADF)); }
|
|
if path == "state" {
|
|
return Ok(OpenResult::ThisScheme { number: STATE_FD, flags: NewFdFlags::from_bits_truncate(O_STAT as u8) });
|
|
}
|
|
if path.is_empty() || path == "." || path == "/" {
|
|
*self.open_count.lock().unwrap_or_else(|e| e.into_inner()) += 1;
|
|
return Ok(OpenResult::ThisScheme { number: 1, flags: NewFdFlags::from_bits_truncate(O_STAT as u8) });
|
|
}
|
|
let mut count = self.open_count.lock().unwrap_or_else(|e| e.into_inner());
|
|
let next_id = 3 + *count; *count += 1;
|
|
Ok(OpenResult::OtherScheme { fd: next_id })
|
|
}
|
|
fn read(&mut self, id: usize, buf: &mut [u8], _offset: u64, _fcntl_flags: u32, _ctx: &CallerCtx) -> Result<usize> {
|
|
if buf.is_empty() { return Err(Error::new(EBADF)); }
|
|
if id == STATE_FD {
|
|
let state = self.serial_state.lock().unwrap_or_else(|e| e.into_inner());
|
|
let text = state.to_display();
|
|
let bytes = text.as_bytes();
|
|
let n = bytes.len().min(buf.len());
|
|
buf[..n].copy_from_slice(&bytes[..n]);
|
|
return Ok(n);
|
|
}
|
|
if id < 3 { return Err(Error::new(EBADF)); }
|
|
let mut bulk_in = self.bulk_in.lock().unwrap_or_else(|e| e.into_inner());
|
|
match bulk_in.transfer_read(buf) {
|
|
Ok(status) if status.bytes_transferred > 0 => Ok(status.bytes_transferred as usize),
|
|
Ok(_) => Ok(0),
|
|
Err(e) => { log::warn!("ACM scheme: read: {}", e); Err(Error::new(EINVAL)) }
|
|
}
|
|
}
|
|
fn write(&mut self, id: usize, buf: &[u8], _offset: u64, _fcntl_flags: u32, _ctx: &CallerCtx) -> Result<usize> {
|
|
if id < 3 || buf.is_empty() { return Err(Error::new(EBADF)); }
|
|
let mut bulk_out = self.bulk_out.lock().unwrap_or_else(|e| e.into_inner());
|
|
match bulk_out.transfer_write(buf) {
|
|
Ok(status) if status.bytes_transferred > 0 => Ok(status.bytes_transferred as usize),
|
|
Ok(_) => Ok(0),
|
|
Err(e) => { log::warn!("ACM scheme: write: {}", e); Err(Error::new(EINVAL)) }
|
|
}
|
|
}
|
|
fn fsync(&mut self, _id: usize, _ctx: &CallerCtx) -> Result<()> { Ok(()) }
|
|
}
|