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:
Red Bear OS
2026-07-12 01:29:08 +03:00
parent 5023b72728
commit 00b799d512
58 changed files with 1443 additions and 317 deletions
+2 -1
View File
@@ -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
View File
@@ -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")?;
+3 -1
View File
@@ -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
View File
@@ -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>)>,
+7 -3
View File
@@ -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
View File
@@ -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")))]
+33 -6
View File
@@ -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);
+2
View File
@@ -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),
}
+21
View File
@@ -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"]
-3
View File
@@ -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
View File
@@ -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.
+164 -29
View File
@@ -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?
}
}
+4 -3
View File
@@ -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) {
+14 -7
View File
@@ -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?
+44 -13
View File
@@ -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
View File
@@ -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),
}
}
}
+4 -5
View File
@@ -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> {
+11 -9
View File
@@ -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")
}
+1
View File
@@ -7,6 +7,7 @@ edition = "2021"
[dependencies]
bitflags.workspace = true
libredox.workspace = true
log.workspace = true
redox_event.workspace = true
redox_syscall.workspace = true
+35 -32
View File
@@ -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.
+1 -1
View File
@@ -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],
};
+2 -2
View File
@@ -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()],
+10 -5
View File
@@ -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))
+8 -3
View File
@@ -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);
+69 -20
View File
@@ -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()?,
+1 -1
View File
@@ -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) => {
+44 -3
View File
@@ -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));
+12
View File
@@ -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
+30
View File
@@ -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
View File
@@ -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);
}
+8 -2
View File
@@ -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);
+27 -19
View File
@@ -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
}
}
+27 -9
View File
@@ -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);
+3
View File
@@ -0,0 +1,3 @@
[unit]
description = "Boot essential services target"
requires_weak = ["00_base.target"]
+3
View File
@@ -0,0 +1,3 @@
[unit]
description = "Boot late stage target"
requires_weak = ["05_boot_essential.target"]
+8
View File
@@ -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"
+8
View File
@@ -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"
+8
View File
@@ -0,0 +1,8 @@
[unit]
description = "Activate console VT"
requires_weak = ["00_base.target"]
[service]
cmd = "inputd"
args = ["-A", "2"]
type = "oneshot_async"
+8
View File
@@ -0,0 +1,8 @@
[unit]
description = "Console getty on VT2"
requires_weak = ["29_activate_console.service"]
[service]
cmd = "getty"
args = ["2"]
type = "oneshot_async"
+7
View File
@@ -0,0 +1,7 @@
[unit]
description = "CPU Thermal Monitor"
requires_weak = ["00_base.target"]
[service]
cmd = "thermald"
type = "oneshot_async"
+8
View File
@@ -0,0 +1,8 @@
[unit]
description = "Debug console on VT3"
requires_weak = ["29_activate_console.service"]
[service]
cmd = "getty"
args = ["3"]
type = "oneshot_async"
+8
View File
@@ -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"
+2
View File
@@ -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 -1
View File
@@ -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 -1
View File
@@ -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"
+7
View File
@@ -0,0 +1,7 @@
[unit]
description = "PCI daemon"
requires_weak = ["41_acpid.service"]
[service]
cmd = "pcid"
type = "notify"
+8
View File
@@ -0,0 +1,8 @@
[unit]
description = "ACPI daemon"
default_dependencies = false
[service]
cmd = "acpid"
inherit_envs = ["RSDP_ADDR", "RSDP_SIZE"]
type = "notify"
+8
View File
@@ -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"
+27
View File
@@ -0,0 +1,27 @@
pub fn status_ok(msg: &str) {
eprintln!("[ OK ] {msg}");
}
pub fn status_fail(msg: &str) {
eprintln!("[ FAILED ] {msg}");
}
pub fn status_skip(msg: &str) {
eprintln!("[ SKIP ] {msg}");
}
pub fn init_error(msg: &str) {
eprintln!("init: {msg}");
}
pub fn init_warn(msg: &str) {
eprintln!("init: {msg}");
}
pub fn init_info(msg: &str) {
eprintln!("init: {msg}");
}
pub fn init_debug(msg: &str) {
eprintln!("init: {msg}");
}
+20 -23
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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();
+16
View File
@@ -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();
+9
View File
@@ -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 } }
+5
View File
@@ -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 }
+12
View File
@@ -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} }) }