eeddbc72fe
- Scheme gains /recover: write '<pci_addr> <reset_device|rescan_bus| disconnect>' to trigger AER recovery. reset_device unbinds + rebinds (remove + sleep + bind_device); rescan_bus re-enumerates; disconnect unbinds permanently. Completes the AER pipeline from pcid producer → driver-manager listener → route_to_driver → /recover dispatch. - Plan v4.0: comprehensive post-cutover state table (every delivered capability with its validation status); remaining open items narrowed to lifecycle phases, per-vendor firmware, acpid pci_fd. - AGENTS.md + docs/README.md bumped to v4.0.
828 lines
29 KiB
Rust
828 lines
29 KiB
Rust
#[cfg(target_os = "redox")]
|
|
use std::collections::BTreeMap;
|
|
use std::collections::{HashMap, VecDeque};
|
|
use std::fs;
|
|
use std::sync::{Arc, Mutex};
|
|
|
|
#[cfg(target_os = "redox")]
|
|
use std::sync::atomic::{AtomicUsize, Ordering};
|
|
|
|
#[cfg(target_os = "redox")]
|
|
use redox_driver_core::device::DeviceId;
|
|
use redox_driver_core::manager::DeviceManager;
|
|
#[cfg(target_os = "redox")]
|
|
use redox_driver_core::manager::ProbeEvent;
|
|
#[cfg(target_os = "redox")]
|
|
use redox_driver_core::driver::ProbeResult;
|
|
#[cfg(target_os = "redox")]
|
|
use redox_scheme::scheme::SchemeSync;
|
|
#[cfg(target_os = "redox")]
|
|
use redox_scheme::{CallerCtx, OpenResult};
|
|
#[cfg(target_os = "redox")]
|
|
use redox_scheme::{
|
|
SignalBehavior, Socket,
|
|
scheme::{SchemeState, register_sync_scheme},
|
|
};
|
|
#[cfg(target_os = "redox")]
|
|
use syscall::Stat;
|
|
#[cfg(target_os = "redox")]
|
|
use syscall::error::{EACCES, EBADF, EINVAL, EIO, ENOENT, ENODEV, Error, Result};
|
|
#[cfg(target_os = "redox")]
|
|
use syscall::flag::{EventFlags, MODE_DIR, MODE_FILE, O_ACCMODE, O_RDONLY, O_RDWR, O_WRONLY};
|
|
#[cfg(target_os = "redox")]
|
|
use syscall::schemev2::NewFdFlags;
|
|
|
|
#[cfg(target_os = "redox")]
|
|
const SCHEME_NAME: &str = "driver-manager";
|
|
#[cfg(target_os = "redox")]
|
|
const ROOT_ID: usize = 1;
|
|
const MAX_EVENT_LINES: usize = 256;
|
|
const PARAM_ROOT: &str = "/tmp/redbear-driver-params";
|
|
|
|
#[cfg(target_os = "redox")]
|
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
|
enum HandleKind {
|
|
Root,
|
|
Devices,
|
|
Device(String),
|
|
Bound,
|
|
Events,
|
|
Modalias,
|
|
Bind,
|
|
Unbind,
|
|
NewId,
|
|
RemoveId,
|
|
DriverOverride,
|
|
Rescan,
|
|
Recover,
|
|
}
|
|
|
|
pub struct DriverManagerScheme {
|
|
pub bound_devices: Mutex<HashMap<String, String>>,
|
|
events: Mutex<VecDeque<String>>,
|
|
manager: Mutex<Option<Arc<std::sync::Mutex<DeviceManager>>>>,
|
|
#[cfg(target_os = "redox")]
|
|
handles: Mutex<BTreeMap<usize, HandleKind>>,
|
|
#[cfg(target_os = "redox")]
|
|
modalias_results: Mutex<BTreeMap<usize, String>>,
|
|
#[cfg(target_os = "redox")]
|
|
next_id: AtomicUsize,
|
|
}
|
|
|
|
#[cfg(target_os = "redox")]
|
|
struct SchemeServer {
|
|
scheme: Arc<DriverManagerScheme>,
|
|
}
|
|
|
|
impl DriverManagerScheme {
|
|
pub fn new() -> Self {
|
|
Self {
|
|
bound_devices: Mutex::new(HashMap::new()),
|
|
events: Mutex::new(VecDeque::new()),
|
|
manager: Mutex::new(None),
|
|
#[cfg(target_os = "redox")]
|
|
handles: Mutex::new(BTreeMap::new()),
|
|
#[cfg(target_os = "redox")]
|
|
modalias_results: Mutex::new(BTreeMap::new()),
|
|
#[cfg(target_os = "redox")]
|
|
next_id: AtomicUsize::new(ROOT_ID + 1),
|
|
}
|
|
}
|
|
|
|
pub fn set_manager(&self, manager: Arc<std::sync::Mutex<DeviceManager>>) {
|
|
if let Ok(mut slot) = self.manager.lock() {
|
|
*slot = Some(manager);
|
|
}
|
|
}
|
|
|
|
#[cfg(target_os = "redox")]
|
|
fn with_manager<R>(&self, f: impl FnOnce(&mut DeviceManager) -> R) -> Result<R> {
|
|
let slot = self.manager.lock().map_err(|_| Error::new(EIO))?;
|
|
let manager = slot.as_ref().ok_or(Error::new(ENODEV))?.clone();
|
|
let mut mgr = manager.lock().map_err(|_| Error::new(EIO))?;
|
|
Ok(f(&mut mgr))
|
|
}
|
|
|
|
pub fn bound_device_addresses(&self) -> Vec<String> {
|
|
match self.sorted_bound_addresses() {
|
|
Ok(addresses) => addresses,
|
|
Err(err) => {
|
|
log::error!("driver-manager: failed to snapshot bound devices: {err}");
|
|
Vec::new()
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn bound_device_pairs(&self) -> Vec<(String, String)> {
|
|
match self.bound_devices.lock() {
|
|
Ok(bound_devices) => bound_devices
|
|
.iter()
|
|
.map(|(addr, driver)| (addr.clone(), driver.clone()))
|
|
.collect(),
|
|
Err(err) => {
|
|
log::error!("driver-manager: failed to snapshot bound device pairs: {err}");
|
|
Vec::new()
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(target_os = "redox")]
|
|
fn alloc_handle(&self, kind: HandleKind) -> Result<usize> {
|
|
let id = self.next_id.fetch_add(1, Ordering::Relaxed);
|
|
let mut handles = self.handles.lock().map_err(|_| Error::new(EIO))?;
|
|
handles.insert(id, kind);
|
|
Ok(id)
|
|
}
|
|
|
|
#[cfg(target_os = "redox")]
|
|
fn handle(&self, id: usize) -> Result<HandleKind> {
|
|
if id == ROOT_ID {
|
|
return Ok(HandleKind::Root);
|
|
}
|
|
|
|
let handles = self.handles.lock().map_err(|_| Error::new(EIO))?;
|
|
handles.get(&id).cloned().ok_or(Error::new(EBADF))
|
|
}
|
|
|
|
#[cfg(target_os = "redox")]
|
|
fn open_from_root(&self, path: &str) -> Result<HandleKind> {
|
|
let trimmed = path.trim_matches('/');
|
|
if trimmed.is_empty() {
|
|
return Ok(HandleKind::Root);
|
|
}
|
|
|
|
let segments = trimmed
|
|
.split('/')
|
|
.filter(|segment| !segment.is_empty())
|
|
.collect::<Vec<_>>();
|
|
|
|
match segments.as_slice() {
|
|
["devices"] => Ok(HandleKind::Devices),
|
|
["bound"] => Ok(HandleKind::Bound),
|
|
["events"] => Ok(HandleKind::Events),
|
|
["modalias"] => Ok(HandleKind::Modalias),
|
|
["bind"] => Ok(HandleKind::Bind),
|
|
["unbind"] => Ok(HandleKind::Unbind),
|
|
["new_id"] => Ok(HandleKind::NewId),
|
|
["remove_id"] => Ok(HandleKind::RemoveId),
|
|
["driver_override"] => Ok(HandleKind::DriverOverride),
|
|
["rescan"] => Ok(HandleKind::Rescan),
|
|
["recover"] => Ok(HandleKind::Recover),
|
|
["devices", pci_addr] if Self::valid_pci_addr(pci_addr) => {
|
|
let _ = self.device_status(pci_addr)?;
|
|
Ok(HandleKind::Device((*pci_addr).to_string()))
|
|
}
|
|
_ => Err(Error::new(ENOENT)),
|
|
}
|
|
}
|
|
|
|
#[cfg(target_os = "redox")]
|
|
fn open_from_devices(&self, path: &str) -> Result<HandleKind> {
|
|
let trimmed = path.trim_matches('/');
|
|
if trimmed.is_empty() {
|
|
return Ok(HandleKind::Devices);
|
|
}
|
|
|
|
if trimmed.contains('/') || !Self::valid_pci_addr(trimmed) {
|
|
return Err(Error::new(ENOENT));
|
|
}
|
|
|
|
let _ = self.device_status(trimmed)?;
|
|
Ok(HandleKind::Device(trimmed.to_string()))
|
|
}
|
|
|
|
fn sorted_bound_addresses(&self) -> std::result::Result<Vec<String>, String> {
|
|
let bound_devices = self
|
|
.bound_devices
|
|
.lock()
|
|
.map_err(|err| format!("bound_devices lock poisoned: {err}"))?;
|
|
let mut addresses = bound_devices.keys().cloned().collect::<Vec<_>>();
|
|
addresses.sort_unstable();
|
|
Ok(addresses)
|
|
}
|
|
|
|
#[cfg(target_os = "redox")]
|
|
fn device_status(&self, pci_addr: &str) -> Result<String> {
|
|
let bound_devices = self.bound_devices.lock().map_err(|_| Error::new(EIO))?;
|
|
let driver_name = bound_devices
|
|
.get(pci_addr)
|
|
.cloned()
|
|
.ok_or(Error::new(ENOENT))?;
|
|
|
|
Ok(format!(
|
|
"pci_addr={pci_addr}\ndriver={driver_name}\nenabled=true\n"
|
|
))
|
|
}
|
|
|
|
#[cfg(target_os = "redox")]
|
|
fn events_output(&self) -> Result<String> {
|
|
let events = self.events.lock().map_err(|_| Error::new(EIO))?;
|
|
Ok(events.iter().cloned().collect::<String>())
|
|
}
|
|
|
|
#[cfg(target_os = "redox")]
|
|
fn bound_output(&self) -> Result<String> {
|
|
let bound_devices = self.bound_devices.lock().map_err(|_| Error::new(EIO))?;
|
|
let mut entries = bound_devices
|
|
.iter()
|
|
.map(|(pci_addr, driver_name)| format!("{pci_addr} -> {driver_name}"))
|
|
.collect::<Vec<_>>();
|
|
entries.sort_unstable();
|
|
|
|
if entries.is_empty() {
|
|
Ok(String::new())
|
|
} else {
|
|
Ok(format!("{}\n", entries.join("\n")))
|
|
}
|
|
}
|
|
|
|
#[cfg(target_os = "redox")]
|
|
fn read_handle_string(&self, id: usize, kind: &HandleKind) -> Result<String> {
|
|
match kind {
|
|
HandleKind::Root => {
|
|
Ok("devices\nbound\nevents\nmodalias\nbind\nunbind\nnew_id\nremove_id\ndriver_override\nrescan\nrecover\n".to_string())
|
|
}
|
|
HandleKind::Devices => {
|
|
let addresses = self.sorted_bound_addresses().map_err(|err| {
|
|
log::error!("driver-manager: failed to read bound device list: {err}");
|
|
Error::new(EIO)
|
|
})?;
|
|
if addresses.is_empty() {
|
|
Ok(String::new())
|
|
} else {
|
|
Ok(format!("{}\n", addresses.join("\n")))
|
|
}
|
|
}
|
|
HandleKind::Device(pci_addr) => self.device_status(pci_addr),
|
|
HandleKind::Bound => self.bound_output(),
|
|
HandleKind::Events => self.events_output(),
|
|
HandleKind::Modalias => {
|
|
if let Ok(results) = self.modalias_results.lock() {
|
|
if let Some(result) = results.get(&id) {
|
|
return Ok(result.clone());
|
|
}
|
|
}
|
|
Ok("write MODALIAS string to /scheme/driver-manager/modalias\n".to_string())
|
|
}
|
|
HandleKind::DriverOverride => {
|
|
let mut out = String::new();
|
|
if let Ok(slot) = self.manager.lock() {
|
|
if let Some(manager) = slot.as_ref() {
|
|
if let Ok(mgr) = manager.lock() {
|
|
for (device, driver) in mgr.driver_overrides_snapshot() {
|
|
out.push_str(&format!("{} {}\n", device.path, driver));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
Ok(out)
|
|
}
|
|
HandleKind::Bind => {
|
|
Ok("write '<driver> <pci_addr>' to force-bind\n".to_string())
|
|
}
|
|
HandleKind::Unbind => Ok("write '<pci_addr>' to unbind\n".to_string()),
|
|
HandleKind::NewId => Ok(
|
|
"write '<driver> <vendor> <device> [subv subd] [class class_mask]' (hex)\n"
|
|
.to_string(),
|
|
),
|
|
HandleKind::RemoveId => Ok("write the same fields as new_id to remove\n".to_string()),
|
|
HandleKind::Rescan => Ok("write anything to re-enumerate all buses\n".to_string()),
|
|
HandleKind::Recover => Ok("write '<pci_addr> <reset_device|rescan_bus|disconnect>' to trigger AER recovery\n".to_string()),
|
|
}
|
|
}
|
|
|
|
#[cfg(target_os = "redox")]
|
|
fn handle_path(&self, kind: &HandleKind) -> String {
|
|
match kind {
|
|
HandleKind::Root => format!("{SCHEME_NAME}:/"),
|
|
HandleKind::Devices => format!("{SCHEME_NAME}:/devices"),
|
|
HandleKind::Device(pci_addr) => format!("{SCHEME_NAME}:/devices/{pci_addr}"),
|
|
HandleKind::Bound => format!("{SCHEME_NAME}:/bound"),
|
|
HandleKind::Events => format!("{SCHEME_NAME}:/events"),
|
|
HandleKind::Modalias => format!("{SCHEME_NAME}:/modalias"),
|
|
HandleKind::Bind => format!("{SCHEME_NAME}:/bind"),
|
|
HandleKind::Unbind => format!("{SCHEME_NAME}:/unbind"),
|
|
HandleKind::NewId => format!("{SCHEME_NAME}:/new_id"),
|
|
HandleKind::RemoveId => format!("{SCHEME_NAME}:/remove_id"),
|
|
HandleKind::DriverOverride => format!("{SCHEME_NAME}:/driver_override"),
|
|
HandleKind::Rescan => format!("{SCHEME_NAME}:/rescan"),
|
|
HandleKind::Recover => format!("{SCHEME_NAME}:/recover"),
|
|
}
|
|
}
|
|
|
|
#[cfg(target_os = "redox")]
|
|
fn handle_mode(&self, kind: &HandleKind) -> u16 {
|
|
match kind {
|
|
HandleKind::Root | HandleKind::Devices => MODE_DIR | 0o755,
|
|
HandleKind::Device(_)
|
|
| HandleKind::Bound
|
|
| HandleKind::Events
|
|
| HandleKind::Modalias
|
|
| HandleKind::Bind
|
|
| HandleKind::Unbind
|
|
| HandleKind::NewId
|
|
| HandleKind::RemoveId
|
|
| HandleKind::DriverOverride
|
|
| HandleKind::Rescan
|
|
| HandleKind::Recover => MODE_FILE | 0o644,
|
|
}
|
|
}
|
|
|
|
#[cfg(target_os = "redox")]
|
|
fn valid_pci_addr(value: &str) -> bool {
|
|
!value.is_empty()
|
|
&& value
|
|
.chars()
|
|
.all(|ch| ch.is_ascii_hexdigit() || matches!(ch, ':' | '.'))
|
|
}
|
|
|
|
#[cfg(target_os = "redox")]
|
|
fn record_probe_event(&self, event: &ProbeEvent) {
|
|
if let ProbeEvent::ProbeCompleted {
|
|
device,
|
|
driver_name,
|
|
result: ProbeResult::Bound,
|
|
} = event
|
|
{
|
|
if device.bus == "pci" {
|
|
notify_bind(self, &device.path, driver_name);
|
|
}
|
|
}
|
|
self.push_event_line(format!("{event:?}\n"));
|
|
}
|
|
|
|
#[cfg(target_os = "redox")]
|
|
fn write_operator(&self, kind: HandleKind, line: &str) -> Result<()> {
|
|
match kind {
|
|
HandleKind::Bind => {
|
|
let mut parts = line.split_whitespace();
|
|
let (Some(driver), Some(addr)) = (parts.next(), parts.next()) else {
|
|
return Err(Error::new(EINVAL));
|
|
};
|
|
let id = DeviceId {
|
|
bus: "pci".to_string(),
|
|
path: addr.to_string(),
|
|
};
|
|
let events = self.with_manager(|mgr| mgr.bind_device(&id, driver))?;
|
|
let mut bound = false;
|
|
for event in &events {
|
|
if matches!(
|
|
event,
|
|
ProbeEvent::ProbeCompleted {
|
|
result: ProbeResult::Bound,
|
|
..
|
|
}
|
|
) {
|
|
bound = true;
|
|
}
|
|
self.record_probe_event(event);
|
|
}
|
|
if bound {
|
|
let retry_events = self.with_manager(|mgr| mgr.retry_deferred())?;
|
|
for event in &retry_events {
|
|
self.record_probe_event(event);
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
HandleKind::Unbind => {
|
|
let addr = line.split_whitespace().next().ok_or(Error::new(EINVAL))?;
|
|
let id = DeviceId {
|
|
bus: "pci".to_string(),
|
|
path: addr.to_string(),
|
|
};
|
|
let event = self.with_manager(|mgr| mgr.remove_device(&id))?;
|
|
if event.is_some() {
|
|
notify_unbind(self, addr);
|
|
}
|
|
Ok(())
|
|
}
|
|
HandleKind::NewId => {
|
|
let (driver, entry) = parse_new_id(line).ok_or(Error::new(EINVAL))?;
|
|
self.with_manager(|mgr| mgr.add_dynid(&driver, entry))
|
|
.and_then(|r| r.map_err(|_| Error::new(EINVAL)))
|
|
}
|
|
HandleKind::RemoveId => {
|
|
let (driver, entry) = parse_new_id(line).ok_or(Error::new(EINVAL))?;
|
|
self.with_manager(|mgr| mgr.remove_dynid(&driver, &entry))
|
|
.and_then(|r| r.map_err(|_| Error::new(EINVAL)))
|
|
}
|
|
HandleKind::DriverOverride => {
|
|
let mut parts = line.split_whitespace();
|
|
let addr = parts.next().ok_or(Error::new(EINVAL))?;
|
|
let id = DeviceId {
|
|
bus: "pci".to_string(),
|
|
path: addr.to_string(),
|
|
};
|
|
match parts.next() {
|
|
Some(driver) => {
|
|
self.with_manager(|mgr| mgr.set_driver_override(id, driver.to_string()))
|
|
}
|
|
None => self.with_manager(|mgr| {
|
|
mgr.clear_driver_override(&id);
|
|
}),
|
|
}
|
|
}
|
|
HandleKind::Rescan => {
|
|
let events = self.with_manager(|mgr| mgr.enumerate())?;
|
|
for event in &events {
|
|
self.record_probe_event(event);
|
|
}
|
|
Ok(())
|
|
}
|
|
HandleKind::Recover => {
|
|
let mut parts = line.split_whitespace();
|
|
let addr = parts.next().ok_or(Error::new(EINVAL))?;
|
|
let action = parts.next().unwrap_or("reset_device");
|
|
let id = DeviceId {
|
|
bus: "pci".to_string(),
|
|
path: addr.to_string(),
|
|
};
|
|
log::warn!(
|
|
"AER recovery: device={} action={}",
|
|
id.path,
|
|
action
|
|
);
|
|
match action {
|
|
"reset_device" => {
|
|
let _event = self.with_manager(|mgr| mgr.remove_device(&id))?;
|
|
thread::sleep(std::time::Duration::from_millis(100));
|
|
let events = self.with_manager(|mgr| mgr.bind_device(&id, ""))?;
|
|
for event in &events {
|
|
self.record_probe_event(event);
|
|
}
|
|
}
|
|
"rescan_bus" => {
|
|
let events = self.with_manager(|mgr| mgr.enumerate())?;
|
|
for event in &events {
|
|
self.record_probe_event(event);
|
|
}
|
|
}
|
|
"disconnect" => {
|
|
let _event = self.with_manager(|mgr| mgr.remove_device(&id))?;
|
|
notify_unbind(self, addr);
|
|
}
|
|
_ => return Err(Error::new(EINVAL)),
|
|
}
|
|
Ok(())
|
|
}
|
|
_ => Err(Error::new(EACCES)),
|
|
}
|
|
}
|
|
|
|
fn push_event_line(&self, line: String) {
|
|
match self.events.lock() {
|
|
Ok(mut events) => {
|
|
if events.len() >= MAX_EVENT_LINES {
|
|
events.pop_front();
|
|
}
|
|
events.push_back(line);
|
|
}
|
|
Err(err) => {
|
|
log::error!("driver-manager: failed to record hotplug event: {err}");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(target_os = "redox")]
|
|
impl SchemeServer {
|
|
fn new(scheme: Arc<DriverManagerScheme>) -> Self {
|
|
Self { scheme }
|
|
}
|
|
}
|
|
|
|
#[cfg(target_os = "redox")]
|
|
impl SchemeSync for SchemeServer {
|
|
fn scheme_root(&mut self) -> Result<usize> {
|
|
Ok(ROOT_ID)
|
|
}
|
|
|
|
fn openat(
|
|
&mut self,
|
|
dirfd: usize,
|
|
path: &str,
|
|
flags: usize,
|
|
_fcntl_flags: u32,
|
|
_ctx: &CallerCtx,
|
|
) -> Result<OpenResult> {
|
|
let accmode = flags & O_ACCMODE;
|
|
if accmode != O_RDONLY && accmode != O_WRONLY && accmode != O_RDWR {
|
|
return Err(Error::new(EACCES));
|
|
}
|
|
|
|
let kind = match self.scheme.handle(dirfd)? {
|
|
HandleKind::Root => self.scheme.open_from_root(path)?,
|
|
HandleKind::Devices => self.scheme.open_from_devices(path)?,
|
|
_ => return Err(Error::new(EACCES)),
|
|
};
|
|
|
|
Ok(OpenResult::ThisScheme {
|
|
number: self.scheme.alloc_handle(kind)?,
|
|
flags: NewFdFlags::empty(),
|
|
})
|
|
}
|
|
|
|
fn read(
|
|
&mut self,
|
|
id: usize,
|
|
buf: &mut [u8],
|
|
offset: u64,
|
|
_flags: u32,
|
|
_ctx: &CallerCtx,
|
|
) -> Result<usize> {
|
|
let kind = self.scheme.handle(id)?;
|
|
let data = self.scheme.read_handle_string(id, &kind)?;
|
|
let bytes = data.as_bytes();
|
|
let offset = usize::try_from(offset).map_err(|_| Error::new(EINVAL))?;
|
|
|
|
if offset >= bytes.len() {
|
|
return Ok(0);
|
|
}
|
|
|
|
let count = (bytes.len() - offset).min(buf.len());
|
|
buf[..count].copy_from_slice(&bytes[offset..offset + count]);
|
|
Ok(count)
|
|
}
|
|
|
|
fn fstat(&mut self, id: usize, stat: &mut Stat, _ctx: &CallerCtx) -> Result<()> {
|
|
let kind = self.scheme.handle(id)?;
|
|
stat.st_mode = self.scheme.handle_mode(&kind);
|
|
Ok(())
|
|
}
|
|
|
|
fn fpath(&mut self, id: usize, buf: &mut [u8], _ctx: &CallerCtx) -> Result<usize> {
|
|
let kind = self.scheme.handle(id)?;
|
|
let path = self.scheme.handle_path(&kind);
|
|
let bytes = path.as_bytes();
|
|
let count = bytes.len().min(buf.len());
|
|
buf[..count].copy_from_slice(&bytes[..count]);
|
|
Ok(count)
|
|
}
|
|
|
|
fn fsync(&mut self, id: usize, _ctx: &CallerCtx) -> Result<()> {
|
|
let _ = self.scheme.handle(id)?;
|
|
Ok(())
|
|
}
|
|
|
|
fn fcntl(&mut self, id: usize, _cmd: usize, _arg: usize, _ctx: &CallerCtx) -> Result<usize> {
|
|
let _ = self.scheme.handle(id)?;
|
|
Ok(0)
|
|
}
|
|
|
|
fn fevent(&mut self, id: usize, _flags: EventFlags, _ctx: &CallerCtx) -> Result<EventFlags> {
|
|
let _ = self.scheme.handle(id)?;
|
|
Ok(EventFlags::empty())
|
|
}
|
|
|
|
fn write(
|
|
&mut self,
|
|
id: usize,
|
|
buf: &[u8],
|
|
_offset: u64,
|
|
_flags: u32,
|
|
_ctx: &CallerCtx,
|
|
) -> Result<usize> {
|
|
let kind = self.scheme.handle(id)?;
|
|
match kind {
|
|
HandleKind::Modalias => {
|
|
let line = String::from_utf8_lossy(buf).trim().to_string();
|
|
if line.is_empty() {
|
|
return Err(Error::new(EINVAL));
|
|
}
|
|
let driver_name = crate::modalias::lookup_modalias(&line);
|
|
let payload = format!("{}\n", driver_name.unwrap_or_default());
|
|
let mut results = self
|
|
.scheme
|
|
.modalias_results
|
|
.lock()
|
|
.map_err(|_| Error::new(EIO))?;
|
|
results.insert(id, payload);
|
|
Ok(buf.len())
|
|
}
|
|
HandleKind::Bind
|
|
| HandleKind::Unbind
|
|
| HandleKind::NewId
|
|
| HandleKind::RemoveId
|
|
| HandleKind::DriverOverride
|
|
| HandleKind::Rescan
|
|
| HandleKind::Recover => {
|
|
let line = String::from_utf8_lossy(buf).trim().to_string();
|
|
self.scheme.write_operator(kind, &line)?;
|
|
Ok(buf.len())
|
|
}
|
|
_ => Err(Error::new(EACCES)),
|
|
}
|
|
}
|
|
|
|
fn on_close(&mut self, id: usize) {
|
|
if id == ROOT_ID {
|
|
return;
|
|
}
|
|
|
|
if let Ok(mut handles) = self.scheme.handles.lock() {
|
|
handles.remove(&id);
|
|
}
|
|
if let Ok(mut results) = self.scheme.modalias_results.lock() {
|
|
results.remove(&id);
|
|
}
|
|
}
|
|
}
|
|
|
|
fn parse_new_id(line: &str) -> Option<(String, redox_driver_core::r#match::DriverMatch)> {
|
|
use redox_driver_core::r#match::DriverMatch;
|
|
|
|
let parts: Vec<&str> = line.split_whitespace().collect();
|
|
if parts.len() < 3 {
|
|
return None;
|
|
}
|
|
let hex = |s: &str| u32::from_str_radix(s.trim_start_matches("0x"), 16).ok();
|
|
let opt16 = |v: u32| {
|
|
if v == 0xFFFF_FFFF {
|
|
None
|
|
} else {
|
|
Some(v as u16)
|
|
}
|
|
};
|
|
let vendor = opt16(hex(parts[1])?);
|
|
let device = opt16(hex(parts[2])?);
|
|
let mut entry = DriverMatch {
|
|
vendor,
|
|
device,
|
|
..Default::default()
|
|
};
|
|
if parts.len() >= 5 {
|
|
entry.subsystem_vendor = opt16(hex(parts[3])?);
|
|
entry.subsystem_device = opt16(hex(parts[4])?);
|
|
}
|
|
if parts.len() >= 7 {
|
|
let class = hex(parts[5])?;
|
|
let mask = hex(parts[6])?;
|
|
if mask & 0x00FF_0000 != 0 {
|
|
entry.class = Some((class >> 16) as u8);
|
|
}
|
|
if mask & 0x0000_FF00 != 0 {
|
|
entry.subclass = Some((class >> 8) as u8);
|
|
}
|
|
if mask & 0x0000_00FF != 0 {
|
|
entry.prog_if = Some(class as u8);
|
|
}
|
|
}
|
|
Some((parts[0].to_string(), entry))
|
|
}
|
|
|
|
fn write_driver_param(pci_addr: &str, param: &str, value: &str) -> std::io::Result<()> {
|
|
let dir = format!("{PARAM_ROOT}/{pci_addr}");
|
|
fs::create_dir_all(&dir)?;
|
|
fs::write(format!("{dir}/{param}"), value)
|
|
}
|
|
|
|
pub fn notify_bind(scheme: &DriverManagerScheme, pci_addr: &str, driver_name: &str) {
|
|
match scheme.bound_devices.lock() {
|
|
Ok(mut bound_devices) => {
|
|
bound_devices.insert(pci_addr.to_string(), driver_name.to_string());
|
|
}
|
|
Err(err) => {
|
|
log::error!(
|
|
"driver-manager: failed to update bound device state for {pci_addr}: {err}"
|
|
);
|
|
}
|
|
}
|
|
|
|
scheme.push_event_line(format!(
|
|
"action=bind pci_addr={pci_addr} driver={driver_name}\n"
|
|
));
|
|
|
|
if let Err(err) = write_driver_param(pci_addr, "driver", driver_name) {
|
|
log::warn!("driver-manager: failed to write driver param for {pci_addr}: {err}");
|
|
}
|
|
if let Err(err) = write_driver_param(pci_addr, "enabled", "true") {
|
|
log::warn!("driver-manager: failed to write enabled param for {pci_addr}: {err}");
|
|
}
|
|
}
|
|
|
|
pub fn notify_unbind(scheme: &DriverManagerScheme, pci_addr: &str) {
|
|
let previous_driver = match scheme.bound_devices.lock() {
|
|
Ok(mut bound_devices) => bound_devices.remove(pci_addr),
|
|
Err(err) => {
|
|
log::error!(
|
|
"driver-manager: failed to remove bound device state for {pci_addr}: {err}"
|
|
);
|
|
None
|
|
}
|
|
};
|
|
|
|
let event_line = if let Some(driver_name) = previous_driver.as_deref() {
|
|
format!("action=unbind pci_addr={pci_addr} driver={driver_name}\n")
|
|
} else {
|
|
format!("action=unbind pci_addr={pci_addr}\n")
|
|
};
|
|
scheme.push_event_line(event_line);
|
|
|
|
if let Err(err) = write_driver_param(pci_addr, "driver", "") {
|
|
log::warn!("driver-manager: failed to clear driver param for {pci_addr}: {err}");
|
|
}
|
|
if let Err(err) = write_driver_param(pci_addr, "enabled", "false") {
|
|
log::warn!("driver-manager: failed to write disabled param for {pci_addr}: {err}");
|
|
}
|
|
}
|
|
|
|
#[cfg(target_os = "redox")]
|
|
pub fn start_scheme_server(scheme: Arc<DriverManagerScheme>) -> std::result::Result<(), String> {
|
|
let socket = Socket::create()
|
|
.map_err(|err| format!("driver-manager: failed to create scheme socket: {err}"))?;
|
|
let mut server = SchemeServer::new(scheme);
|
|
|
|
register_sync_scheme(&socket, SCHEME_NAME, &mut server)
|
|
.map_err(|err| format!("driver-manager: failed to register scheme:{SCHEME_NAME}: {err}"))?;
|
|
|
|
log::info!("driver-manager: registered scheme:{SCHEME_NAME}");
|
|
|
|
std::thread::Builder::new()
|
|
.name("driver-manager-scheme".to_string())
|
|
.spawn(move || {
|
|
let mut state = SchemeState::new();
|
|
|
|
loop {
|
|
let request = match socket.next_request(SignalBehavior::Restart) {
|
|
Ok(Some(request)) => request,
|
|
Ok(None) => {
|
|
log::info!("driver-manager: scheme socket closed, shutting down");
|
|
break;
|
|
}
|
|
Err(err) => {
|
|
log::error!("driver-manager: failed to read scheme request: {err}");
|
|
break;
|
|
}
|
|
};
|
|
|
|
if let redox_scheme::RequestKind::Call(request) = request.kind() {
|
|
let response = request.handle_sync(&mut server, &mut state);
|
|
if let Err(err) = socket.write_response(response, SignalBehavior::Restart) {
|
|
log::error!("driver-manager: failed to write scheme response: {err}");
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
})
|
|
.map_err(|err| format!("driver-manager: failed to spawn scheme server thread: {err}"))?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[cfg(not(target_os = "redox"))]
|
|
pub fn start_scheme_server(_scheme: Arc<DriverManagerScheme>) -> std::result::Result<(), String> {
|
|
Ok(())
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::parse_new_id;
|
|
|
|
#[test]
|
|
fn new_id_parses_vendor_device() {
|
|
let (driver, entry) = parse_new_id("e1000d 0x8086 0x100E").unwrap();
|
|
assert_eq!(driver, "e1000d");
|
|
assert_eq!(entry.vendor, Some(0x8086));
|
|
assert_eq!(entry.device, Some(0x100E));
|
|
assert_eq!(entry.subsystem_vendor, None);
|
|
assert_eq!(entry.class, None);
|
|
}
|
|
|
|
#[test]
|
|
fn new_id_parses_full_linux_form() {
|
|
let (driver, entry) =
|
|
parse_new_id("ahcid 0x8086 0x2922 0x8086 0x0A06 0x010601 0xFFFFFF").unwrap();
|
|
assert_eq!(driver, "ahcid");
|
|
assert_eq!(entry.vendor, Some(0x8086));
|
|
assert_eq!(entry.device, Some(0x2922));
|
|
assert_eq!(entry.subsystem_vendor, Some(0x8086));
|
|
assert_eq!(entry.subsystem_device, Some(0x0A06));
|
|
assert_eq!(entry.class, Some(0x01));
|
|
assert_eq!(entry.subclass, Some(0x06));
|
|
assert_eq!(entry.prog_if, Some(0x01));
|
|
}
|
|
|
|
#[test]
|
|
fn new_id_maps_any_id_to_none() {
|
|
let (_, entry) = parse_new_id("x 0xFFFFFFFF 0x100E").unwrap();
|
|
assert_eq!(entry.vendor, None);
|
|
assert_eq!(entry.device, Some(0x100E));
|
|
}
|
|
|
|
#[test]
|
|
fn new_id_rejects_short_input() {
|
|
assert!(parse_new_id("e1000d 0x8086").is_none());
|
|
assert!(parse_new_id("").is_none());
|
|
assert!(parse_new_id("e1000d zz 0x100E").is_none());
|
|
}
|
|
|
|
#[test]
|
|
fn new_id_class_mask_selects_bytes() {
|
|
let (_, entry) = parse_new_id("x 0x8086 0x100E 0 0 0x020000 0xFF0000").unwrap();
|
|
assert_eq!(entry.class, Some(0x02));
|
|
assert_eq!(entry.subclass, None);
|
|
assert_eq!(entry.prog_if, None);
|
|
}
|
|
}
|