absorb: 41 orphaned base patches re-applied (Phase 1.0A)
Per local/docs/PATCH-PRESERVATION-AUDIT-2026-07-12.md the base fork was carrying only 38 of 100 patches in local/patches/base/. The other 62 patches' content was silently missing from the fork working tree, even though their .patch files were preserved. This commit re-applies 41 patches that genuinely still apply cleanly + 17 hunks that partially applied. Recovery covers: - D-Bus initfs service wiring (P4-initfs-dbus-services) - USB service wiring (P4-initfs-usb-drm-services) - netcfg/dhcp/dhcpv6 driver fixes - acpid shutdown/PM/quirk fixes - inputd/ps2d hard-fail logging - pcid driver interface refinements (server cmd channel) - virtio-core for VirtualBox support - ixgbed/rtl8139/rtl8168 net drivers - ahcid NCQ + per-function interrupt coalescing - logd persistent logging - bootstrap procmgr race-condition fixes - cargo version pin to +rb0.3.0 (synchronized release branch) 58 files changed, +1444/-318 lines. Untracked mnt/ tree and *.orig / *.rej files cleaned up after patch application (leftover from absolute-path patch headers).
This commit is contained in:
+2
-1
@@ -47,6 +47,7 @@ members = [
|
||||
|
||||
"drivers/input/ps2d",
|
||||
"drivers/input/usbhidd",
|
||||
"drivers/thermald",
|
||||
|
||||
"drivers/net/driver-network",
|
||||
"drivers/net/e1000d",
|
||||
@@ -152,4 +153,4 @@ libredox = { path = "../libredox" }
|
||||
redox-scheme = { path = "../redox-scheme" }
|
||||
|
||||
[patch."https://gitlab.redox-os.org/redox-os/relibc.git"]
|
||||
#redox-ioctl = { path = "../../relibc/source/redox-ioctl" }
|
||||
redox-ioctl = { path = "../../relibc/source/redox-ioctl" }
|
||||
|
||||
+8
-1
@@ -48,7 +48,14 @@ fn daemon(daemon: SchemeDaemon) -> anyhow::Result<()> {
|
||||
|
||||
let pid = libredox::call::getpid()?;
|
||||
|
||||
let hw_file = Fd::open("/scheme/audiohw", flag::O_WRONLY | flag::O_CLOEXEC, 0)?;
|
||||
let hw_file = match Fd::open("/scheme/audiohw", flag::O_WRONLY | flag::O_CLOEXEC, 0) {
|
||||
Ok(fd) => fd,
|
||||
Err(err) if err.errno() == syscall::ENODEV => {
|
||||
eprintln!("audiod: no audio hardware detected");
|
||||
return Ok(());
|
||||
}
|
||||
Err(err) => return Err(err).context("failed to open /scheme/audiohw"),
|
||||
};
|
||||
|
||||
let socket = Socket::create().context("failed to create scheme")?;
|
||||
|
||||
|
||||
@@ -1708,7 +1708,9 @@ impl<'a> ProcScheme<'a> {
|
||||
false, // stop_or_continue
|
||||
awoken,
|
||||
) {
|
||||
log::error!("failed to send SIGCHLD to parent PID {ppid:?}: {err}");
|
||||
// EPERM on SIGCHLD to PID 1 is a known kernel limitation.
|
||||
// The procmgr continues correctly after this; downgrade to debug.
|
||||
log::debug!("failed to send SIGCHLD to parent PID {ppid:?}: {err}");
|
||||
}
|
||||
|
||||
if let Some(init_rc) = self.processes.get(&INIT_PID) {
|
||||
|
||||
+139
-1
@@ -128,6 +128,84 @@ impl Drop for PhysmapGuard {
|
||||
let _ = libredox::call::munmap(self.virt as *mut (), self.size);
|
||||
}
|
||||
}
|
||||
|
||||
/// Suspend-to-RAM (S3 sleep state)
|
||||
/// See ACPI 6.1 spec for SLP_TYPa/SLP_TYPb encoding
|
||||
pub fn suspend_to_ram(&self) {
|
||||
log::info!("ACPI: attempting suspend-to-RAM (S3)");
|
||||
let fadt = match self.fadt() {
|
||||
Some(f) => f,
|
||||
None => { log::error!("ACPI S3: missing FADT"); return; }
|
||||
};
|
||||
let pm1a = fadt.pm1a_control_block as u16;
|
||||
if pm1a == 0 {
|
||||
log::error!("ACPI S3: PM1a port is zero");
|
||||
return;
|
||||
}
|
||||
let aml_symbols = self.aml_symbols.read();
|
||||
let s3_name = match acpi::aml::namespace::AmlName::from_str("\\_S3") {
|
||||
Ok(n) => n,
|
||||
Err(e) => { log::error!("ACPI S3: \\_S3 name error: {:?}", e); return; }
|
||||
};
|
||||
let s3 = match &aml_symbols.aml_context {
|
||||
Some(ctx) => match ctx.namespace.lock().get(s3_name) {
|
||||
Ok(s) => s,
|
||||
Err(e) => { log::error!("ACPI S3: \\_S3 not found: {:?}", e); return; }
|
||||
},
|
||||
None => { log::error!("ACPI S3: AML context missing"); return; }
|
||||
};
|
||||
let pkg = match s3.deref() {
|
||||
acpi::aml::object::Object::Package(p) => p,
|
||||
_ => { log::error!("ACPI S3: \\_S3 not a package"); return; }
|
||||
};
|
||||
let slp_typa = match pkg[0].deref() {
|
||||
acpi::aml::object::Object::Integer(i) => *i as u16,
|
||||
_ => { log::error!("ACPI S3: SLP_TYPa not integer"); return; }
|
||||
};
|
||||
let mut val = (1u16 << 13) | (slp_typa & 0x1FFF);
|
||||
log::info!("ACPI S3: writing PM1a=0x{:04X} val=0x{:04X}", pm1a, val);
|
||||
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
||||
{ Pio::<u16>::new(pm1a).write(val); }
|
||||
}
|
||||
|
||||
|
||||
/// Query ACPI battery via \\_SB_.BAT0._BST
|
||||
/// Returns (remaining_capacity_mAh, present_voltage_mV) if available
|
||||
pub fn read_battery_status(&self) -> Option<(u32, u32)> {
|
||||
let aml_symbols = self.aml_symbols.read();
|
||||
let ctx = aml_symbols.aml_context.as_ref()?;
|
||||
let mut ns = ctx.namespace.lock();
|
||||
let bst_name = acpi::aml::namespace::AmlName::from_str("\\_SB_.BAT0._BST").ok()?;
|
||||
let bst = ns.get(bst_name).ok()?;
|
||||
match bst.deref() {
|
||||
acpi::aml::object::Object::Package(p) if p.len() >= 4 => {
|
||||
let cap = match p[1].deref() { acpi::aml::object::Object::Integer(i) => *i as u32, _ => return None };
|
||||
let volt = match p[2].deref() { acpi::aml::object::Object::Integer(i) => *i as u32, _ => return None };
|
||||
Some((cap, volt))
|
||||
}
|
||||
_ => { log::warn!("ACPI: _BST not a valid battery package"); None }
|
||||
}
|
||||
}
|
||||
|
||||
/// Query ACPI battery info via \\_SB_.BAT0._BIF
|
||||
/// Returns (design_capacity_mAh, last_full_capacity_mAh, design_voltage_mV) if available
|
||||
pub fn read_battery_info(&self) -> Option<(u32, u32, u32)> {
|
||||
let aml_symbols = self.aml_symbols.read();
|
||||
let ctx = aml_symbols.aml_context.as_ref()?;
|
||||
let mut ns = ctx.namespace.lock();
|
||||
let bif_name = acpi::aml::namespace::AmlName::from_str("\\_SB_.BAT0._BIF").ok()?;
|
||||
let bif = ns.get(bif_name).ok()?;
|
||||
match bif.deref() {
|
||||
acpi::aml::object::Object::Package(p) if p.len() >= 13 => {
|
||||
let design = match p[1].deref() { acpi::aml::object::Object::Integer(i) => *i as u32, _ => return None };
|
||||
let last = match p[2].deref() { acpi::aml::object::Object::Integer(i) => *i as u32, _ => return None };
|
||||
let volt = match p[4].deref() { acpi::aml::object::Object::Integer(i) => *i as u32, _ => return None };
|
||||
Some((design, last, volt))
|
||||
}
|
||||
_ => { log::warn!("ACPI: _BIF not a valid battery package"); None }
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
@@ -286,7 +364,11 @@ impl AmlSymbols {
|
||||
match self.init(pci_fd) {
|
||||
Ok(()) => (),
|
||||
Err(err) => {
|
||||
log::error!("failed to initialize AML context: {}", err);
|
||||
if pci_fd.is_none() {
|
||||
log::debug!("AML init deferred until PCI registration: {}", err);
|
||||
} else {
|
||||
log::error!("failed to initialize AML context: {}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -442,6 +524,62 @@ impl AcpiContext {
|
||||
.flatten()
|
||||
}
|
||||
|
||||
pub fn evaluate_acpi_method(
|
||||
&mut self,
|
||||
path: &str,
|
||||
method: &str,
|
||||
args: &[u64],
|
||||
) -> Result<Vec<u64>, AmlEvalError> {
|
||||
let full_path = format!("{path}.{method}");
|
||||
let aml_name = AmlName::from_str(&full_path).map_err(|_| AmlEvalError::DeserializationError)?;
|
||||
let args = args
|
||||
.iter()
|
||||
.copied()
|
||||
.map(AmlSerdeValue::Integer)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
match self.aml_eval(aml_name, args)? {
|
||||
AmlSerdeValue::Integer(value) => Ok(vec![value]),
|
||||
AmlSerdeValue::Package { contents } => contents
|
||||
.into_iter()
|
||||
.map(|value| match value {
|
||||
AmlSerdeValue::Integer(value) => Ok(value),
|
||||
_ => Err(AmlEvalError::DeserializationError),
|
||||
})
|
||||
.collect(),
|
||||
_ => Err(AmlEvalError::DeserializationError),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn device_power_on(&mut self, device_path: &str) {
|
||||
match self.evaluate_acpi_method(device_path, "_PS0", &[]) {
|
||||
Ok(values) => {
|
||||
log::debug!("{}._PS0 => {:?}", device_path, values);
|
||||
}
|
||||
Err(error) => {
|
||||
log::warn!("Failed to power on {} with _PS0: {:?}", device_path, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn device_power_off(&mut self, device_path: &str) {
|
||||
match self.evaluate_acpi_method(device_path, "_PS3", &[]) {
|
||||
Ok(values) => {
|
||||
log::debug!("{}._PS3 => {:?}", device_path, values);
|
||||
}
|
||||
Err(error) => {
|
||||
log::warn!("Failed to power off {} with _PS3: {:?}", device_path, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn device_get_performance(&mut self, device_path: &str) -> Result<u64, AmlEvalError> {
|
||||
self.evaluate_acpi_method(device_path, "_PPC", &[])?
|
||||
.into_iter()
|
||||
.next()
|
||||
.ok_or(AmlEvalError::DeserializationError)
|
||||
}
|
||||
|
||||
pub fn init(
|
||||
rxsdt_physaddrs: impl Iterator<Item = u64>,
|
||||
ec: Vec<(RegionSpace, Box<dyn RegionHandler>)>,
|
||||
|
||||
@@ -503,10 +503,14 @@ impl<'sdt> Iterator for DmarRawIter<'sdt> {
|
||||
let len_bytes = <[u8; 2]>::try_from(type_bytes)
|
||||
.expect("expected a 2-byte slice to be convertible to [u8; 2]");
|
||||
|
||||
let ty = u16::from_ne_bytes(type_bytes);
|
||||
let len = u16::from_ne_bytes(len_bytes);
|
||||
let len = u16::from_ne_bytes(len_bytes) as usize;
|
||||
|
||||
if len < 4 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let ty = u16::from_ne_bytes(type_bytes);
|
||||
|
||||
let len = usize::try_from(len).expect("expected u16 to fit within usize");
|
||||
|
||||
if len > remainder.len() {
|
||||
log::warn!("DMAR remapping structure length was smaller than the remaining length of the table.");
|
||||
|
||||
+101
-25
@@ -3,6 +3,7 @@ use std::os::unix::io::AsRawFd;
|
||||
use std::usize;
|
||||
|
||||
use event::{user_data, EventQueue};
|
||||
use log::error;
|
||||
use pcid_interface::PciFunctionHandle;
|
||||
use redox_scheme::scheme::register_sync_scheme;
|
||||
use redox_scheme::Socket;
|
||||
@@ -22,13 +23,28 @@ fn daemon(daemon: daemon::Daemon, pcid_handle: PciFunctionHandle) -> ! {
|
||||
let mut name = pci_config.func.name();
|
||||
name.push_str("_ac97");
|
||||
|
||||
let bar0 = pci_config.func.bars[0].expect_port();
|
||||
let bar1 = pci_config.func.bars[1].expect_port();
|
||||
let bar0 = match pci_config.func.bars[0].try_port() {
|
||||
Ok(port) => port,
|
||||
Err(err) => {
|
||||
error!("ac97d: invalid BAR0: {err}");
|
||||
std::process::exit(1);
|
||||
}
|
||||
};
|
||||
let bar1 = match pci_config.func.bars[1].try_port() {
|
||||
Ok(port) => port,
|
||||
Err(err) => {
|
||||
error!("ac97d: invalid BAR1: {err}");
|
||||
std::process::exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
let irq = pci_config
|
||||
.func
|
||||
.legacy_interrupt_line
|
||||
.expect("ac97d: no legacy interrupts supported");
|
||||
.unwrap_or_else(|| {
|
||||
error!("ac97d: no legacy interrupts supported");
|
||||
std::process::exit(1);
|
||||
});
|
||||
|
||||
println!(" + ac97 {}", pci_config.func.display());
|
||||
|
||||
@@ -40,13 +56,35 @@ fn daemon(daemon: daemon::Daemon, pcid_handle: PciFunctionHandle) -> ! {
|
||||
common::file_level(),
|
||||
);
|
||||
|
||||
common::acquire_port_io_rights().expect("ac97d: failed to set I/O privilege level to Ring 3");
|
||||
if let Err(err) = common::acquire_port_io_rights() {
|
||||
error!("ac97d: failed to set I/O privilege level to Ring 3: {err}");
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
let mut irq_file = irq.irq_handle("ac97d");
|
||||
let mut irq_file = match irq.try_irq_handle("ac97d") {
|
||||
Ok(file) => file,
|
||||
Err(err) => {
|
||||
error!("ac97d: failed to open IRQ handle: {err}");
|
||||
std::process::exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
let socket = Socket::nonblock().expect("ac97d: failed to create socket");
|
||||
let mut device =
|
||||
unsafe { device::Ac97::new(bar0, bar1).expect("ac97d: failed to allocate device") };
|
||||
let socket = match Socket::nonblock() {
|
||||
Ok(socket) => socket,
|
||||
Err(err) => {
|
||||
error!("ac97d: failed to create socket: {err}");
|
||||
std::process::exit(1);
|
||||
}
|
||||
};
|
||||
let mut device = unsafe {
|
||||
match device::Ac97::new(bar0, bar1) {
|
||||
Ok(device) => device,
|
||||
Err(err) => {
|
||||
error!("ac97d: failed to allocate device: {err}");
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
};
|
||||
let mut readiness_based = ReadinessBased::new(&socket, 16);
|
||||
|
||||
user_data! {
|
||||
@@ -56,49 +94,81 @@ fn daemon(daemon: daemon::Daemon, pcid_handle: PciFunctionHandle) -> ! {
|
||||
}
|
||||
}
|
||||
|
||||
let event_queue = EventQueue::<Source>::new().expect("ac97d: Could not create event queue.");
|
||||
let event_queue = match EventQueue::<Source>::new() {
|
||||
Ok(queue) => queue,
|
||||
Err(err) => {
|
||||
error!("ac97d: could not create event queue: {err}");
|
||||
std::process::exit(1);
|
||||
}
|
||||
};
|
||||
event_queue
|
||||
.subscribe(
|
||||
irq_file.as_raw_fd() as usize,
|
||||
Source::Irq,
|
||||
event::EventFlags::READ,
|
||||
)
|
||||
.unwrap();
|
||||
.unwrap_or_else(|err| {
|
||||
error!("ac97d: failed to subscribe IRQ fd: {err}");
|
||||
std::process::exit(1);
|
||||
});
|
||||
event_queue
|
||||
.subscribe(
|
||||
socket.inner().raw(),
|
||||
Source::Scheme,
|
||||
event::EventFlags::READ,
|
||||
)
|
||||
.unwrap();
|
||||
.unwrap_or_else(|err| {
|
||||
error!("ac97d: failed to subscribe scheme fd: {err}");
|
||||
std::process::exit(1);
|
||||
});
|
||||
|
||||
register_sync_scheme(&socket, "audiohw", &mut device)
|
||||
.expect("ac97d: failed to register audiohw scheme to namespace");
|
||||
register_sync_scheme(&socket, "audiohw", &mut device).unwrap_or_else(|err| {
|
||||
error!("ac97d: failed to register audiohw scheme to namespace: {err}");
|
||||
std::process::exit(1);
|
||||
});
|
||||
daemon.ready();
|
||||
|
||||
libredox::call::setrens(0, 0).expect("ac97d: failed to enter null namespace");
|
||||
if let Err(err) = libredox::call::setrens(0, 0) {
|
||||
error!("ac97d: failed to enter null namespace: {err}");
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
let all = [Source::Irq, Source::Scheme];
|
||||
for event in all
|
||||
.into_iter()
|
||||
.chain(event_queue.map(|e| e.expect("ac97d: failed to get next event").user_data))
|
||||
{
|
||||
for event in all.into_iter().chain(event_queue.map(|e| match e {
|
||||
Ok(event) => event.user_data,
|
||||
Err(err) => {
|
||||
error!("ac97d: failed to get next event: {err}");
|
||||
std::process::exit(1);
|
||||
}
|
||||
})) {
|
||||
match event {
|
||||
Source::Irq => {
|
||||
let mut irq = [0; 8];
|
||||
irq_file.read(&mut irq).unwrap();
|
||||
if let Err(err) = irq_file.read(&mut irq) {
|
||||
error!("ac97d: failed to read IRQ file: {err}");
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
if !device.irq() {
|
||||
continue;
|
||||
}
|
||||
irq_file.write(&mut irq).unwrap();
|
||||
if let Err(err) = irq_file.write(&mut irq) {
|
||||
error!("ac97d: failed to acknowledge IRQ: {err}");
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
readiness_based
|
||||
.poll_all_requests(&mut device)
|
||||
.expect("ac97d: failed to poll requests");
|
||||
.unwrap_or_else(|err| {
|
||||
error!("ac97d: failed to poll requests: {err}");
|
||||
std::process::exit(1);
|
||||
});
|
||||
readiness_based
|
||||
.write_responses()
|
||||
.expect("ac97d: failed to write to socket");
|
||||
.unwrap_or_else(|err| {
|
||||
error!("ac97d: failed to write to socket: {err}");
|
||||
std::process::exit(1);
|
||||
});
|
||||
|
||||
/*
|
||||
let next_read = device_irq.next_read();
|
||||
@@ -110,10 +180,16 @@ fn daemon(daemon: daemon::Daemon, pcid_handle: PciFunctionHandle) -> ! {
|
||||
Source::Scheme => {
|
||||
readiness_based
|
||||
.read_and_process_requests(&mut device)
|
||||
.expect("ac97d: failed to read from socket");
|
||||
.unwrap_or_else(|err| {
|
||||
error!("ac97d: failed to read from socket: {err}");
|
||||
std::process::exit(1);
|
||||
});
|
||||
readiness_based
|
||||
.write_responses()
|
||||
.expect("ac97d: failed to write to socket");
|
||||
.unwrap_or_else(|err| {
|
||||
error!("ac97d: failed to write to socket: {err}");
|
||||
std::process::exit(1);
|
||||
});
|
||||
|
||||
/*
|
||||
let next_read = device.borrow().next_read();
|
||||
@@ -125,7 +201,7 @@ fn daemon(daemon: daemon::Daemon, pcid_handle: PciFunctionHandle) -> ! {
|
||||
}
|
||||
}
|
||||
|
||||
std::process::exit(0);
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
|
||||
|
||||
@@ -6,7 +6,7 @@ use std::os::unix::io::AsRawFd;
|
||||
use std::usize;
|
||||
|
||||
use event::{user_data, EventQueue};
|
||||
use pcid_interface::irq_helpers::pci_allocate_interrupt_vector;
|
||||
use pcid_interface::irq_helpers::try_pci_allocate_interrupt_vector;
|
||||
use pcid_interface::PciFunctionHandle;
|
||||
|
||||
pub mod hda;
|
||||
@@ -38,9 +38,19 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! {
|
||||
|
||||
log::info!("IHDA {}", pci_config.func.display());
|
||||
|
||||
if let Err(err) = pci_config.func.bars[0].try_mem() {
|
||||
log::error!("ihdad: invalid BAR0: {err}");
|
||||
std::process::exit(1);
|
||||
}
|
||||
let address = unsafe { pcid_handle.map_bar(0) }.ptr.as_ptr() as usize;
|
||||
|
||||
let irq_file = pci_allocate_interrupt_vector(&mut pcid_handle, "ihdad");
|
||||
let irq_file = match try_pci_allocate_interrupt_vector(&mut pcid_handle, "ihdad") {
|
||||
Ok(irq) => irq,
|
||||
Err(err) => {
|
||||
log::error!("ihdad: failed to allocate interrupt vector: {err}");
|
||||
std::process::exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
{
|
||||
let vend_prod: u32 = ((pci_config.func.full_device_id.vendor_id as u32) << 16)
|
||||
@@ -53,11 +63,28 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! {
|
||||
}
|
||||
}
|
||||
|
||||
let event_queue =
|
||||
EventQueue::<Source>::new().expect("ihdad: Could not create event queue.");
|
||||
let socket = Socket::nonblock().expect("ihdad: failed to create socket");
|
||||
let event_queue = match EventQueue::<Source>::new() {
|
||||
Ok(queue) => queue,
|
||||
Err(err) => {
|
||||
log::error!("ihdad: could not create event queue: {err}");
|
||||
std::process::exit(1);
|
||||
}
|
||||
};
|
||||
let socket = match Socket::nonblock() {
|
||||
Ok(socket) => socket,
|
||||
Err(err) => {
|
||||
log::error!("ihdad: failed to create socket: {err}");
|
||||
std::process::exit(1);
|
||||
}
|
||||
};
|
||||
let mut device = unsafe {
|
||||
hda::IntelHDA::new(address, vend_prod).expect("ihdad: failed to allocate device")
|
||||
match hda::IntelHDA::new(address, vend_prod) {
|
||||
Ok(device) => device,
|
||||
Err(err) => {
|
||||
log::error!("ihdad: failed to allocate device: {err}");
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
};
|
||||
let mut readiness_based = ReadinessBased::new(&socket, 16);
|
||||
|
||||
|
||||
@@ -44,6 +44,7 @@ pub fn setup_logging(
|
||||
Ok(b) => {
|
||||
logger = logger.with_output(b.with_filter(file_level).flush_on_newline(true).build())
|
||||
}
|
||||
Err(error) if error.raw_os_error() == Some(19) => {}
|
||||
Err(error) => eprintln!("Failed to create {logfile_base}.log: {}", error),
|
||||
}
|
||||
|
||||
@@ -61,6 +62,7 @@ pub fn setup_logging(
|
||||
.build(),
|
||||
)
|
||||
}
|
||||
Err(error) if error.raw_os_error() == Some(19) => {}
|
||||
Err(error) => eprintln!("Failed to create {logfile_base}.ansi.log: {}", error),
|
||||
}
|
||||
|
||||
|
||||
@@ -51,5 +51,26 @@ ids = { 0x8086 = [
|
||||
0x56B3, # Pro A60
|
||||
0x56C0, # GPU Flex 170
|
||||
0x56C1, # GPU Flex 140
|
||||
# Alder Lake-S Desktop
|
||||
0x4680, 0x4682, 0x4688, 0x468A, 0x468B,
|
||||
0x4690, 0x4692, 0x4693,
|
||||
# Alder Lake-P Mobile
|
||||
0x46A0, 0x46A1, 0x46A2, 0x46A3, 0x46A6,
|
||||
0x46A8, 0x46AA, 0x462A, 0x4626, 0x4628,
|
||||
0x46B0, 0x46B1, 0x46B2, 0x46B3,
|
||||
0x46C0, 0x46C1, 0x46C2, 0x46C3,
|
||||
# Alder Lake-N Low-Power
|
||||
0x46D0, 0x46D1, 0x46D2, 0x46D3, 0x46D4,
|
||||
# Raptor Lake-S Desktop
|
||||
0xA780, 0xA781, 0xA782, 0xA783,
|
||||
0xA788, 0xA789, 0xA78A, 0xA78B,
|
||||
# Raptor Lake-P Mobile
|
||||
0xA720, 0xA7A0, 0xA7A8, 0xA7AA, 0xA7AB,
|
||||
# Raptor Lake-U Mobile
|
||||
0xA721, 0xA7A1, 0xA7A9, 0xA7AC, 0xA7AD,
|
||||
# Meteor Lake
|
||||
0x7D40, 0x7D45, 0x7D55, 0x7D60, 0x7DD5,
|
||||
# Arrow Lake-H
|
||||
0x7D51, 0x7DD1,
|
||||
] }
|
||||
command = ["ihdgd"]
|
||||
|
||||
@@ -37,9 +37,6 @@ fn daemon(daemon: daemon::Daemon) -> ! {
|
||||
|
||||
//TODO: launch pcid based on backend information?
|
||||
// Must launch after acpid but before probe calls /scheme/acpi/symbols
|
||||
#[allow(deprecated, reason = "we can't yet move this to init")]
|
||||
daemon::Daemon::spawn(process::Command::new("pcid"));
|
||||
|
||||
daemon.ready();
|
||||
|
||||
//TODO: HWD is meant to locate PCI/XHCI/etc devices in ACPI and DeviceTree definitions and start their drivers
|
||||
|
||||
+3
-6
@@ -29,9 +29,6 @@ vendor = 0x1AF4
|
||||
device = 0x1001
|
||||
command = ["/scheme/initfs/lib/drivers/virtio-blkd"]
|
||||
|
||||
[[drivers]]
|
||||
name = "virtio-gpu"
|
||||
class = 3
|
||||
vendor = 0x1AF4
|
||||
device = 0x1050
|
||||
command = ["/scheme/initfs/lib/drivers/virtio-gpud"]
|
||||
# Red Bear full-image graphics hands display-class GPUs to the root filesystem
|
||||
# DRM/KMS stack. Do not claim QEMU virtio-gpu in initfs, or the later root
|
||||
# pcid-spawner cannot pass the PCI function to /usr/bin/redox-drm.
|
||||
|
||||
@@ -97,6 +97,14 @@ enum KeyboardCommandData {
|
||||
const DEFAULT_TIMEOUT: u64 = 50_000;
|
||||
// Reset timeout in microseconds
|
||||
const RESET_TIMEOUT: u64 = 1_000_000;
|
||||
// Maximum bytes to drain during flush (Linux: I8042_BUFFER_SIZE)
|
||||
const FLUSH_LIMIT: usize = 4096;
|
||||
// Controller self-test pass value (Linux: I8042_RET_CTL_TEST)
|
||||
const SELFTEST_PASS: u8 = 0x55;
|
||||
// Controller self-test retries (Linux: 5 attempts)
|
||||
const SELFTEST_RETRIES: usize = 5;
|
||||
// AUX port test pass value (Linux returns 0x00 on success)
|
||||
const AUX_TEST_PASS: u8 = 0x00;
|
||||
|
||||
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
||||
pub struct Ps2 {
|
||||
@@ -263,6 +271,30 @@ impl Ps2 {
|
||||
self.write(command as u8)
|
||||
}
|
||||
|
||||
pub fn set_leds(&mut self, caps: bool, num: bool, scroll: bool) {
|
||||
let mut led_byte = 0u8;
|
||||
if scroll { led_byte |= 1; }
|
||||
if num { led_byte |= 2; }
|
||||
if caps { led_byte |= 4; }
|
||||
if let Err(err) = self.keyboard_command_inner(0xED) {
|
||||
log::debug!("ps2d: LED command 0xED not supported: {:?}", err);
|
||||
return;
|
||||
}
|
||||
match self.read_timeout(DEFAULT_TIMEOUT) {
|
||||
Ok(0xFA) => {
|
||||
if let Err(err) = self.write(led_byte) {
|
||||
log::debug!("ps2d: failed to send LED byte {:02X}: {:?}", led_byte, err);
|
||||
}
|
||||
}
|
||||
Ok(val) => {
|
||||
log::debug!("ps2d: LED command ACK expected 0xFA, got {:02X}", val);
|
||||
}
|
||||
Err(err) => {
|
||||
log::debug!("ps2d: LED command ACK timeout: {:?}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn next(&mut self) -> Option<(bool, u8)> {
|
||||
let status = self.status();
|
||||
if status.contains(StatusFlags::OUTPUT_FULL) {
|
||||
@@ -273,6 +305,50 @@ impl Ps2 {
|
||||
}
|
||||
}
|
||||
|
||||
/// Drain all pending bytes from the controller output buffer.
|
||||
/// Borrowed from Linux i8042_flush(): stale firmware/BIOS bytes can be
|
||||
/// misinterpreted as device responses during initialization.
|
||||
fn flush(&mut self) -> usize {
|
||||
let mut count = 0;
|
||||
while self.status().contains(StatusFlags::OUTPUT_FULL) {
|
||||
if count >= FLUSH_LIMIT {
|
||||
warn!("flush: exceeded limit, controller may be stuck");
|
||||
break;
|
||||
}
|
||||
let data = self.data.read();
|
||||
trace!("flush: discarded {:02X}", data);
|
||||
count += 1;
|
||||
}
|
||||
if count > 0 {
|
||||
debug!("flushed {} stale bytes from controller", count);
|
||||
}
|
||||
count
|
||||
}
|
||||
|
||||
/// Test the AUX (mouse) port via controller command 0xA9.
|
||||
/// Borrowed from Linux: verifies electrical connectivity before
|
||||
/// attempting to talk to the mouse. Returns true if the port passed.
|
||||
fn test_aux_port(&mut self) -> bool {
|
||||
if let Err(err) = self.command(Command::TestSecond) {
|
||||
warn!("aux port test command failed: {:?}", err);
|
||||
return false;
|
||||
}
|
||||
match self.read() {
|
||||
Ok(AUX_TEST_PASS) => {
|
||||
debug!("aux port test passed");
|
||||
true
|
||||
}
|
||||
Ok(val) => {
|
||||
warn!("aux port test failed: {:02X}", val);
|
||||
false
|
||||
}
|
||||
Err(err) => {
|
||||
warn!("aux port test read timeout: {:?}", err);
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn init_keyboard(&mut self) -> Result<(), Error> {
|
||||
let mut b;
|
||||
|
||||
@@ -310,66 +386,125 @@ impl Ps2 {
|
||||
}
|
||||
|
||||
pub fn init(&mut self) -> Result<(), Error> {
|
||||
// Linux i8042_controller_check(): verify controller is present by
|
||||
// flushing any stale data. A stuck output buffer means no controller.
|
||||
self.flush();
|
||||
|
||||
// Bare-metal controllers may be slow after firmware handoff.
|
||||
// Give the controller a moment to finish POST before sending commands.
|
||||
std::thread::sleep(std::time::Duration::from_millis(50));
|
||||
|
||||
{
|
||||
// Disable devices
|
||||
self.command(Command::DisableFirst)?;
|
||||
self.command(Command::DisableSecond)?;
|
||||
// Disable both ports first — use retry because the controller
|
||||
// may still be settling or temporarily unresponsive.
|
||||
// Failure here is non-fatal: we continue and attempt the rest
|
||||
// of initialization. A truly absent controller will fail later
|
||||
// at self-test or keyboard reset.
|
||||
if let Err(err) = self.retry(
|
||||
format_args!("disable first port"),
|
||||
3,
|
||||
|x| x.command(Command::DisableFirst),
|
||||
) {
|
||||
warn!("disable first port failed: {:?}", err);
|
||||
}
|
||||
if let Err(err) = self.retry(
|
||||
format_args!("disable second port"),
|
||||
3,
|
||||
|x| x.command(Command::DisableSecond),
|
||||
) {
|
||||
warn!("disable second port failed: {:?}", err);
|
||||
}
|
||||
}
|
||||
|
||||
// Disable clocks, disable interrupts, and disable translate
|
||||
// Flush again after disabling — firmware may have queued more bytes
|
||||
self.flush();
|
||||
|
||||
// Linux i8042_controller_init() step 1: write a known-safe config
|
||||
// (interrupts off, both ports disabled) so stale config can't cause
|
||||
// spurious interrupts during the rest of init.
|
||||
{
|
||||
// Since the default config may have interrupts enabled, and the kernel may eat up
|
||||
// our data in that case, we will write a config without reading the current one
|
||||
let config = ConfigFlags::POST_PASSED
|
||||
| ConfigFlags::FIRST_DISABLED
|
||||
| ConfigFlags::SECOND_DISABLED;
|
||||
self.set_config(config)?;
|
||||
}
|
||||
|
||||
// The keyboard seems to still collect bytes even when we disable
|
||||
// the port, so we must disable the keyboard too
|
||||
// Linux i8042_controller_selftest(): retry up to 5 times with delay.
|
||||
// "On some really fragile systems this does not take the first time."
|
||||
{
|
||||
let mut passed = false;
|
||||
for attempt in 0..SELFTEST_RETRIES {
|
||||
if let Err(err) = self.command(Command::TestController) {
|
||||
warn!("self-test command failed (attempt {}): {:?}", attempt + 1, err);
|
||||
continue;
|
||||
}
|
||||
match self.read() {
|
||||
Ok(SELFTEST_PASS) => {
|
||||
passed = true;
|
||||
break;
|
||||
}
|
||||
Ok(val) => {
|
||||
warn!(
|
||||
"self-test unexpected value {:02X} (attempt {}/{})",
|
||||
val,
|
||||
attempt + 1,
|
||||
SELFTEST_RETRIES
|
||||
);
|
||||
}
|
||||
Err(err) => {
|
||||
warn!("self-test read timeout (attempt {}): {:?}", attempt + 1, err);
|
||||
}
|
||||
}
|
||||
// Linux: msleep(50) between retries
|
||||
std::thread::sleep(std::time::Duration::from_millis(50));
|
||||
}
|
||||
if !passed {
|
||||
// Linux on x86: "giving up on controller selftest, continuing anyway"
|
||||
warn!("controller self-test did not pass after {} attempts, continuing", SELFTEST_RETRIES);
|
||||
}
|
||||
}
|
||||
|
||||
// Flush any bytes the self-test may have left behind
|
||||
self.flush();
|
||||
|
||||
// Linux i8042_controller_init() step 2: set keyboard defaults
|
||||
// (disable scanning so keyboard doesn't send scancodes during init)
|
||||
self.retry(format_args!("keyboard defaults"), 4, |x| {
|
||||
// Set defaults and disable scanning
|
||||
let b = x.keyboard_command(KeyboardCommand::SetDefaultsDisable)?;
|
||||
if b != 0xFA {
|
||||
error!("keyboard failed to set defaults: {:02X}", b);
|
||||
return Err(Error::CommandRetry);
|
||||
}
|
||||
|
||||
Ok(b)
|
||||
})?;
|
||||
|
||||
{
|
||||
// Perform the self test
|
||||
self.command(Command::TestController)?;
|
||||
let r = self.read()?;
|
||||
if r != 0x55 {
|
||||
warn!("self test unexpected value: {:02X}", r);
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize keyboard
|
||||
if let Err(err) = self.init_keyboard() {
|
||||
error!("failed to initialize keyboard: {:?}", err);
|
||||
return Err(err);
|
||||
}
|
||||
|
||||
// Enable second device
|
||||
let enable_mouse = match self.command(Command::EnableSecond) {
|
||||
Ok(()) => true,
|
||||
Err(err) => {
|
||||
error!("failed to initialize mouse: {:?}", err);
|
||||
false
|
||||
// Linux: test AUX port (command 0xA9) before enabling.
|
||||
// Skips mouse init entirely if the port is not electrically present.
|
||||
let aux_ok = self.test_aux_port();
|
||||
|
||||
// Enable second device (mouse) only if AUX port tested OK
|
||||
let enable_mouse = if aux_ok {
|
||||
match self.command(Command::EnableSecond) {
|
||||
Ok(()) => true,
|
||||
Err(err) => {
|
||||
warn!("failed to enable aux port after test passed: {:?}", err);
|
||||
false
|
||||
}
|
||||
}
|
||||
} else {
|
||||
info!("skipping mouse init: aux port test did not pass");
|
||||
false
|
||||
};
|
||||
|
||||
{
|
||||
// Enable keyboard data reporting
|
||||
// Use inner function to prevent retries
|
||||
// Response is ignored since scanning is now on
|
||||
if let Err(err) = self.keyboard_command_inner(KeyboardCommand::EnableReporting as u8) {
|
||||
error!("failed to initialize keyboard reporting: {:?}", err);
|
||||
//TODO: fix by using interrupts?
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ use std::process;
|
||||
|
||||
use common::acquire_port_io_rights;
|
||||
use event::{user_data, EventQueue};
|
||||
use inputd::ProducerHandle;
|
||||
use inputd::InputProducer;
|
||||
|
||||
use crate::state::Ps2d;
|
||||
|
||||
@@ -31,7 +31,8 @@ fn daemon(daemon: daemon::Daemon) -> ! {
|
||||
|
||||
acquire_port_io_rights().expect("ps2d: failed to get I/O permission");
|
||||
|
||||
let input = ProducerHandle::new().expect("ps2d: failed to open input producer");
|
||||
let keyboard_input = InputProducer::new_named_or_fallback("ps2-keyboard").expect("ps2d: failed to open keyboard input");
|
||||
let mouse_input = InputProducer::new_named_or_fallback("ps2-mouse").expect("ps2d: failed to open mouse input");
|
||||
|
||||
user_data! {
|
||||
enum Source {
|
||||
@@ -97,7 +98,7 @@ fn daemon(daemon: daemon::Daemon) -> ! {
|
||||
"ps2d: registered producer handle, listening on serio/0 (keyboard) and serio/1 (mouse)"
|
||||
);
|
||||
|
||||
let mut ps2d = Ps2d::new(input, time_file);
|
||||
let mut ps2d = Ps2d::new(keyboard_input, mouse_input, time_file);
|
||||
|
||||
let mut data = [0; 256];
|
||||
for event in event_queue.map(|e| e.expect("ps2d: failed to get next event").user_data) {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
use crate::controller::Ps2;
|
||||
use std::time::Duration;
|
||||
|
||||
pub const RESET_RETRIES: usize = 10;
|
||||
pub const RESET_TIMEOUT: Duration = Duration::from_millis(1000);
|
||||
pub const RESET_RETRIES: usize = 3;
|
||||
pub const RESET_TIMEOUT: Duration = Duration::from_millis(250);
|
||||
pub const COMMAND_TIMEOUT: Duration = Duration::from_millis(100);
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
@@ -61,6 +61,10 @@ impl MouseTx {
|
||||
if data == 0xFA {
|
||||
self.write_i += 1;
|
||||
self.try_write(ps2)?;
|
||||
} else if data == 0xFE {
|
||||
// PS/2 RESEND: mouse asks us to resend the current command byte
|
||||
log::debug!("mouse requested resend for byte {:02X}, resending", self.write.get(self.write_i).unwrap_or(&0));
|
||||
self.try_write(ps2)?;
|
||||
} else {
|
||||
log::error!("unknown mouse response {:02X}", data);
|
||||
return Err(());
|
||||
@@ -260,7 +264,8 @@ impl MouseState {
|
||||
MouseResult::Timeout(COMMAND_TIMEOUT)
|
||||
} else {
|
||||
log::warn!("unknown mouse response {:02X} after reset", data);
|
||||
self.reset(ps2)
|
||||
*self = MouseState::None;
|
||||
MouseResult::None
|
||||
}
|
||||
}
|
||||
MouseState::Bat => {
|
||||
@@ -352,12 +357,14 @@ impl MouseState {
|
||||
self.reset(ps2)
|
||||
}
|
||||
MouseState::Reset => {
|
||||
log::warn!("timeout waiting for mouse reset");
|
||||
self.reset(ps2)
|
||||
log::debug!("timeout waiting for mouse reset, fast-failing");
|
||||
*self = MouseState::None;
|
||||
MouseResult::None
|
||||
}
|
||||
MouseState::Bat => {
|
||||
log::warn!("timeout waiting for BAT completion");
|
||||
self.reset(ps2)
|
||||
log::debug!("timeout waiting for BAT completion, fast-failing");
|
||||
*self = MouseState::None;
|
||||
MouseResult::None
|
||||
}
|
||||
MouseState::IdentifyTouchpad { .. } => {
|
||||
//TODO: retry?
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use inputd::ProducerHandle;
|
||||
use inputd::InputProducer;
|
||||
use log::{error, warn};
|
||||
use orbclient::{ButtonEvent, KeyEvent, MouseEvent, MouseRelativeEvent, ScrollEvent};
|
||||
use std::{
|
||||
@@ -44,7 +44,8 @@ pub struct Ps2d {
|
||||
ps2: Ps2,
|
||||
vmmouse: bool,
|
||||
vmmouse_relative: bool,
|
||||
input: ProducerHandle,
|
||||
keyboard_input: InputProducer,
|
||||
mouse_input: InputProducer,
|
||||
time_file: File,
|
||||
extended: bool,
|
||||
mouse_x: i32,
|
||||
@@ -56,12 +57,18 @@ pub struct Ps2d {
|
||||
mouse_timeout: Option<TimeSpec>,
|
||||
packets: [u8; 4],
|
||||
packet_i: usize,
|
||||
caps_lock: bool,
|
||||
num_lock: bool,
|
||||
scroll_lock: bool,
|
||||
leds_dirty: bool,
|
||||
}
|
||||
|
||||
impl Ps2d {
|
||||
pub fn new(input: ProducerHandle, time_file: File) -> Self {
|
||||
pub fn new(keyboard_input: InputProducer, mouse_input: InputProducer, time_file: File) -> Self {
|
||||
let mut ps2 = Ps2::new();
|
||||
ps2.init().expect("failed to initialize");
|
||||
if let Err(err) = ps2.init() {
|
||||
log::error!("ps2d: controller init failed: {:?}", err);
|
||||
}
|
||||
|
||||
// FIXME add an option for orbital to disable this when an app captures the mouse.
|
||||
let vmmouse_relative = false;
|
||||
@@ -77,7 +84,8 @@ impl Ps2d {
|
||||
ps2,
|
||||
vmmouse,
|
||||
vmmouse_relative,
|
||||
input,
|
||||
keyboard_input,
|
||||
mouse_input,
|
||||
time_file,
|
||||
extended: false,
|
||||
mouse_x: 0,
|
||||
@@ -89,6 +97,10 @@ impl Ps2d {
|
||||
mouse_timeout: None,
|
||||
packets: [0; 4],
|
||||
packet_i: 0,
|
||||
caps_lock: false,
|
||||
num_lock: true,
|
||||
scroll_lock: false,
|
||||
leds_dirty: true,
|
||||
};
|
||||
|
||||
if !this.vmmouse {
|
||||
@@ -96,6 +108,12 @@ impl Ps2d {
|
||||
this.handle_mouse(None);
|
||||
}
|
||||
|
||||
// Flush initial LED state (Num Lock on by default)
|
||||
if this.leds_dirty {
|
||||
this.leds_dirty = false;
|
||||
this.ps2.set_leds(this.caps_lock, this.num_lock, this.scroll_lock);
|
||||
}
|
||||
|
||||
this
|
||||
}
|
||||
|
||||
@@ -272,8 +290,21 @@ impl Ps2d {
|
||||
}
|
||||
};
|
||||
|
||||
if scancode != 0 && pressed {
|
||||
match scancode {
|
||||
orbclient::K_CAPS => { self.caps_lock = !self.caps_lock; self.leds_dirty = true; },
|
||||
orbclient::K_NUM => { self.num_lock = !self.num_lock; self.leds_dirty = true; },
|
||||
orbclient::K_SCROLL => { self.scroll_lock = !self.scroll_lock; self.leds_dirty = true; },
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
if self.leds_dirty {
|
||||
self.leds_dirty = false;
|
||||
self.ps2.set_leds(self.caps_lock, self.num_lock, self.scroll_lock);
|
||||
}
|
||||
|
||||
if scancode != 0 {
|
||||
self.input
|
||||
self.keyboard_input
|
||||
.write_event(
|
||||
KeyEvent {
|
||||
character: '\0',
|
||||
@@ -304,7 +335,7 @@ impl Ps2d {
|
||||
|
||||
if self.vmmouse_relative {
|
||||
if dx != 0 || dy != 0 {
|
||||
self.input
|
||||
self.mouse_input
|
||||
.write_event(
|
||||
MouseRelativeEvent {
|
||||
dx: dx as i32,
|
||||
@@ -320,14 +351,14 @@ impl Ps2d {
|
||||
if x != self.mouse_x || y != self.mouse_y {
|
||||
self.mouse_x = x;
|
||||
self.mouse_y = y;
|
||||
self.input
|
||||
self.mouse_input
|
||||
.write_event(MouseEvent { x, y }.to_event())
|
||||
.expect("ps2d: failed to write mouse event");
|
||||
}
|
||||
};
|
||||
|
||||
if dz != 0 {
|
||||
self.input
|
||||
self.mouse_input
|
||||
.write_event(
|
||||
ScrollEvent {
|
||||
x: 0,
|
||||
@@ -348,7 +379,7 @@ impl Ps2d {
|
||||
self.mouse_left = left;
|
||||
self.mouse_middle = middle;
|
||||
self.mouse_right = right;
|
||||
self.input
|
||||
self.mouse_input
|
||||
.write_event(
|
||||
ButtonEvent {
|
||||
left,
|
||||
@@ -441,13 +472,13 @@ impl Ps2d {
|
||||
}
|
||||
|
||||
if dx != 0 || dy != 0 {
|
||||
self.input
|
||||
self.mouse_input
|
||||
.write_event(MouseRelativeEvent { dx, dy }.to_event())
|
||||
.expect("ps2d: failed to write mouse event");
|
||||
}
|
||||
|
||||
if dz != 0 {
|
||||
self.input
|
||||
self.mouse_input
|
||||
.write_event(ScrollEvent { x: 0, y: dz }.to_event())
|
||||
.expect("ps2d: failed to write scroll event");
|
||||
}
|
||||
@@ -462,7 +493,7 @@ impl Ps2d {
|
||||
self.mouse_left = left;
|
||||
self.mouse_middle = middle;
|
||||
self.mouse_right = right;
|
||||
self.input
|
||||
self.mouse_input
|
||||
.write_event(
|
||||
ButtonEvent {
|
||||
left,
|
||||
|
||||
+275
-17
@@ -1,5 +1,5 @@
|
||||
use std::fs::{File, OpenOptions};
|
||||
use std::io::{self, Read, Write};
|
||||
use std::io::{self, ErrorKind, Read, Write};
|
||||
use std::mem::size_of;
|
||||
use std::os::fd::{AsFd, AsRawFd, BorrowedFd, FromRawFd, RawFd};
|
||||
use std::os::unix::fs::OpenOptionsExt;
|
||||
@@ -31,6 +31,24 @@ unsafe fn any_as_u8_slice_mut<T: Sized>(p: &mut T) -> &mut [u8] {
|
||||
slice::from_raw_parts_mut((p as *mut T) as *mut u8, size_of::<T>())
|
||||
}
|
||||
|
||||
fn validate_input_name(kind: &str, name: &str) -> io::Result<()> {
|
||||
if name.is_empty() {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::InvalidInput,
|
||||
format!("input {kind} name must not be empty"),
|
||||
));
|
||||
}
|
||||
|
||||
if name.contains('/') {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::InvalidInput,
|
||||
format!("input {kind} name must not contain '/'"),
|
||||
));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub struct ConsumerHandle(File);
|
||||
|
||||
pub enum ConsumerHandleEvent<'a> {
|
||||
@@ -64,25 +82,53 @@ impl ConsumerHandle {
|
||||
let fd = self.0.as_raw_fd();
|
||||
let written = libredox::call::fpath(fd as usize, &mut buffer)?;
|
||||
|
||||
assert!(written <= buffer.len());
|
||||
if written > buffer.len() {
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::InvalidData,
|
||||
"inputd: display path exceeded buffer size",
|
||||
));
|
||||
}
|
||||
|
||||
let mut display_path = PathBuf::from(
|
||||
std::str::from_utf8(&buffer[..written])
|
||||
.expect("init: display path UTF-8 check failed")
|
||||
.to_owned(),
|
||||
);
|
||||
display_path.set_file_name(format!(
|
||||
"v2/{}",
|
||||
display_path.file_name().unwrap().to_str().unwrap()
|
||||
));
|
||||
let display_path = display_path.to_str().unwrap();
|
||||
let path_str = std::str::from_utf8(&buffer[..written]).map_err(|e| {
|
||||
io::Error::new(
|
||||
io::ErrorKind::InvalidData,
|
||||
format!("inputd: display path is not valid UTF-8: {e}"),
|
||||
)
|
||||
})?;
|
||||
let mut display_path = PathBuf::from(path_str.to_owned());
|
||||
|
||||
let file_name = display_path
|
||||
.file_name()
|
||||
.and_then(|n| n.to_str())
|
||||
.ok_or_else(|| {
|
||||
io::Error::new(
|
||||
io::ErrorKind::InvalidData,
|
||||
format!(
|
||||
"inputd: display path has no valid file name: {}",
|
||||
display_path.display()
|
||||
),
|
||||
)
|
||||
})?;
|
||||
display_path.set_file_name(format!("v2/{file_name}"));
|
||||
let display_path_str = display_path.to_str().ok_or_else(|| {
|
||||
io::Error::new(
|
||||
io::ErrorKind::InvalidData,
|
||||
format!(
|
||||
"inputd: constructed display path is not valid UTF-8: {}",
|
||||
display_path.display()
|
||||
),
|
||||
)
|
||||
})?;
|
||||
|
||||
let display_file =
|
||||
libredox::call::open(display_path, (O_CLOEXEC | O_NONBLOCK | O_RDWR) as _, 0)
|
||||
libredox::call::open(display_path_str, (O_CLOEXEC | O_NONBLOCK | O_RDWR) as _, 0)
|
||||
.map(|socket| unsafe { File::from_raw_fd(socket as RawFd) })
|
||||
.unwrap_or_else(|err| {
|
||||
panic!("failed to open display {}: {}", display_path, err);
|
||||
});
|
||||
.map_err(|err| {
|
||||
io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
format!("inputd: failed to open display {display_path_str}: {err}"),
|
||||
)
|
||||
})?;
|
||||
|
||||
Ok(display_file)
|
||||
}
|
||||
@@ -152,8 +198,12 @@ impl DisplayHandle {
|
||||
|
||||
if nread == 0 {
|
||||
Ok(None)
|
||||
} else if nread != size_of::<VtEvent>() {
|
||||
Err(io::Error::new(
|
||||
io::ErrorKind::InvalidData,
|
||||
format!("inputd: partial vt event read: got {nread}, expected {}", size_of::<VtEvent>()),
|
||||
))
|
||||
} else {
|
||||
assert_eq!(nread, size_of::<VtEvent>());
|
||||
Ok(Some(event))
|
||||
}
|
||||
}
|
||||
@@ -197,6 +247,160 @@ pub struct VtEvent {
|
||||
pub vt: usize,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
#[repr(C)]
|
||||
pub struct HotplugEventHeader {
|
||||
pub kind: u32,
|
||||
pub device_id: u32,
|
||||
pub name_len: u32,
|
||||
pub _reserved: u32,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct HotplugEvent {
|
||||
pub kind: u32,
|
||||
pub device_id: u32,
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
/// Handle for opening a named producer on the input scheme.
|
||||
/// Opens /scheme/input/producer/{name}
|
||||
pub struct NamedProducerHandle {
|
||||
fd: File,
|
||||
}
|
||||
|
||||
impl NamedProducerHandle {
|
||||
pub fn new(name: &str) -> io::Result<Self> {
|
||||
validate_input_name("producer", name)?;
|
||||
|
||||
let path = format!("/scheme/input/producer/{name}");
|
||||
File::open(path).map(|fd| Self { fd })
|
||||
}
|
||||
|
||||
pub fn write_event(&mut self, event: &orbclient::Event) -> io::Result<()> {
|
||||
self.fd.write(unsafe { any_as_u8_slice(event) })?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DeviceConsumerHandle {
|
||||
fd: File,
|
||||
}
|
||||
|
||||
impl DeviceConsumerHandle {
|
||||
pub fn new(device_name: &str) -> io::Result<Self> {
|
||||
validate_input_name("device", device_name)?;
|
||||
|
||||
let fd = OpenOptions::new()
|
||||
.read(true)
|
||||
.custom_flags(O_NONBLOCK as i32)
|
||||
.open(format!("/scheme/input/{device_name}"))?;
|
||||
|
||||
Ok(Self { fd })
|
||||
}
|
||||
|
||||
pub fn event_handle(&self) -> BorrowedFd<'_> {
|
||||
self.fd.as_fd()
|
||||
}
|
||||
|
||||
pub fn read_event(&mut self) -> io::Result<Option<orbclient::Event>> {
|
||||
let mut raw = [0_u8; size_of::<orbclient::Event>()];
|
||||
|
||||
match self.fd.read(&mut raw) {
|
||||
Ok(0) => Ok(None),
|
||||
Ok(read) => {
|
||||
assert_eq!(read, raw.len());
|
||||
Ok(Some(unsafe {
|
||||
core::ptr::read_unaligned(raw.as_ptr().cast::<orbclient::Event>())
|
||||
}))
|
||||
}
|
||||
Err(err) if err.kind() == ErrorKind::WouldBlock => Ok(None),
|
||||
Err(err) => Err(err),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct HotplugHandle {
|
||||
fd: File,
|
||||
partial: Vec<u8>,
|
||||
}
|
||||
|
||||
impl HotplugHandle {
|
||||
pub fn new() -> io::Result<Self> {
|
||||
let fd = OpenOptions::new()
|
||||
.read(true)
|
||||
.custom_flags(O_NONBLOCK as i32)
|
||||
.open("/scheme/input/events")?;
|
||||
|
||||
Ok(Self {
|
||||
fd,
|
||||
partial: Vec::new(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn event_handle(&self) -> BorrowedFd<'_> {
|
||||
self.fd.as_fd()
|
||||
}
|
||||
|
||||
pub fn read_event(&mut self) -> io::Result<Option<HotplugEvent>> {
|
||||
let mut buf = [0_u8; 1024];
|
||||
|
||||
loop {
|
||||
if let Some(event) = Self::try_parse_event(&mut self.partial)? {
|
||||
return Ok(Some(event));
|
||||
}
|
||||
|
||||
match self.fd.read(&mut buf) {
|
||||
Ok(0) => return Ok(None),
|
||||
Ok(read) => self.partial.extend_from_slice(&buf[..read]),
|
||||
Err(err) if err.kind() == ErrorKind::WouldBlock => return Ok(None),
|
||||
Err(err) => return Err(err),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn try_parse_event(partial: &mut Vec<u8>) -> io::Result<Option<HotplugEvent>> {
|
||||
if partial.len() < size_of::<HotplugEventHeader>() {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let header =
|
||||
unsafe { core::ptr::read_unaligned(partial.as_ptr().cast::<HotplugEventHeader>()) };
|
||||
let name_len = usize::try_from(header.name_len).map_err(|_| {
|
||||
io::Error::new(
|
||||
io::ErrorKind::InvalidData,
|
||||
"invalid input hotplug name length",
|
||||
)
|
||||
})?;
|
||||
let total_len = size_of::<HotplugEventHeader>()
|
||||
.checked_add(name_len)
|
||||
.ok_or_else(|| {
|
||||
io::Error::new(io::ErrorKind::InvalidData, "input hotplug event too large")
|
||||
})?;
|
||||
|
||||
if partial.len() < total_len {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let name = std::str::from_utf8(&partial[size_of::<HotplugEventHeader>()..total_len])
|
||||
.map_err(|_| {
|
||||
io::Error::new(
|
||||
io::ErrorKind::InvalidData,
|
||||
"input hotplug name is not UTF-8",
|
||||
)
|
||||
})?
|
||||
.to_owned();
|
||||
|
||||
partial.drain(..total_len);
|
||||
|
||||
Ok(Some(HotplugEvent {
|
||||
kind: header.kind,
|
||||
device_id: header.device_id,
|
||||
name,
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ProducerHandle(File);
|
||||
|
||||
impl ProducerHandle {
|
||||
@@ -210,4 +414,58 @@ impl ProducerHandle {
|
||||
"write_event: short write ({} != {})", written, std::mem::size_of::<orbclient::Event>());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn query_keymap_char(&self, scancode: u8, shift: bool, altgr: bool) -> Option<char> {
|
||||
use std::io::Read;
|
||||
let path = format!("/scheme/keymap/translate/{}/{}/{}", scancode, shift as u8, altgr as u8);
|
||||
let mut f = match std::fs::File::open(&path) {
|
||||
Ok(f) => f,
|
||||
Err(_) => return None,
|
||||
};
|
||||
let mut buf = [0u8; 4];
|
||||
match f.read(&mut buf) {
|
||||
Ok(n) if n > 0 => {
|
||||
let s = std::str::from_utf8(&buf[..n]).ok()?;
|
||||
s.chars().next()
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn send_led_state(caps: bool, num: bool, scroll: bool) -> io::Result<()> {
|
||||
let led_byte = (caps as u8) | ((num as u8) << 1) | ((scroll as u8) << 2);
|
||||
let path = format!("/scheme/input/control/leds/{}", led_byte);
|
||||
std::fs::write(&path, &[])?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Convenience wrapper that tries a named producer first,
|
||||
/// falling back to the legacy anonymous producer on failure.
|
||||
pub enum InputProducer {
|
||||
Named(NamedProducerHandle),
|
||||
Legacy(ProducerHandle),
|
||||
}
|
||||
|
||||
impl InputProducer {
|
||||
/// Open a named producer (`/scheme/input/producer/{name}`).
|
||||
/// Falls back to the legacy anonymous producer on failure.
|
||||
pub fn new_named_or_fallback(name: &str) -> io::Result<Self> {
|
||||
match NamedProducerHandle::new(name) {
|
||||
Ok(named) => Ok(InputProducer::Named(named)),
|
||||
Err(_) => ProducerHandle::new().map(InputProducer::Legacy),
|
||||
}
|
||||
}
|
||||
|
||||
/// Open the legacy anonymous producer directly.
|
||||
pub fn new_legacy() -> io::Result<Self> {
|
||||
ProducerHandle::new().map(InputProducer::Legacy)
|
||||
}
|
||||
|
||||
pub fn write_event(&mut self, event: orbclient::Event) -> io::Result<()> {
|
||||
match self {
|
||||
InputProducer::Named(h) => h.write_event(&event),
|
||||
InputProducer::Legacy(h) => h.write_event(event),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ use std::{cmp, mem, ptr, slice, thread, time};
|
||||
|
||||
use driver_network::NetworkAdapter;
|
||||
|
||||
use syscall::error::Result;
|
||||
use syscall::error::{Error, Result, EIO};
|
||||
|
||||
use common::dma::Dma;
|
||||
|
||||
@@ -207,11 +207,10 @@ impl NetworkAdapter for Intel8254x {
|
||||
}
|
||||
|
||||
fn dma_array<T, const N: usize>() -> Result<[Dma<T>; N]> {
|
||||
Ok((0..N)
|
||||
let vec: Vec<Dma<T>> = (0..N)
|
||||
.map(|_| Ok(unsafe { Dma::zeroed()?.assume_init() }))
|
||||
.collect::<Result<Vec<_>>>()?
|
||||
.try_into()
|
||||
.unwrap_or_else(|_| unreachable!()))
|
||||
.collect::<Result<Vec<_>>>()?;
|
||||
vec.try_into().map_err(|_| Error::new(EIO))
|
||||
}
|
||||
impl Intel8254x {
|
||||
pub unsafe fn new(base: usize) -> Result<Self> {
|
||||
|
||||
@@ -3,6 +3,7 @@ use std::os::unix::io::AsRawFd;
|
||||
|
||||
use driver_network::NetworkScheme;
|
||||
use event::{user_data, EventQueue};
|
||||
use pcid_interface::irq_helpers::pci_allocate_interrupt_vector;
|
||||
use pcid_interface::PciFunctionHandle;
|
||||
|
||||
pub mod device;
|
||||
@@ -25,14 +26,11 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! {
|
||||
common::file_level(),
|
||||
);
|
||||
|
||||
let irq = pci_config
|
||||
.func
|
||||
.legacy_interrupt_line
|
||||
.expect("e1000d: no legacy interrupts supported");
|
||||
let irq_vector = pci_allocate_interrupt_vector(&mut pcid_handle, "e1000d");
|
||||
|
||||
log::info!("E1000 {}", pci_config.func.display());
|
||||
|
||||
let mut irq_file = irq.irq_handle("e1000d");
|
||||
let irq_file = irq_vector.irq_handle();
|
||||
|
||||
let address = unsafe { pcid_handle.map_bar(0) }.ptr.as_ptr() as usize;
|
||||
|
||||
@@ -53,9 +51,11 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! {
|
||||
|
||||
let event_queue = EventQueue::<Source>::new().expect("e1000d: failed to create event queue");
|
||||
|
||||
let irq_fd = irq_vector.irq_handle().as_raw_fd();
|
||||
|
||||
event_queue
|
||||
.subscribe(
|
||||
irq_file.as_raw_fd() as usize,
|
||||
irq_fd as usize,
|
||||
Source::Irq,
|
||||
event::EventFlags::READ,
|
||||
)
|
||||
@@ -70,15 +70,17 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! {
|
||||
|
||||
libredox::call::setrens(0, 0).expect("e1000d: failed to enter null namespace");
|
||||
|
||||
scheme.tick().unwrap();
|
||||
scheme.tick().expect("e1000d: tick failed");
|
||||
|
||||
let mut irq_file = irq_vector.irq_handle();
|
||||
|
||||
for event in event_queue.map(|e| e.expect("e1000d: failed to get event")) {
|
||||
match event.user_data {
|
||||
Source::Irq => {
|
||||
let mut irq = [0; 8];
|
||||
irq_file.read(&mut irq).unwrap();
|
||||
irq_file.read(&mut irq).expect("e1000d: IRQ read failed");
|
||||
if unsafe { scheme.adapter().irq() } {
|
||||
irq_file.write(&mut irq).unwrap();
|
||||
irq_file.write(&mut irq).expect("e1000d: IRQ ack failed");
|
||||
|
||||
scheme.tick().expect("e1000d: failed to handle IRQ")
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ edition = "2021"
|
||||
[dependencies]
|
||||
bitflags.workspace = true
|
||||
libredox.workspace = true
|
||||
log.workspace = true
|
||||
redox_event.workspace = true
|
||||
redox_syscall.workspace = true
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ use std::time::{Duration, Instant};
|
||||
use std::{cmp, mem, ptr, slice, thread};
|
||||
|
||||
use driver_network::NetworkAdapter;
|
||||
use syscall::error::Result;
|
||||
use syscall::error::{Error, Result, EIO};
|
||||
|
||||
use common::dma::Dma;
|
||||
|
||||
@@ -45,7 +45,12 @@ impl NetworkAdapter for Intel8259x {
|
||||
|
||||
if (status & IXGBE_RXDADV_STAT_DD) != 0 {
|
||||
if (status & IXGBE_RXDADV_STAT_EOP) == 0 {
|
||||
panic!("increase buffer size or decrease MTU")
|
||||
log::error!("ixgbed: received fragmented packet, skipping descriptor");
|
||||
desc.read.pkt_addr = self.receive_buffer[self.receive_index].physical() as u64;
|
||||
desc.read.hdr_addr = 0;
|
||||
self.write_reg(IXGBE_RDT(0), self.receive_index as u32);
|
||||
self.receive_index = wrap_ring(self.receive_index, self.receive_ring.len());
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let data = unsafe {
|
||||
@@ -132,13 +137,25 @@ impl Intel8259x {
|
||||
.map(|_| Ok(unsafe { Dma::zeroed()?.assume_init() }))
|
||||
.collect::<Result<Vec<_>>>()?
|
||||
.try_into()
|
||||
.unwrap_or_else(|_| unreachable!()),
|
||||
.map_err(|v: Vec<_>| {
|
||||
log::error!(
|
||||
"ixgbed: internal error: DMA buffer array conversion failed (got {} items, expected 32)",
|
||||
v.len()
|
||||
);
|
||||
Error::new(EIO)
|
||||
})?,
|
||||
receive_ring: unsafe { Dma::zeroed()?.assume_init() },
|
||||
transmit_buffer: (0..32)
|
||||
.map(|_| Ok(unsafe { Dma::zeroed()?.assume_init() }))
|
||||
.collect::<Result<Vec<_>>>()?
|
||||
.try_into()
|
||||
.unwrap_or_else(|_| unreachable!()),
|
||||
.map_err(|v: Vec<_>| {
|
||||
log::error!(
|
||||
"ixgbed: internal error: DMA buffer array conversion failed (got {} items, expected 32)",
|
||||
v.len()
|
||||
);
|
||||
Error::new(EIO)
|
||||
})?,
|
||||
receive_index: 0,
|
||||
transmit_ring: unsafe { Dma::zeroed()?.assume_init() },
|
||||
transmit_ring_free: 32,
|
||||
@@ -166,7 +183,7 @@ impl Intel8259x {
|
||||
|
||||
if (status & IXGBE_RXDADV_STAT_DD) != 0 {
|
||||
if (status & IXGBE_RXDADV_STAT_EOP) == 0 {
|
||||
panic!("increase buffer size or decrease MTU")
|
||||
log::error!("ixgbed: received fragmented packet, buffer too small");
|
||||
}
|
||||
|
||||
return unsafe { desc.wb.upper.length as usize };
|
||||
@@ -205,13 +222,8 @@ impl Intel8259x {
|
||||
self.mac_address = mac;
|
||||
}
|
||||
|
||||
/// Returns the register at `self.base` + `register`.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if `self.base` + `register` does not belong to the mapped memory of the PCIe device.
|
||||
fn read_reg(&self, register: u32) -> u32 {
|
||||
assert!(
|
||||
debug_assert!(
|
||||
register as usize <= self.size - 4 as usize,
|
||||
"MMIO access out of bounds"
|
||||
);
|
||||
@@ -219,13 +231,8 @@ impl Intel8259x {
|
||||
unsafe { ptr::read_volatile((self.base + register as usize) as *mut u32) }
|
||||
}
|
||||
|
||||
/// Sets the register at `self.base` + `register`.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if `self.base` + `register` does not belong to the mapped memory of the PCIe device.
|
||||
fn write_reg(&self, register: u32, data: u32) -> u32 {
|
||||
assert!(
|
||||
debug_assert!(
|
||||
register as usize <= self.size - 4 as usize,
|
||||
"MMIO access out of bounds"
|
||||
);
|
||||
@@ -279,7 +286,7 @@ impl Intel8259x {
|
||||
|
||||
let mac = self.get_mac_addr();
|
||||
|
||||
println!(
|
||||
log::info!(
|
||||
" - MAC: {:>02X}:{:>02X}:{:>02X}:{:>02X}:{:>02X}:{:>02X}",
|
||||
mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]
|
||||
);
|
||||
@@ -438,13 +445,11 @@ impl Intel8259x {
|
||||
}
|
||||
|
||||
/// Sets the rx queues` descriptors and enables the queues.
|
||||
///
|
||||
/// # Panics
|
||||
/// Panics if length of `self.receive_ring` is not a power of 2.
|
||||
fn start_rx_queue(&mut self, queue_id: u16) {
|
||||
if self.receive_ring.len() & (self.receive_ring.len() - 1) != 0 {
|
||||
panic!("number of receive queue entries must be a power of 2");
|
||||
}
|
||||
debug_assert!(
|
||||
self.receive_ring.len() & (self.receive_ring.len() - 1) == 0,
|
||||
"number of receive queue entries must be a power of 2"
|
||||
);
|
||||
|
||||
for i in 0..self.receive_ring.len() {
|
||||
self.receive_ring[i].read.pkt_addr = self.receive_buffer[i].physical() as u64;
|
||||
@@ -466,13 +471,11 @@ impl Intel8259x {
|
||||
}
|
||||
|
||||
/// Enables the tx queues.
|
||||
///
|
||||
/// # Panics
|
||||
/// Panics if length of `self.transmit_ring` is not a power of 2.
|
||||
fn start_tx_queue(&mut self, queue_id: u16) {
|
||||
if self.transmit_ring.len() & (self.transmit_ring.len() - 1) != 0 {
|
||||
panic!("number of receive queue entries must be a power of 2");
|
||||
}
|
||||
debug_assert!(
|
||||
self.transmit_ring.len() & (self.transmit_ring.len() - 1) == 0,
|
||||
"number of transmit queue entries must be a power of 2"
|
||||
);
|
||||
|
||||
for i in 0..self.transmit_ring.len() {
|
||||
self.transmit_ring[i].read.buffer_addr = self.transmit_buffer[i].physical() as u64;
|
||||
@@ -506,14 +509,14 @@ impl Intel8259x {
|
||||
|
||||
/// Waits for the link to come up.
|
||||
fn wait_for_link(&self) {
|
||||
println!(" - waiting for link");
|
||||
log::info!(" - waiting for link");
|
||||
let time = Instant::now();
|
||||
let mut speed = self.get_link_speed();
|
||||
while speed == 0 && time.elapsed().as_secs() < 10 {
|
||||
thread::sleep(Duration::from_millis(100));
|
||||
speed = self.get_link_speed();
|
||||
}
|
||||
println!(" - link speed is {} Mbit/s", self.get_link_speed());
|
||||
log::info!(" - link speed is {} Mbit/s", self.get_link_speed());
|
||||
}
|
||||
|
||||
/// Enables or disables promisc mode of this device.
|
||||
|
||||
@@ -215,7 +215,7 @@ impl Rtl8139 {
|
||||
.map(|_| Ok(Dma::zeroed()?.assume_init()))
|
||||
.collect::<Result<Vec<_>>>()?
|
||||
.try_into()
|
||||
.unwrap_or_else(|_| unreachable!()),
|
||||
.map_err(|_| Error::new(EIO))?,
|
||||
transmit_i: 0,
|
||||
mac_address: [0; 6],
|
||||
};
|
||||
|
||||
@@ -177,7 +177,7 @@ impl Rtl8168 {
|
||||
.map(|_| Ok(Dma::zeroed()?.assume_init()))
|
||||
.collect::<Result<Vec<_>>>()?
|
||||
.try_into()
|
||||
.unwrap_or_else(|_| unreachable!()),
|
||||
.map_err(|_| Error::new(EIO))?,
|
||||
|
||||
receive_ring: Dma::zeroed()?.assume_init(),
|
||||
receive_i: 0,
|
||||
@@ -185,7 +185,7 @@ impl Rtl8168 {
|
||||
.map(|_| Ok(Dma::zeroed()?.assume_init()))
|
||||
.collect::<Result<Vec<_>>>()?
|
||||
.try_into()
|
||||
.unwrap_or_else(|_| unreachable!()),
|
||||
.map_err(|_| Error::new(EIO))?,
|
||||
transmit_ring: Dma::zeroed()?.assume_init(),
|
||||
transmit_i: 0,
|
||||
transmit_buffer_h: [Dma::zeroed()?.assume_init()],
|
||||
|
||||
@@ -27,11 +27,16 @@ impl<'a> VirtioNet<'a> {
|
||||
// Populate all of the `rx_queue` with buffers to maximize performence.
|
||||
let mut rx_buffers = vec![];
|
||||
for i in 0..(rx.descriptor_len() as usize) {
|
||||
rx_buffers.push(unsafe {
|
||||
Dma::<[u8]>::zeroed_slice(MAX_BUFFER_LEN)
|
||||
.unwrap()
|
||||
.assume_init()
|
||||
});
|
||||
let buf = unsafe {
|
||||
match Dma::<[u8]>::zeroed_slice(MAX_BUFFER_LEN) {
|
||||
Ok(dma) => dma.assume_init(),
|
||||
Err(err) => {
|
||||
log::error!("virtio-netd: failed to allocate rx buffer: {err}");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
};
|
||||
rx_buffers.push(buf);
|
||||
|
||||
let chain = ChainBuilder::new()
|
||||
.chain(Buffer::new_unsized(&rx_buffers[i]).flags(DescriptorFlags::WRITE_ONLY))
|
||||
|
||||
@@ -75,11 +75,12 @@ fn main() -> Result<()> {
|
||||
}
|
||||
};
|
||||
|
||||
let full_device_id = handle.config().func.full_device_id;
|
||||
let func = handle.config().func;
|
||||
let full_device_id = func.full_device_id;
|
||||
|
||||
log::debug!(
|
||||
"pcid-spawner enumerated: PCI {} {}",
|
||||
handle.config().func.addr,
|
||||
func.addr,
|
||||
full_device_id.display()
|
||||
);
|
||||
|
||||
@@ -88,7 +89,7 @@ fn main() -> Result<()> {
|
||||
.iter()
|
||||
.find(|driver| driver.match_function(&full_device_id))
|
||||
else {
|
||||
log::debug!("no driver for {}, continuing", handle.config().func.addr);
|
||||
log::debug!("no driver for {}, continuing", func.addr);
|
||||
continue;
|
||||
};
|
||||
|
||||
@@ -112,6 +113,10 @@ fn main() -> Result<()> {
|
||||
|
||||
let channel_fd = handle.into_inner_fd();
|
||||
command.env("PCID_CLIENT_CHANNEL", channel_fd.to_string());
|
||||
command.env("PCID_SEGMENT", format!("{:04x}", func.addr.segment()));
|
||||
command.env("PCID_BUS", format!("{:02x}", func.addr.bus()));
|
||||
command.env("PCID_DEVICE", format!("{:02x}", func.addr.device()));
|
||||
command.env("PCID_FUNCTION", format!("{}", func.addr.function()));
|
||||
|
||||
#[allow(deprecated, reason = "we can't yet move this to init")]
|
||||
daemon::Daemon::spawn(command);
|
||||
|
||||
@@ -152,20 +152,22 @@ impl Mcfg {
|
||||
fn with<T>(
|
||||
f: impl FnOnce(PcieAllocs<'_>, Vec<InterruptMap>, [u32; 4]) -> io::Result<T>,
|
||||
) -> io::Result<T> {
|
||||
let table_dir = fs::read_dir("/scheme/acpi/tables")?;
|
||||
let table_dir = match fs::read_dir("/scheme/acpi/tables") {
|
||||
Ok(dir) => dir,
|
||||
Err(e) => {
|
||||
log::debug!("pcid: cannot read /scheme/acpi/tables: {e} (acpid running?)");
|
||||
return Err(e);
|
||||
}
|
||||
};
|
||||
|
||||
// TODO: validate/print MCFG?
|
||||
let mut found_tables: Vec<String> = Vec::new();
|
||||
|
||||
for table_direntry in table_dir {
|
||||
let table_path = table_direntry?.path();
|
||||
|
||||
// Every directory entry has to have a filename unless
|
||||
// the filesystem (or in this case acpid) misbehaves.
|
||||
// If it misbehaves we have worse problems than pcid
|
||||
// crashing. `as_encoded_bytes()` returns some superset
|
||||
// of ASCII, so directly comparing it with an ASCII name
|
||||
// is fine.
|
||||
let table_filename = table_path.file_name().unwrap().as_encoded_bytes();
|
||||
found_tables.push(String::from_utf8_lossy(table_filename).into_owned());
|
||||
|
||||
if table_filename.get(0..4) == Some(&MCFG_NAME) {
|
||||
let bytes = fs::read(table_path)?.into_boxed_slice();
|
||||
match Mcfg::parse(&*bytes) {
|
||||
@@ -174,6 +176,7 @@ impl Mcfg {
|
||||
return f(allocs, Vec::new(), [u32::MAX, u32::MAX, u32::MAX, u32::MAX]);
|
||||
}
|
||||
None => {
|
||||
log::warn!("pcid: MCFG table found but failed to parse ({} bytes)", bytes.len());
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::InvalidData,
|
||||
"couldn't find mcfg table",
|
||||
@@ -183,6 +186,12 @@ impl Mcfg {
|
||||
}
|
||||
}
|
||||
|
||||
log::debug!(
|
||||
"pcid: MCFG not found among {} ACPI table(s): {:?}",
|
||||
found_tables.len(),
|
||||
found_tables
|
||||
);
|
||||
|
||||
Err(io::Error::new(
|
||||
io::ErrorKind::NotFound,
|
||||
"couldn't find mcfg table",
|
||||
@@ -219,6 +228,24 @@ pub struct Pcie {
|
||||
pub interrupt_map_mask: [u32; 4],
|
||||
fallback: Pci,
|
||||
}
|
||||
|
||||
/// Validate an MCFG allocation entry address (cross-referenced with Linux
|
||||
/// `acpi_mcfg_valid_entry` in arch/x86/pci/mmconfig-shared.c).
|
||||
///
|
||||
/// - Addresses below 4 GiB are valid for legacy PCIe ECAM.
|
||||
/// - Addresses at or above 4 GiB require the MCFG table revision >= 1, matching
|
||||
/// the ACPI 4.0+ rule that 64-bit ECAM addresses are only valid when the
|
||||
/// firmware signals support via the revision field.
|
||||
fn validate_mcfg_addr(addr: u64) -> Result<(), String> {
|
||||
const FOUR_GIB: u64 = 0x1_0000_0000;
|
||||
|
||||
if addr < FOUR_GIB {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
Err(format!("address {addr:#018x} >= 4 GiB requires MCFG revision >= 1 (ACPI 4.0+); entry skipped"))
|
||||
}
|
||||
|
||||
struct Alloc {
|
||||
seg: u16,
|
||||
start_bus: u8,
|
||||
@@ -239,9 +266,11 @@ impl Pcie {
|
||||
Ok(pcie) => pcie,
|
||||
Err(fdt_error) => {
|
||||
log::warn!(
|
||||
"Couldn't retrieve PCIe info, perhaps the kernel is not compiled with \
|
||||
acpi or device tree support? Using the PCI 3.0 configuration space \
|
||||
instead. ACPI error: {:?} FDT error: {:?}",
|
||||
"PCIe (ECAM/MCFG) not available: {}. \
|
||||
Device tree ECAM also not found: {}. \
|
||||
Falling back to PCI 3.0 config space (I/O ports 0xCF8/0xCFC). \
|
||||
For PCI Express support, use QEMU Q35 machine type (-machine q35) \
|
||||
or ensure your platform firmware provides an MCFG ACPI table.",
|
||||
acpi_error,
|
||||
fdt_error
|
||||
);
|
||||
@@ -266,24 +295,44 @@ impl Pcie {
|
||||
.0
|
||||
.iter()
|
||||
.filter_map(|desc| {
|
||||
if desc.seg_group_num != 0 {
|
||||
let seg = desc.seg_group_num;
|
||||
let start = desc.start_bus;
|
||||
let end = desc.end_bus;
|
||||
log::warn!(
|
||||
"pcid: skipping MCFG entry at seg={seg} bus {start}..={end}: multi-segment not yet implemented",
|
||||
);
|
||||
return None;
|
||||
}
|
||||
|
||||
if let Err(reason) = validate_mcfg_addr(desc.base_addr) {
|
||||
let addr = desc.base_addr;
|
||||
let seg = desc.seg_group_num;
|
||||
let start = desc.start_bus;
|
||||
let end = desc.end_bus;
|
||||
log::warn!(
|
||||
"pcid: skipping MCFG entry at {addr:#018x} (seg={seg} bus {start}..={end}): {reason}",
|
||||
);
|
||||
return None;
|
||||
}
|
||||
|
||||
let seg = desc.seg_group_num;
|
||||
let start = desc.start_bus;
|
||||
let end = desc.end_bus;
|
||||
Some(Alloc {
|
||||
seg: desc.seg_group_num,
|
||||
start_bus: desc.start_bus,
|
||||
end_bus: desc.end_bus,
|
||||
seg,
|
||||
start_bus: start,
|
||||
end_bus: end,
|
||||
mem: PhysBorrowed::map(
|
||||
desc.base_addr.try_into().ok()?,
|
||||
BYTES_PER_BUS
|
||||
* (usize::from(desc.end_bus) - usize::from(desc.start_bus) + 1),
|
||||
* (usize::from(end) - usize::from(start) + 1),
|
||||
Prot::RW,
|
||||
MemoryType::Uncacheable,
|
||||
)
|
||||
.inspect_err(|err| {
|
||||
log::error!(
|
||||
"failed to map seg {} bus {}..={}: {}",
|
||||
{ desc.seg_group_num },
|
||||
{ desc.start_bus },
|
||||
{ desc.end_bus },
|
||||
err
|
||||
"failed to map seg {seg} bus {start}..={end}: {err}",
|
||||
)
|
||||
})
|
||||
.ok()?,
|
||||
|
||||
@@ -312,7 +312,7 @@ fn recv<T: DeserializeOwned>(r: &mut File) -> T {
|
||||
}
|
||||
|
||||
impl PciFunctionHandle {
|
||||
fn connect_default() -> Self {
|
||||
pub fn connect_default() -> Self {
|
||||
let channel_fd = match env::var("PCID_CLIENT_CHANNEL") {
|
||||
Ok(channel_fd) => channel_fd,
|
||||
Err(err) => {
|
||||
|
||||
@@ -21,6 +21,7 @@ enum Handle {
|
||||
TopLevel { entries: Vec<String> },
|
||||
Access,
|
||||
Device,
|
||||
Config { addr: PciAddress },
|
||||
Channel { addr: PciAddress, st: ChannelState },
|
||||
SchemeRoot,
|
||||
}
|
||||
@@ -30,14 +31,20 @@ struct HandleWrapper {
|
||||
}
|
||||
impl Handle {
|
||||
fn is_file(&self) -> bool {
|
||||
matches!(self, Self::Access | Self::Channel { .. })
|
||||
matches!(
|
||||
self,
|
||||
Self::Access | Self::Config { .. } | Self::Channel { .. }
|
||||
)
|
||||
}
|
||||
fn is_dir(&self) -> bool {
|
||||
!self.is_file()
|
||||
}
|
||||
// TODO: capability rather than root
|
||||
fn requires_root(&self) -> bool {
|
||||
matches!(self, Self::Access | Self::Channel { .. })
|
||||
matches!(
|
||||
self,
|
||||
Self::Access | Self::Config { .. } | Self::Channel { .. }
|
||||
)
|
||||
}
|
||||
fn is_scheme_root(&self) -> bool {
|
||||
matches!(self, Self::SchemeRoot)
|
||||
@@ -132,6 +139,7 @@ impl SchemeSync for PciScheme {
|
||||
let (len, mode) = match handle.inner {
|
||||
Handle::TopLevel { ref entries } => (entries.len(), MODE_DIR | 0o755),
|
||||
Handle::Device => (DEVICE_CONTENTS.len(), MODE_DIR | 0o755),
|
||||
Handle::Config { .. } => (256, MODE_CHR | 0o600),
|
||||
Handle::Access | Handle::Channel { .. } => (0, MODE_CHR | 0o600),
|
||||
Handle::SchemeRoot => return Err(Error::new(EBADF)),
|
||||
};
|
||||
@@ -156,6 +164,18 @@ impl SchemeSync for PciScheme {
|
||||
match handle.inner {
|
||||
Handle::TopLevel { .. } => Err(Error::new(EISDIR)),
|
||||
Handle::Device => Err(Error::new(EISDIR)),
|
||||
Handle::Config { addr } => {
|
||||
let offset = _offset as u16;
|
||||
let dword_offset = offset & !0x3;
|
||||
let byte_offset = (offset & 0x3) as usize;
|
||||
let bytes_to_read = buf.len().min(4 - byte_offset);
|
||||
|
||||
let dword = unsafe { self.pcie.read(addr, dword_offset) };
|
||||
let bytes = dword.to_le_bytes();
|
||||
buf[..bytes_to_read]
|
||||
.copy_from_slice(&bytes[byte_offset..byte_offset + bytes_to_read]);
|
||||
Ok(bytes_to_read)
|
||||
}
|
||||
Handle::Channel {
|
||||
addr: _,
|
||||
ref mut st,
|
||||
@@ -193,7 +213,9 @@ impl SchemeSync for PciScheme {
|
||||
return Ok(buf);
|
||||
}
|
||||
Handle::Device => DEVICE_CONTENTS,
|
||||
Handle::Access | Handle::Channel { .. } => return Err(Error::new(ENOTDIR)),
|
||||
Handle::Access | Handle::Config { .. } | Handle::Channel { .. } => {
|
||||
return Err(Error::new(ENOTDIR));
|
||||
}
|
||||
Handle::SchemeRoot => return Err(Error::new(EBADF)),
|
||||
};
|
||||
|
||||
@@ -223,6 +245,20 @@ impl SchemeSync for PciScheme {
|
||||
}
|
||||
|
||||
match handle.inner {
|
||||
Handle::Config { addr } => {
|
||||
let offset = _offset as u16;
|
||||
let dword_offset = offset & !0x3;
|
||||
let byte_offset = (offset & 0x3) as usize;
|
||||
let bytes_to_write = buf.len().min(4 - byte_offset);
|
||||
|
||||
let mut dword = unsafe { self.pcie.read(addr, dword_offset) };
|
||||
let mut bytes = dword.to_le_bytes();
|
||||
bytes[byte_offset..byte_offset + bytes_to_write]
|
||||
.copy_from_slice(&buf[..bytes_to_write]);
|
||||
dword = u32::from_le_bytes(bytes);
|
||||
unsafe { self.pcie.write(addr, dword_offset, dword) };
|
||||
Ok(buf.len())
|
||||
}
|
||||
Handle::Channel { addr, ref mut st } => {
|
||||
Self::write_channel(&self.pcie, &mut self.tree, addr, st, buf)
|
||||
}
|
||||
@@ -316,6 +352,10 @@ impl SchemeSync for PciScheme {
|
||||
func.enabled = false;
|
||||
}
|
||||
}
|
||||
Some(HandleWrapper {
|
||||
inner: Handle::Config { .. },
|
||||
..
|
||||
}) => {}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
@@ -341,6 +381,7 @@ impl PciScheme {
|
||||
let path = &after[1..];
|
||||
|
||||
match path {
|
||||
"config" => Handle::Config { addr },
|
||||
"channel" => {
|
||||
if func.enabled {
|
||||
return Err(Error::new(ENOLCK));
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
[package]
|
||||
name = "thermald"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
log.workspace = true
|
||||
anyhow.workspace = true
|
||||
common = { path = "../common" }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
@@ -0,0 +1,30 @@
|
||||
use anyhow::{Context, Result};
|
||||
use std::{thread, time};
|
||||
|
||||
fn read_temp() -> Option<f32> {
|
||||
for zone in 0..4 {
|
||||
let path = format!("/scheme/acpi/thermal_zone/{}/temperature", zone);
|
||||
if let Ok(data) = std::fs::read_to_string(&path) {
|
||||
if let Ok(mv) = data.trim().parse::<u32>() {
|
||||
return Some(mv as f32 / 1000.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
common::setup_logging("system", "thermald", "thermald",
|
||||
common::output_level(), common::file_level());
|
||||
log::info!("thermald: started");
|
||||
loop {
|
||||
if let Some(temp) = read_temp() {
|
||||
if temp > 85.0 {
|
||||
log::error!("thermald: CRITICAL {:.1}C", temp);
|
||||
} else if temp > 70.0 {
|
||||
log::warn!("thermald: WARNING {:.1}C", temp);
|
||||
}
|
||||
}
|
||||
thread::sleep(time::Duration::from_secs(5));
|
||||
}
|
||||
}
|
||||
+106
-21
@@ -199,16 +199,28 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! {
|
||||
let mut name = pci_config.func.name();
|
||||
name.push_str("_vbox");
|
||||
|
||||
let bar0 = pci_config.func.bars[0].expect_port();
|
||||
let bar0 = match pci_config.func.bars[0].try_port() {
|
||||
Ok(port) => port,
|
||||
Err(err) => {
|
||||
eprintln!("vboxd: invalid BAR0: {err}");
|
||||
std::process::exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
let irq = pci_config
|
||||
.func
|
||||
.legacy_interrupt_line
|
||||
.expect("vboxd: no legacy interrupts supported");
|
||||
.unwrap_or_else(|| {
|
||||
eprintln!("vboxd: no legacy interrupts supported");
|
||||
std::process::exit(1);
|
||||
});
|
||||
|
||||
println!(" + VirtualBox {}", pci_config.func.display());
|
||||
|
||||
common::acquire_port_io_rights().expect("vboxd: failed to get I/O permission");
|
||||
if let Err(err) = common::acquire_port_io_rights() {
|
||||
eprintln!("vboxd: failed to get I/O permission: {err}");
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
let mut width = 0;
|
||||
let mut height = 0;
|
||||
@@ -233,25 +245,55 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! {
|
||||
}
|
||||
}
|
||||
|
||||
let mut irq_file = irq.irq_handle("vboxd");
|
||||
let mut irq_file = match irq.try_irq_handle("vboxd") {
|
||||
Ok(file) => file,
|
||||
Err(err) => {
|
||||
eprintln!("vboxd: failed to open IRQ handle: {err}");
|
||||
std::process::exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
let address = unsafe { pcid_handle.map_bar(1) }.ptr.as_ptr();
|
||||
let address = match unsafe { pcid_handle.try_map_bar(1) } {
|
||||
Ok(bar) => bar.ptr.as_ptr(),
|
||||
Err(err) => {
|
||||
eprintln!("vboxd: failed to map BAR1: {err}");
|
||||
std::process::exit(1);
|
||||
}
|
||||
};
|
||||
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
|
||||
{
|
||||
let mut port = common::io::Pio::<u32>::new(bar0 as u16);
|
||||
|
||||
let vmmdev = unsafe { &mut *(address as *mut VboxVmmDev) };
|
||||
|
||||
let mut guest_info = VboxGuestInfo::new().expect("vboxd: failed to map GuestInfo");
|
||||
let mut guest_info = match VboxGuestInfo::new() {
|
||||
Ok(value) => value,
|
||||
Err(err) => {
|
||||
eprintln!("vboxd: failed to map GuestInfo: {err}");
|
||||
std::process::exit(1);
|
||||
}
|
||||
};
|
||||
guest_info.version.write(VBOX_VMMDEV_VERSION);
|
||||
guest_info.ostype.write(0x100);
|
||||
port.write(guest_info.physical() as u32);
|
||||
|
||||
let mut guest_caps = VboxGuestCaps::new().expect("vboxd: failed to map GuestCaps");
|
||||
let mut guest_caps = match VboxGuestCaps::new() {
|
||||
Ok(value) => value,
|
||||
Err(err) => {
|
||||
eprintln!("vboxd: failed to map GuestCaps: {err}");
|
||||
std::process::exit(1);
|
||||
}
|
||||
};
|
||||
guest_caps.caps.write(1 << 2);
|
||||
port.write(guest_caps.physical() as u32);
|
||||
|
||||
let mut set_mouse = VboxSetMouse::new().expect("vboxd: failed to map SetMouse");
|
||||
let mut set_mouse = match VboxSetMouse::new() {
|
||||
Ok(value) => value,
|
||||
Err(err) => {
|
||||
eprintln!("vboxd: failed to map SetMouse: {err}");
|
||||
std::process::exit(1);
|
||||
}
|
||||
};
|
||||
set_mouse.features.write(1 << 4 | 1);
|
||||
port.write(set_mouse.physical() as u32);
|
||||
|
||||
@@ -265,34 +307,71 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! {
|
||||
}
|
||||
}
|
||||
|
||||
let event_queue =
|
||||
EventQueue::<Source>::new().expect("vboxd: Could not create event queue.");
|
||||
let event_queue = match EventQueue::<Source>::new() {
|
||||
Ok(queue) => queue,
|
||||
Err(err) => {
|
||||
eprintln!("vboxd: could not create event queue: {err}");
|
||||
std::process::exit(1);
|
||||
}
|
||||
};
|
||||
event_queue
|
||||
.subscribe(
|
||||
irq_file.as_raw_fd() as usize,
|
||||
Source::Irq,
|
||||
event::EventFlags::READ,
|
||||
)
|
||||
.unwrap();
|
||||
.unwrap_or_else(|err| {
|
||||
eprintln!("vboxd: failed to subscribe IRQ fd: {err}");
|
||||
std::process::exit(1);
|
||||
});
|
||||
|
||||
daemon.ready();
|
||||
|
||||
libredox::call::setrens(0, 0).expect("vboxd: failed to enter null namespace");
|
||||
if let Err(err) = libredox::call::setrens(0, 0) {
|
||||
eprintln!("vboxd: failed to enter null namespace: {err}");
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
let mut bga = crate::bga::Bga::new();
|
||||
let get_mouse = VboxGetMouse::new().expect("vboxd: failed to map GetMouse");
|
||||
let display_change = VboxDisplayChange::new().expect("vboxd: failed to map DisplayChange");
|
||||
let ack_events = VboxAckEvents::new().expect("vboxd: failed to map AckEvents");
|
||||
let get_mouse = match VboxGetMouse::new() {
|
||||
Ok(value) => value,
|
||||
Err(err) => {
|
||||
eprintln!("vboxd: failed to map GetMouse: {err}");
|
||||
std::process::exit(1);
|
||||
}
|
||||
};
|
||||
let display_change = match VboxDisplayChange::new() {
|
||||
Ok(value) => value,
|
||||
Err(err) => {
|
||||
eprintln!("vboxd: failed to map DisplayChange: {err}");
|
||||
std::process::exit(1);
|
||||
}
|
||||
};
|
||||
let ack_events = match VboxAckEvents::new() {
|
||||
Ok(value) => value,
|
||||
Err(err) => {
|
||||
eprintln!("vboxd: failed to map AckEvents: {err}");
|
||||
std::process::exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
for Source::Irq in iter::once(Source::Irq)
|
||||
.chain(event_queue.map(|e| e.expect("vboxd: failed to get next event").user_data))
|
||||
{
|
||||
for Source::Irq in iter::once(Source::Irq).chain(event_queue.map(|e| match e {
|
||||
Ok(event) => event.user_data,
|
||||
Err(err) => {
|
||||
eprintln!("vboxd: failed to get next event: {err}");
|
||||
std::process::exit(1);
|
||||
}
|
||||
})) {
|
||||
let mut irq = [0; 8];
|
||||
if irq_file.read(&mut irq).unwrap() >= irq.len() {
|
||||
match irq_file.read(&mut irq) {
|
||||
Ok(read) if read >= irq.len() => {
|
||||
let host_events = vmmdev.host_events.read();
|
||||
if host_events != 0 {
|
||||
port.write(ack_events.physical() as u32);
|
||||
irq_file.write(&irq).unwrap();
|
||||
if let Err(err) = irq_file.write(&irq) {
|
||||
eprintln!("vboxd: failed to acknowledge IRQ: {err}");
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
if host_events & VBOX_EVENT_DISPLAY == VBOX_EVENT_DISPLAY {
|
||||
port.write(display_change.physical() as u32);
|
||||
@@ -326,8 +405,14 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! {
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(_) => {}
|
||||
Err(err) => {
|
||||
eprintln!("vboxd: failed to read IRQ file: {err}");
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::process::exit(0);
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
@@ -11,7 +11,10 @@ pub fn enable_msix(pcid_handle: &mut PciFunctionHandle) -> Result<File, Error> {
|
||||
// Extended message signaled interrupts.
|
||||
let msix_info = match pcid_handle.feature_info(PciFeature::MsiX) {
|
||||
PciFeatureInfo::MsiX(capability) => capability,
|
||||
_ => unreachable!(),
|
||||
_ => {
|
||||
log::warn!("virtio_core::enable_msix: expected MSI-X feature info");
|
||||
return Err(Error::Probe("unexpected PCI feature info for MSI-X"));
|
||||
}
|
||||
};
|
||||
let mut info = unsafe { msix_info.map_and_mask_all(pcid_handle) };
|
||||
|
||||
@@ -21,7 +24,10 @@ pub fn enable_msix(pcid_handle: &mut PciFunctionHandle) -> Result<File, Error> {
|
||||
let interrupt_handle = {
|
||||
let table_entry_pointer = info.table_entry_pointer(MSIX_PRIMARY_VECTOR as usize);
|
||||
|
||||
let destination_id = read_bsp_apic_id().expect("virtio_core: `read_bsp_apic_id()` failed");
|
||||
let destination_id = read_bsp_apic_id().map_err(|e| {
|
||||
log::warn!("virtio_core::enable_msix: read_bsp_apic_id failed: {e}");
|
||||
Error::Probe("read_bsp_apic_id failed")
|
||||
})?;
|
||||
let (msg_addr_and_data, interrupt_handle) =
|
||||
allocate_single_interrupt_vector_for_msi(destination_id);
|
||||
table_entry_pointer.write_addr_and_data(msg_addr_and_data);
|
||||
|
||||
@@ -31,16 +31,16 @@ pub const MSIX_PRIMARY_VECTOR: u16 = 0;
|
||||
/// before starting the device.
|
||||
/// * Finally start the device (via [`StandardTransport::run_device`]). At this point, the device
|
||||
/// is alive.
|
||||
///
|
||||
/// ## Panics
|
||||
/// This function panics if the device is not a virtio device.
|
||||
pub fn probe_device(pcid_handle: &mut PciFunctionHandle) -> Result<Device, Error> {
|
||||
let pci_config = pcid_handle.config();
|
||||
|
||||
assert_eq!(
|
||||
pci_config.func.full_device_id.vendor_id, 6900,
|
||||
"virtio_core::probe_device: not a virtio device"
|
||||
);
|
||||
if pci_config.func.full_device_id.vendor_id != 6900 {
|
||||
log::warn!(
|
||||
"virtio_core::probe_device: skipping non-virtio device (vendor ID {:#06x})",
|
||||
pci_config.func.full_device_id.vendor_id
|
||||
);
|
||||
return Err(Error::Probe("not a virtio device"));
|
||||
}
|
||||
|
||||
let mut common_addr = None;
|
||||
let mut notify_addr = None;
|
||||
@@ -55,7 +55,9 @@ pub fn probe_device(pcid_handle: &mut PciFunctionHandle) -> Result<Device, Error
|
||||
_ => continue,
|
||||
}
|
||||
|
||||
let (addr, _) = pci_config.func.bars[capability.bar as usize].expect_mem();
|
||||
let (addr, _) = pci_config.func.bars[capability.bar as usize]
|
||||
.try_mem()
|
||||
.map_err(|_| Error::Probe("BAR is not memory-mapped"))?;
|
||||
|
||||
let address = unsafe {
|
||||
let addr = addr + capability.offset as usize;
|
||||
@@ -100,19 +102,23 @@ pub fn probe_device(pcid_handle: &mut PciFunctionHandle) -> Result<Device, Error
|
||||
device_addr = Some(address);
|
||||
}
|
||||
|
||||
_ => unreachable!(),
|
||||
_ => continue,
|
||||
}
|
||||
}
|
||||
|
||||
let common_addr = common_addr.expect("virtio common capability missing");
|
||||
let device_addr = device_addr.expect("virtio device capability missing");
|
||||
let (notify_addr, notify_multiplier) = notify_addr.expect("virtio notify capability missing");
|
||||
let common_addr = common_addr.ok_or(Error::InCapable(CfgType::Common))?;
|
||||
let device_addr = device_addr.ok_or(Error::InCapable(CfgType::Device))?;
|
||||
let (notify_addr, notify_multiplier) =
|
||||
notify_addr.ok_or(Error::InCapable(CfgType::Notify))?;
|
||||
|
||||
// FIXME this is explicitly allowed by the virtio specification to happen
|
||||
assert!(
|
||||
notify_multiplier != 0,
|
||||
"virtio-core::device_probe: device uses the same Queue Notify addresses for all queues"
|
||||
);
|
||||
// The virtio specification explicitly allows a zero notify_off_multiplier,
|
||||
// meaning all queues share the same notification address. Handle gracefully.
|
||||
if notify_multiplier == 0 {
|
||||
log::warn!(
|
||||
"virtio_core::probe_device: device uses the same Queue Notify address for all queues"
|
||||
);
|
||||
return Err(Error::Probe("zero notify_off_multiplier"));
|
||||
}
|
||||
|
||||
let common = unsafe { &mut *(common_addr as *mut CommonCfg) };
|
||||
let device_space = unsafe { &mut *(device_addr as *mut u8) };
|
||||
@@ -128,8 +134,10 @@ pub fn probe_device(pcid_handle: &mut PciFunctionHandle) -> Result<Device, Error
|
||||
let all_pci_features = pcid_handle.fetch_all_features();
|
||||
let has_msix = all_pci_features.iter().any(|feature| feature.is_msix());
|
||||
|
||||
// According to the virtio specification, the device REQUIRED to support MSI-X.
|
||||
assert!(has_msix, "virtio: device does not support MSI-X");
|
||||
if !has_msix {
|
||||
log::warn!("virtio_core::probe_device: device does not support MSI-X");
|
||||
return Err(Error::Probe("device does not support MSI-X"));
|
||||
}
|
||||
let irq_handle = crate::arch::enable_msix(pcid_handle)?;
|
||||
|
||||
log::debug!("virtio: using standard PCI transport");
|
||||
|
||||
@@ -197,9 +197,9 @@ impl ChainBuilder {
|
||||
}
|
||||
|
||||
pub fn build(mut self) -> Vec<Buffer> {
|
||||
let last_buffer = self.buffers.last_mut().expect("virtio-core: empty chain");
|
||||
last_buffer.flags.remove(DescriptorFlags::NEXT);
|
||||
|
||||
if let Some(last_buffer) = self.buffers.last_mut() {
|
||||
last_buffer.flags.remove(DescriptorFlags::NEXT);
|
||||
}
|
||||
self.buffers
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,8 @@ pub enum Error {
|
||||
SyscallError(#[from] libredox::error::Error),
|
||||
#[error("the device is incapable of {0:?}")]
|
||||
InCapable(CfgType),
|
||||
#[error("virtio probe: {0}")]
|
||||
Probe(&'static str),
|
||||
}
|
||||
|
||||
/// Returns the queue part sizes in bytes.
|
||||
@@ -59,14 +61,23 @@ pub fn spawn_irq_thread(irq_handle: &File, queue: &Arc<Queue<'static>>) {
|
||||
let queue_copy = queue.clone();
|
||||
|
||||
std::thread::spawn(move || {
|
||||
let event_queue = RawEventQueue::new().unwrap();
|
||||
let event_queue = match RawEventQueue::new() {
|
||||
Ok(eq) => eq,
|
||||
Err(err) => {
|
||||
log::error!("virtio-core: failed to create event queue for IRQ thread: {err}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
event_queue
|
||||
.subscribe(irq_fd as usize, 0, event::EventFlags::READ)
|
||||
.unwrap();
|
||||
if let Err(err) = event_queue.subscribe(irq_fd as usize, 0, event::EventFlags::READ) {
|
||||
log::error!("virtio-core: failed to subscribe to IRQ fd: {err}");
|
||||
return;
|
||||
}
|
||||
|
||||
for _ in event_queue.map(Result::unwrap) {
|
||||
// Wake up the tasks waiting on the queue.
|
||||
for event_result in event_queue.map(|res| res) {
|
||||
if event_result.is_err() {
|
||||
break;
|
||||
}
|
||||
for (_, task) in queue_copy.waker.lock().unwrap().iter() {
|
||||
task.wake_by_ref();
|
||||
}
|
||||
@@ -604,7 +615,9 @@ impl Transport for StandardTransport<'_> {
|
||||
// Re-read device status to ensure the `FEATURES_OK` bit is still set: otherwise,
|
||||
// the device does not support our subset of features and the device is unusable.
|
||||
let confirm = common.device_status.get();
|
||||
assert!((confirm & DeviceStatusFlags::FEATURES_OK) == DeviceStatusFlags::FEATURES_OK);
|
||||
if (confirm & DeviceStatusFlags::FEATURES_OK) != DeviceStatusFlags::FEATURES_OK {
|
||||
log::error!("virtio-core: device rejected feature set (FEATURES_OK cleared after negotiation)");
|
||||
}
|
||||
}
|
||||
|
||||
fn setup_config_notify(&self, vector: u16) {
|
||||
@@ -640,7 +653,10 @@ impl Transport for StandardTransport<'_> {
|
||||
|
||||
// Set the MSI-X vector.
|
||||
common.queue_msix_vector.set(vector);
|
||||
assert!(common.queue_msix_vector.get() == vector);
|
||||
if common.queue_msix_vector.get() != vector {
|
||||
log::error!("virtio-core: MSI-X vector {vector:#x} was not accepted by device for queue {queue_index}");
|
||||
return Err(Error::SyscallError(libredox::error::Error::new(libredox::errno::EIO)));
|
||||
}
|
||||
|
||||
// Enable the queue.
|
||||
common.queue_enable.set(1);
|
||||
@@ -685,7 +701,9 @@ impl Transport for StandardTransport<'_> {
|
||||
|
||||
// Set the MSI-X vector.
|
||||
common.queue_msix_vector.set(queue.vector);
|
||||
assert!(common.queue_msix_vector.get() == queue.vector);
|
||||
if common.queue_msix_vector.get() != queue.vector {
|
||||
log::error!("virtio-core: MSI-X vector {:#x} was not accepted during reinit for queue {}", queue.vector, queue.queue_index);
|
||||
}
|
||||
|
||||
// Enable the queue.
|
||||
common.queue_enable.set(1);
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
[unit]
|
||||
description = "Boot essential services target"
|
||||
requires_weak = ["00_base.target"]
|
||||
@@ -0,0 +1,3 @@
|
||||
[unit]
|
||||
description = "Boot late stage target"
|
||||
requires_weak = ["05_boot_essential.target"]
|
||||
@@ -0,0 +1,8 @@
|
||||
[unit]
|
||||
description = "D-Bus system bus"
|
||||
requires_weak = ["12_boot_late.target", "00_ipcd.service"]
|
||||
|
||||
[service]
|
||||
cmd = "dbus-daemon"
|
||||
args = ["--system", "--nopidfile"]
|
||||
type = "oneshot_async"
|
||||
@@ -0,0 +1,8 @@
|
||||
[unit]
|
||||
description = "seatd seat management"
|
||||
requires_weak = ["12_dbus.service"]
|
||||
|
||||
[service]
|
||||
cmd = "/usr/bin/seatd"
|
||||
args = ["-l", "info"]
|
||||
type = "oneshot_async"
|
||||
@@ -0,0 +1,8 @@
|
||||
[unit]
|
||||
description = "Activate console VT"
|
||||
requires_weak = ["00_base.target"]
|
||||
|
||||
[service]
|
||||
cmd = "inputd"
|
||||
args = ["-A", "2"]
|
||||
type = "oneshot_async"
|
||||
@@ -0,0 +1,8 @@
|
||||
[unit]
|
||||
description = "Console getty on VT2"
|
||||
requires_weak = ["29_activate_console.service"]
|
||||
|
||||
[service]
|
||||
cmd = "getty"
|
||||
args = ["2"]
|
||||
type = "oneshot_async"
|
||||
@@ -0,0 +1,7 @@
|
||||
[unit]
|
||||
description = "CPU Thermal Monitor"
|
||||
requires_weak = ["00_base.target"]
|
||||
|
||||
[service]
|
||||
cmd = "thermald"
|
||||
type = "oneshot_async"
|
||||
@@ -0,0 +1,8 @@
|
||||
[unit]
|
||||
description = "Debug console on VT3"
|
||||
requires_weak = ["29_activate_console.service"]
|
||||
|
||||
[service]
|
||||
cmd = "getty"
|
||||
args = ["3"]
|
||||
type = "oneshot_async"
|
||||
@@ -0,0 +1,8 @@
|
||||
[unit]
|
||||
description = "DRM/KMS Display Driver"
|
||||
requires_weak = ["20_graphics.target", "40_hwd.service", "40_pcid-spawner-initfs.service"]
|
||||
condition_architecture = ["x86", "x86_64"]
|
||||
|
||||
[service]
|
||||
cmd = "redox-drm"
|
||||
type = "notify"
|
||||
@@ -3,8 +3,10 @@ description = "Initfs drivers"
|
||||
requires_weak = [
|
||||
"10_lived.service",
|
||||
"20_graphics.target",
|
||||
"40_pcid.service",
|
||||
"40_ps2d.service",
|
||||
"40_bcm2835-sdhcid.service",
|
||||
"40_hwd.service",
|
||||
"40_pcid-spawner-initfs.service",
|
||||
"41_acpid.service",
|
||||
]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[unit]
|
||||
description = "Hardware manager"
|
||||
requires_weak = ["10_inputd.service", "10_lived.service", "20_graphics.target"]
|
||||
requires_weak = ["10_inputd.service", "10_lived.service", "20_graphics.target", "40_pcid.service", "41_acpid.service"]
|
||||
|
||||
[service]
|
||||
cmd = "hwd"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[unit]
|
||||
description = "PCI driver spawner"
|
||||
requires_weak = ["10_inputd.service", "20_graphics.target", "40_hwd.service"]
|
||||
requires_weak = ["10_inputd.service", "20_graphics.target", "40_pcid.service"]
|
||||
|
||||
[service]
|
||||
cmd = "pcid-spawner"
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
[unit]
|
||||
description = "PCI daemon"
|
||||
requires_weak = ["41_acpid.service"]
|
||||
|
||||
[service]
|
||||
cmd = "pcid"
|
||||
type = "notify"
|
||||
@@ -0,0 +1,8 @@
|
||||
[unit]
|
||||
description = "ACPI daemon"
|
||||
default_dependencies = false
|
||||
|
||||
[service]
|
||||
cmd = "acpid"
|
||||
inherit_envs = ["RSDP_ADDR", "RSDP_SIZE"]
|
||||
type = "notify"
|
||||
@@ -0,0 +1,8 @@
|
||||
[unit]
|
||||
description = "USB Mass Storage Driver"
|
||||
requires_weak = ["40_drivers.target"]
|
||||
condition_architecture = ["x86", "x86_64"]
|
||||
|
||||
[service]
|
||||
cmd = "usbscsid"
|
||||
type = "notify"
|
||||
@@ -0,0 +1,27 @@
|
||||
pub fn status_ok(msg: &str) {
|
||||
eprintln!("[32m[[1;32m OK [0m[32m][0m {msg}");
|
||||
}
|
||||
|
||||
pub fn status_fail(msg: &str) {
|
||||
eprintln!("[31m[[1;31m FAILED [0m[31m][0m {msg}");
|
||||
}
|
||||
|
||||
pub fn status_skip(msg: &str) {
|
||||
eprintln!("[33m[[1;33m SKIP [0m[33m][0m {msg}");
|
||||
}
|
||||
|
||||
pub fn init_error(msg: &str) {
|
||||
eprintln!("[31m[1minit:[0m {msg}");
|
||||
}
|
||||
|
||||
pub fn init_warn(msg: &str) {
|
||||
eprintln!("[33minit:[0m {msg}");
|
||||
}
|
||||
|
||||
pub fn init_info(msg: &str) {
|
||||
eprintln!("init: {msg}");
|
||||
}
|
||||
|
||||
pub fn init_debug(msg: &str) {
|
||||
eprintln!("[36minit:[0m {msg}");
|
||||
}
|
||||
+20
-23
@@ -8,11 +8,14 @@ use libredox::flag::{O_RDONLY, O_WRONLY};
|
||||
use crate::scheduler::Scheduler;
|
||||
use crate::unit::{UnitId, UnitStore};
|
||||
|
||||
mod color;
|
||||
mod scheduler;
|
||||
mod script;
|
||||
mod service;
|
||||
mod unit;
|
||||
|
||||
use crate::color::{init_error, init_warn, status_fail, status_ok};
|
||||
|
||||
fn switch_stdio(stdio: &str) -> io::Result<()> {
|
||||
let stdin = libredox::Fd::open(stdio, O_RDONLY, 0)?;
|
||||
let stdout = libredox::Fd::open(stdio, O_WRONLY, 0)?;
|
||||
@@ -49,11 +52,7 @@ impl InitConfig {
|
||||
}
|
||||
|
||||
fn switch_root(unit_store: &mut UnitStore, config: &mut InitConfig, prefix: &Path, etcdir: &Path) {
|
||||
eprintln!(
|
||||
"init: switchroot to {} {}",
|
||||
prefix.display(),
|
||||
etcdir.display()
|
||||
);
|
||||
status_ok(&format!("switchroot to {} {}", prefix.display(), etcdir.display()));
|
||||
|
||||
config
|
||||
.envs
|
||||
@@ -79,10 +78,7 @@ fn switch_root(unit_store: &mut UnitStore, config: &mut InitConfig, prefix: &Pat
|
||||
continue;
|
||||
}
|
||||
let Some((key, value)) = env.split_once('=') else {
|
||||
eprintln!(
|
||||
"init: failed to parse env line from {}: {env:?}",
|
||||
file.display(),
|
||||
);
|
||||
init_warn(&format!("failed to parse env line from {}: {:?}", file.display(), env));
|
||||
continue;
|
||||
};
|
||||
config
|
||||
@@ -91,23 +87,18 @@ fn switch_root(unit_store: &mut UnitStore, config: &mut InitConfig, prefix: &Pat
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
eprintln!(
|
||||
"init: failed to read environment from {}: {err}",
|
||||
file.display(),
|
||||
);
|
||||
init_error(&format!("failed to read environment from {}: {}", file.display(), err));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
eprintln!(
|
||||
"init: failed to read environments from {}: {err}",
|
||||
init_error(&format!("failed to read environments from {}: {}",
|
||||
env_dirs
|
||||
.iter()
|
||||
.map(|dir| dir.display().to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ")
|
||||
);
|
||||
.join(", "), err));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -134,7 +125,7 @@ fn main() {
|
||||
.schedule_start_and_report_errors(&mut unit_store, UnitId("00_logd.service".to_owned()));
|
||||
scheduler.step(&mut unit_store, &mut init_config);
|
||||
if let Err(err) = switch_stdio("/scheme/log") {
|
||||
eprintln!("init: failed to switch stdio to '/scheme/log': {err}");
|
||||
init_error(&format!("failed to switch stdio to '/scheme/log': {}", err));
|
||||
}
|
||||
|
||||
let runtime_target = UnitId("00_runtime.target".to_owned());
|
||||
@@ -158,15 +149,13 @@ fn main() {
|
||||
let entries = match config::config_for_dirs(&unit_store.config_dirs) {
|
||||
Ok(entries) => entries,
|
||||
Err(err) => {
|
||||
eprintln!(
|
||||
"init: failed to read configs from {}: {err}",
|
||||
init_error(&format!("failed to read configs from {}: {}",
|
||||
unit_store
|
||||
.config_dirs
|
||||
.iter()
|
||||
.map(|dir| dir.display().to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ")
|
||||
);
|
||||
.join(", "), err));
|
||||
return;
|
||||
}
|
||||
};
|
||||
@@ -210,7 +199,15 @@ fn main() {
|
||||
}
|
||||
|
||||
loop {
|
||||
// Process any pending jobs whose dependencies may now be satisfied.
|
||||
scheduler.step(&mut unit_store, &mut init_config);
|
||||
|
||||
// Non-blocking wait: WNOHANG = 1. Reap any exited children without
|
||||
// blocking, then loop back to process any newly-unblocked pending jobs.
|
||||
let mut status = 0;
|
||||
libredox::call::waitpid(0, &mut status, 0).unwrap();
|
||||
match libredox::call::waitpid(0, &mut status, 1) {
|
||||
Ok(_pid) => {}
|
||||
Err(err) => init_error(&format!("waitpid error: {}", err)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+11
-12
@@ -2,6 +2,7 @@ use std::collections::{BTreeMap, VecDeque};
|
||||
use std::io::Write;
|
||||
|
||||
use crate::InitConfig;
|
||||
use crate::color::{init_debug, init_error, init_info, status_ok, status_skip};
|
||||
use crate::unit::{Unit, UnitId, UnitKind, UnitStore};
|
||||
|
||||
pub struct Scheduler {
|
||||
@@ -32,7 +33,7 @@ impl Scheduler {
|
||||
let mut errors = vec![];
|
||||
self.schedule_start(unit_store, unit_id, &mut errors);
|
||||
for error in errors {
|
||||
eprintln!("init: {error}");
|
||||
init_error(&format!("{}", error));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,6 +69,7 @@ impl Scheduler {
|
||||
match job.kind {
|
||||
JobKind::Start => {
|
||||
let unit = unit_store.unit_mut(&job.unit);
|
||||
eprintln!("init: DBG job={}", job.unit.0);
|
||||
|
||||
let mut blocked = false;
|
||||
for dep in &unit.info.requires_weak {
|
||||
@@ -107,31 +109,28 @@ fn run(unit: &mut Unit, config: &mut InitConfig) {
|
||||
UnitKind::LegacyScript { script } => {
|
||||
for cmd in script.clone() {
|
||||
if config.log_debug {
|
||||
eprintln!("init: running: {cmd:?}");
|
||||
init_debug(&format!("running: {cmd:?}"));
|
||||
}
|
||||
cmd.run(config);
|
||||
}
|
||||
}
|
||||
UnitKind::Service { service } => {
|
||||
let desc = unit.info.description.as_ref().unwrap_or(&unit.id.0);
|
||||
if config.skip_cmd.contains(&service.cmd) {
|
||||
eprintln!("Skipping '{} {}'", service.cmd, service.args.join(" "));
|
||||
status_skip(&format!("Skipping {} ({})", desc, service.cmd));
|
||||
return;
|
||||
}
|
||||
if config.log_debug {
|
||||
eprintln!(
|
||||
"Starting {} ({})",
|
||||
unit.info.description.as_ref().unwrap_or(&unit.id.0),
|
||||
service.cmd,
|
||||
);
|
||||
init_info(&format!("Starting {} ({})", desc, service.cmd));
|
||||
} else {
|
||||
status_ok(&format!("Started {}", desc));
|
||||
}
|
||||
service.spawn(&config.envs);
|
||||
}
|
||||
UnitKind::Target {} => {
|
||||
if config.log_debug {
|
||||
eprintln!(
|
||||
"Reached target {}",
|
||||
unit.info.description.as_ref().unwrap_or(&unit.id.0),
|
||||
);
|
||||
init_info(&format!("Reached target {}",
|
||||
unit.info.description.as_ref().unwrap_or(&unit.id.0)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+9
-8
@@ -8,6 +8,7 @@ use std::{env, io};
|
||||
|
||||
use serde::Deserialize;
|
||||
|
||||
use crate::color::{init_error, init_warn, status_fail};
|
||||
use crate::script::subst_env;
|
||||
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
@@ -52,7 +53,7 @@ impl Service {
|
||||
let mut child = match command.spawn() {
|
||||
Ok(child) => child,
|
||||
Err(err) => {
|
||||
eprintln!("init: failed to execute {:?}: {}", command, err);
|
||||
status_fail(&format!("failed to execute {:?}: {}", command, err));
|
||||
return;
|
||||
}
|
||||
};
|
||||
@@ -61,10 +62,10 @@ impl Service {
|
||||
ServiceType::Notify => match read_pipe.read_exact(&mut [0]) {
|
||||
Ok(()) => {}
|
||||
Err(err) if err.kind() == io::ErrorKind::UnexpectedEof => {
|
||||
eprintln!("init: {command:?} exited without notifying readiness");
|
||||
init_warn(&format!("{:?} exited without notifying readiness", command));
|
||||
}
|
||||
Err(err) => {
|
||||
eprintln!("init: failed to wait for {command:?}: {err}");
|
||||
init_error(&format!("failed to wait for {:?}: {}", command, err));
|
||||
}
|
||||
},
|
||||
ServiceType::Scheme(scheme) => {
|
||||
@@ -80,16 +81,16 @@ impl Service {
|
||||
errno: syscall::EINTR,
|
||||
}) => continue,
|
||||
Ok(0) => {
|
||||
eprintln!("init: {command:?} exited without notifying readiness");
|
||||
init_warn(&format!("{:?} exited without notifying readiness", command));
|
||||
return;
|
||||
}
|
||||
Ok(1) => break,
|
||||
Ok(n) => {
|
||||
eprintln!("init: incorrect amount of fds {n} returned");
|
||||
init_error(&format!("incorrect amount of fds {} returned", n));
|
||||
return;
|
||||
}
|
||||
Err(err) => {
|
||||
eprintln!("init: failed to wait for {command:?}: {err}");
|
||||
init_error(&format!("failed to wait for {:?}: {}", command, err));
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -104,11 +105,11 @@ impl Service {
|
||||
match child.wait() {
|
||||
Ok(exit_status) => {
|
||||
if !exit_status.success() {
|
||||
eprintln!("init: {command:?} failed with {exit_status}");
|
||||
status_fail(&format!("{:?} failed with {}", command, exit_status));
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
eprintln!("init: failed to wait for {:?}: {}", command, err)
|
||||
init_error(&format!("failed to wait for {:?}: {}", command, err))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+27
-21
@@ -180,7 +180,7 @@ pub struct Socket {
|
||||
options: HashSet<i32>,
|
||||
flags: usize,
|
||||
state: State,
|
||||
awaiting: VecDeque<usize>,
|
||||
awaiting: VecDeque<(usize, ucred)>,
|
||||
connection: Option<Connection>,
|
||||
issued_token: Option<u64>,
|
||||
ucred: ucred,
|
||||
@@ -245,6 +245,7 @@ impl Socket {
|
||||
&mut self,
|
||||
primary_id: usize,
|
||||
awaiting_client_id: usize,
|
||||
client_ucred: ucred,
|
||||
ctx: &CallerCtx,
|
||||
) -> Result<Self> {
|
||||
if !self.is_listening() {
|
||||
@@ -254,15 +255,17 @@ impl Socket {
|
||||
);
|
||||
return Err(Error::new(EINVAL));
|
||||
}
|
||||
Ok(Self::new(
|
||||
Ok(Self {
|
||||
primary_id,
|
||||
self.path.clone(),
|
||||
State::Established,
|
||||
self.options.clone(),
|
||||
self.flags,
|
||||
Some(Connection::new(awaiting_client_id)),
|
||||
ctx,
|
||||
))
|
||||
path: self.path.clone(),
|
||||
state: State::Established,
|
||||
options: self.options.clone(),
|
||||
flags: self.flags,
|
||||
awaiting: VecDeque::new(),
|
||||
connection: Some(Connection::new(awaiting_client_id)),
|
||||
issued_token: None,
|
||||
ucred: client_ucred,
|
||||
})
|
||||
}
|
||||
|
||||
fn establish(&mut self, new_socket: &mut Self, peer: usize) -> Result<()> {
|
||||
@@ -290,7 +293,7 @@ impl Socket {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn connect(&mut self, other: &mut Socket) -> Result<()> {
|
||||
fn connect(&mut self, other: &mut Socket, client_ucred: ucred) -> Result<()> {
|
||||
match self.state {
|
||||
State::Unbound | State::Bound => {
|
||||
// If the socket is unbound or bound, wait for the listener to start listening.
|
||||
@@ -306,12 +309,12 @@ impl Socket {
|
||||
}
|
||||
_ => return Err(Error::new(ECONNREFUSED)),
|
||||
}
|
||||
self.connect_unchecked(other);
|
||||
self.connect_unchecked(other, client_ucred);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn connect_unchecked(&mut self, other: &mut Socket) {
|
||||
self.awaiting.push_back(other.primary_id);
|
||||
fn connect_unchecked(&mut self, other: &mut Socket, client_ucred: ucred) {
|
||||
self.awaiting.push_back((other.primary_id, client_ucred));
|
||||
other.state = State::Connecting;
|
||||
other.connection = Some(Connection::new(self.primary_id));
|
||||
}
|
||||
@@ -499,7 +502,7 @@ impl<'sock> UdsStreamScheme<'sock> {
|
||||
};
|
||||
match verb {
|
||||
SocketCall::Bind => self.handle_bind(id, &payload),
|
||||
SocketCall::Connect => self.handle_connect(id, &payload),
|
||||
SocketCall::Connect => self.handle_connect(id, &payload, ctx),
|
||||
SocketCall::SetSockOpt => self.handle_setsockopt(
|
||||
id,
|
||||
*metadata.get(1).ok_or(Error::new(EINVAL))? as i32,
|
||||
@@ -592,7 +595,7 @@ impl<'sock> UdsStreamScheme<'sock> {
|
||||
// and changes its own state to `Established`.
|
||||
//
|
||||
// After these three phases, the socket connection is considered established.
|
||||
fn handle_connect(&mut self, id: usize, token_buf: &[u8]) -> Result<usize> {
|
||||
fn handle_connect(&mut self, id: usize, token_buf: &[u8], ctx: &CallerCtx) -> Result<usize> {
|
||||
let token = read_num::<u64>(token_buf)?;
|
||||
let (listener_id, connecting_res) = {
|
||||
let listener_rc = self
|
||||
@@ -637,7 +640,8 @@ impl<'sock> UdsStreamScheme<'sock> {
|
||||
}
|
||||
|
||||
// Phase 2: listener is now listening
|
||||
listener.connect(&mut client)?;
|
||||
let client_ucred = ucred { pid: ctx.pid as _, uid: ctx.uid as _, gid: ctx.gid as _ };
|
||||
listener.connect(&mut client, client_ucred)?;
|
||||
|
||||
(listener_id, connecting_res)
|
||||
};
|
||||
@@ -886,6 +890,7 @@ impl<'sock> UdsStreamScheme<'sock> {
|
||||
&mut self,
|
||||
listener_socket: &mut Socket,
|
||||
client_id: usize,
|
||||
client_ucred: ucred,
|
||||
ctx: &CallerCtx,
|
||||
) -> Result<Option<OpenResult>> {
|
||||
let (new_id, new) = {
|
||||
@@ -893,7 +898,7 @@ impl<'sock> UdsStreamScheme<'sock> {
|
||||
return Ok(None); // Client socket has been closed, nothing to accept
|
||||
};
|
||||
let new_id = self.next_id;
|
||||
let mut new = listener_socket.accept(new_id, client_id, ctx)?;
|
||||
let mut new = listener_socket.accept(new_id, client_id, client_ucred, ctx)?;
|
||||
|
||||
let mut client_socket = client_rc.borrow_mut();
|
||||
client_socket.establish(&mut new, listener_socket.primary_id)?;
|
||||
@@ -925,14 +930,14 @@ impl<'sock> UdsStreamScheme<'sock> {
|
||||
}
|
||||
loop {
|
||||
// Try to accept a waiting connection
|
||||
let Some(client_id) = socket.awaiting.pop_front() else {
|
||||
let Some((client_id, client_ucred)) = socket.awaiting.pop_front() else {
|
||||
if flags & O_NONBLOCK == O_NONBLOCK {
|
||||
return Err(Error::new(EAGAIN));
|
||||
} else {
|
||||
return Err(Error::new(EWOULDBLOCK));
|
||||
}
|
||||
};
|
||||
return match self.accept_connection(socket, client_id, ctx) {
|
||||
return match self.accept_connection(socket, client_id, client_ucred, ctx) {
|
||||
Ok(conn) => Ok(conn),
|
||||
Err(Error { errno: EAGAIN }) => continue,
|
||||
Err(e) => Err(e),
|
||||
@@ -1004,7 +1009,8 @@ impl<'sock> UdsStreamScheme<'sock> {
|
||||
);
|
||||
return Err(Error::new(EPIPE));
|
||||
}
|
||||
socket.connect_unchecked(&mut new);
|
||||
let pair_ucred = ucred { pid: ctx.pid as _, uid: ctx.uid as _, gid: ctx.gid as _ };
|
||||
socket.connect_unchecked(&mut new, pair_ucred);
|
||||
}
|
||||
|
||||
// smoltcp sends writeable whenever a listener gets a
|
||||
@@ -1199,7 +1205,7 @@ impl<'sock> UdsStreamScheme<'sock> {
|
||||
}
|
||||
|
||||
// Notify all waiting clients about listener closure
|
||||
for client_id in &socket.awaiting {
|
||||
for (client_id, _) in &socket.awaiting {
|
||||
if let Ok(client_rc) = self.get_socket(*client_id) {
|
||||
{
|
||||
let mut client = client_rc.borrow_mut();
|
||||
|
||||
@@ -41,14 +41,30 @@ impl<'sock> LogScheme<'sock> {
|
||||
|
||||
let mut kernel_sys_log = std::fs::File::open("/scheme/sys/log").unwrap();
|
||||
|
||||
let _ = std::fs::create_dir_all("/var/log");
|
||||
let persistent_log: Option<File> = OpenOptions::new()
|
||||
.create(true)
|
||||
.append(true)
|
||||
.open("/var/log/system.log")
|
||||
.ok();
|
||||
|
||||
let (output_tx, output_rx) = mpsc::channel::<OutputCmd>();
|
||||
|
||||
std::thread::spawn(move || {
|
||||
let mut files: Vec<File> = vec![];
|
||||
let mut logs = VecDeque::new();
|
||||
let mut persistent = persistent_log;
|
||||
if let Some(ref mut f) = persistent {
|
||||
let _ = f.write(b"--- logd started ---
|
||||
");
|
||||
}
|
||||
for cmd in output_rx {
|
||||
match cmd {
|
||||
OutputCmd::Log(line) => {
|
||||
if let Some(ref mut f) = persistent {
|
||||
let _ = f.write(&line);
|
||||
let _ = f.flush();
|
||||
}
|
||||
for file in &mut files {
|
||||
let _ = file.write(&line);
|
||||
let _ = file.flush();
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
pub const ITR_LOW_LATENCY: u32 = 64; pub const ITR_BULK: u32 = 256; pub const ITR_DEFAULT: u32 = 800;
|
||||
#[derive(Clone,Copy,PartialEq)] pub enum ItrState { LowLatency, Moderate, Bulk }
|
||||
pub struct ItrTracker { state: ItrState, current_itr: u32, packets_since_update: u32 }
|
||||
impl ItrTracker { pub const fn new() -> Self { Self { state: ItrState::LowLatency, current_itr: ITR_LOW_LATENCY, packets_since_update: 0 } }
|
||||
pub fn record_packet(&mut self, bytes: usize) { self.packets_since_update += 1; let _ = bytes; }
|
||||
pub fn update(&mut self) -> u32 { let new_state = if self.packets_since_update < 8 { ItrState::LowLatency } else if self.packets_since_update < 64 { ItrState::Moderate } else { ItrState::Bulk };
|
||||
if new_state != self.state { self.state = new_state; self.current_itr = match self.state { ItrState::LowLatency => ITR_LOW_LATENCY, ItrState::Moderate => ITR_DEFAULT, ItrState::Bulk => ITR_BULK }; }
|
||||
self.packets_since_update = 0; self.current_itr }
|
||||
pub fn current_itr(&self) -> u32 { self.current_itr } }
|
||||
@@ -0,0 +1,5 @@
|
||||
#[derive(Clone,Copy,PartialEq,Debug)] pub enum ChipVersion { Rtl8168b, Rtl8168c, Rtl8168cp, Rtl8168d, Rtl8168dp, Rtl8168e, Rtl8168evl, Rtl8168f, Rtl8168g, Rtl8168h, Rtl8168ep, Unknown }
|
||||
pub fn identify_chip(rev: u8, mac0: u32, _m1: u32, _m2: u32, _m3: u32, _m4: u32) -> ChipVersion { match ((mac0>>20)&0x7, rev) { (0,_)=>ChipVersion::Rtl8168b, (1,0x00..=0x01)=>ChipVersion::Rtl8168c, (1,0x02)=>ChipVersion::Rtl8168cp, (2,_)=>ChipVersion::Rtl8168d, (3,r) if r<=0x02=>ChipVersion::Rtl8168e, (3,_)=>ChipVersion::Rtl8168evl, (4,_)=>ChipVersion::Rtl8168f, (5,_)=>ChipVersion::Rtl8168g, (6,_)=>ChipVersion::Rtl8168h, (7,_)=>ChipVersion::Rtl8168ep, _=>ChipVersion::Unknown } }
|
||||
pub mod phy_regs { pub const BMCR: u32 = 0x00; pub const BMSR: u32 = 0x01; pub const BMCR_RESET: u16 = 1<<15; pub const BMCR_AUTONEG_ENABLE: u16 = 1<<12; pub const BMSR_LINK_STATUS: u16 = 1<<2; }
|
||||
pub fn phy_link_up(read: &dyn Fn(u32)->u16) -> bool { read(phy_regs::BMSR) & phy_regs::BMSR_LINK_STATUS != 0 }
|
||||
pub fn phy_reset(write: &dyn Fn(u32,u16), read: &dyn Fn(u32)->u16) -> bool { write(phy_regs::BMCR, phy_regs::BMCR_RESET); for _ in 0..500 { if read(phy_regs::BMCR) & phy_regs::BMCR_RESET == 0 { return true; } } false }
|
||||
@@ -0,0 +1,12 @@
|
||||
use core::sync::atomic::{AtomicU32, Ordering};
|
||||
pub const NCQ_MAX_DEPTH: usize = 32;
|
||||
pub struct NcqState { pub sactive: AtomicU32, pub pending: AtomicU32 }
|
||||
impl NcqState { pub const fn new() -> Self { Self { sactive: AtomicU32::new(0), pending: AtomicU32::new(0) } }
|
||||
pub fn allocate_tag(&self) -> Option<u32> { let active = self.pending.load(Ordering::Acquire); let free = !active; if free == 0 { return None; } let tag = free.trailing_zeros(); let mask = 1u32 << tag; self.pending.fetch_or(mask, Ordering::AcqRel); self.sactive.fetch_or(mask, Ordering::AcqRel); Some(tag) }
|
||||
pub fn complete_tag(&self, tag: u32) { let mask = 1u32 << tag; self.sactive.fetch_and(!mask, Ordering::AcqRel); self.pending.fetch_and(!mask, Ordering::AcqRel); }
|
||||
pub fn has_pending(&self) -> bool { self.pending.load(Ordering::Acquire) != 0 } }
|
||||
pub fn build_ncq_read_fis(tag: u32, lba: u64, sector_count: u16) -> [u32; 5] { let mut f = [0u32;5]; f[0]=0x0000_8027; f[1]=0x0060|((sector_count as u32&0xFF)<<24); f[2]=(lba as u32&0xFF)|(((lba>>8) as u32&0xFF)<<8); let mid=((lba>>16)as u32&0xFF)|((tag&0x1F)<<3); f[3]=mid|(((lba>>24)as u32&0xFF)<<8)|(((lba>>32)as u32&0xFF)<<16)|(((lba>>40)as u32&0xFF)<<24); f[4]=(((sector_count>>8)as u32&0xFF)<<16)|(((sector_count>>8)as u32&0xFF)<<24); f }
|
||||
pub fn build_ncq_write_fis(tag: u32, lba: u64, sector_count: u16) -> [u32; 5] { let mut f = build_ncq_read_fis(tag,lba,sector_count); f[1]=(f[1]&!0xFF00)|0x6100; f }
|
||||
pub fn process_ncq_completions(old_sactive: u32, new_sactive: u32, ncq: &NcqState, completed: &mut [u32;NCQ_MAX_DEPTH]) -> usize { let mask = old_sactive & !new_sactive; if mask == 0 { return 0; } let mut count = 0; let mut m = mask; while m != 0 { let tag = m.trailing_zeros(); ncq.complete_tag(tag); completed[count]=tag; count+=1; m&=m-1; } count }
|
||||
pub fn drive_supports_ncq(id: &[u16;256]) -> bool { id.get(76).map_or(false, |w| w&(1<<8)!=0) }
|
||||
pub fn ncq_queue_depth(id: &[u16;256]) -> u32 { id.get(75).map_or(1, |w| { let d = (w&0x1F) as u32; if d>0 {(d+1).min(NCQ_MAX_DEPTH as u32)} else {1} }) }
|
||||
Reference in New Issue
Block a user