Update Red Bear driver substrate

Red Bear OS Team
This commit is contained in:
2026-04-16 12:43:10 +01:00
parent 6259dc06da
commit 6c418bb03b
23 changed files with 982 additions and 169 deletions
@@ -1,7 +1,7 @@
#ifndef _LINUX_DEVICE_H
#define _LINUX_DEVICE_H
#include <linux/types.h>
#include "types.h"
#include <stddef.h>
struct device_driver {
@@ -27,11 +27,7 @@ static inline void dev_set_drvdata(struct device *dev, void *data)
dev->driver_data = data;
}
struct class {
const char *name;
};
extern struct device *devm_kzalloc(struct device *dev, size_t size, gfp_t flags);
extern void *devm_kzalloc(struct device *dev, size_t size, gfp_t flags);
extern void devm_kfree(struct device *dev, void *ptr);
#endif
@@ -1,6 +1,7 @@
#ifndef _LINUX_FIRMWARE_H
#define _LINUX_FIRMWARE_H
#include <stddef.h>
#include <linux/types.h>
struct firmware {
@@ -23,4 +24,6 @@ extern int request_firmware_nowait(
extern int request_firmware_direct(const struct firmware **fw,
const char *name, struct device *dev);
#define FW_ACTION_HOTPLUG 0
#endif
@@ -3,29 +3,16 @@
#include <linux/types.h>
#include <linux/irq.h>
#include <linux/spinlock.h>
static inline int in_interrupt(void)
{
return 0;
}
extern void local_irq_save(unsigned long *flags);
extern void local_irq_restore(unsigned long flags);
extern void local_irq_disable(void);
extern void local_irq_enable(void);
extern int irqs_disabled(void);
static inline int in_irq(void)
{
return 0;
}
static inline void local_irq_save(unsigned long *flags)
{
(void)flags;
}
static inline void local_irq_restore(unsigned long flags)
{
(void)flags;
}
static inline void local_irq_disable(void) {}
static inline void local_irq_enable(void) {}
static inline int in_interrupt(void) { return irqs_disabled(); }
static inline int in_irq(void) { return irqs_disabled(); }
#define disable_irq_nosync(irq) ((void)(irq))
#define enable_irq(irq) ((void)(irq))
@@ -11,12 +11,7 @@ extern void mutex_init(struct mutex *lock);
extern void mutex_lock(struct mutex *lock);
extern void mutex_unlock(struct mutex *lock);
extern int mutex_is_locked(struct mutex *lock);
static inline int mutex_trylock(struct mutex *lock)
{
(void)lock;
return 1;
}
extern int mutex_trylock(struct mutex *lock);
#define DEFINE_MUTEX(name) struct mutex name = { .__opaque = {0} }
@@ -24,7 +24,7 @@ struct pci_device_id {
struct pci_dev {
u16 vendor;
u16 device;
u16 device_id;
u8 bus_number;
u8 dev_number;
u8 func_number;
@@ -33,7 +33,7 @@ struct pci_dev {
u64 resource_start[6];
u64 resource_len[6];
void *driver_data;
struct device device;
struct device device_obj;
};
struct pci_driver {
@@ -11,39 +11,13 @@ struct timer_list {
unsigned char __opaque[64];
};
static inline void setup_timer(struct timer_list *timer,
void (*function)(unsigned long),
unsigned long data)
{
timer->function = function;
timer->data = data;
timer->expires = 0;
}
static inline int mod_timer(struct timer_list *timer, unsigned long expires)
{
(void)timer;
(void)expires;
return 0;
}
static inline int del_timer(struct timer_list *timer)
{
(void)timer;
return 0;
}
static inline int del_timer_sync(struct timer_list *timer)
{
(void)timer;
return 0;
}
static inline int timer_pending(const struct timer_list *timer)
{
(void)timer;
return 0;
}
extern void setup_timer(struct timer_list *timer,
void (*function)(unsigned long),
unsigned long data);
extern int mod_timer(struct timer_list *timer, unsigned long expires);
extern int del_timer(struct timer_list *timer);
extern int del_timer_sync(struct timer_list *timer);
extern int timer_pending(const struct timer_list *timer);
#define DEFINE_TIMER(_name, _function, _flags, _data) \
struct timer_list _name = { .function = (_function), .data = (_data) }
@@ -2,13 +2,19 @@
pub mod rust_impl;
#[cfg(all(test, not(target_os = "redox")))]
mod test_host_redox_shims;
pub use rust_impl::device;
pub use rust_impl::dma;
pub use rust_impl::drm_shim;
pub use rust_impl::firmware;
pub use rust_impl::io;
pub use rust_impl::irq;
pub use rust_impl::mac80211;
pub use rust_impl::memory;
pub use rust_impl::net;
pub use rust_impl::pci;
pub use rust_impl::sync;
pub use rust_impl::wireless;
pub use rust_impl::workqueue;
@@ -6,11 +6,11 @@ const GFP_DMA32: u32 = 2;
/// Wrapper to make raw pointers `Send`, required because `DEVRES_MAP` is a
/// global `Mutex` (which needs `T: Send`). Raw pointers are not `Send` by
/// default since the compiler can't prove thread-safety. Here each `(ptr,
/// Layout)` pair is exclusively owned by the device that allocated it — only
/// default since the compiler can't prove thread-safety. Here each tracked
/// pointer is exclusively owned by the device that allocated it — only
/// freed via `devm_kfree` or `devres_free_all` — so sending across threads is
/// safe.
struct TrackedAlloc(*mut u8, Layout);
struct TrackedAlloc(*mut u8);
unsafe impl Send for TrackedAlloc {}
lazy_static::lazy_static! {
@@ -42,16 +42,15 @@ pub extern "C" fn devm_kzalloc(dev: *mut u8, size: usize, flags: u32) -> *mut u8
return ptr;
}
let layout = match tracked_layout(size, flags) {
Some(layout) => layout,
None => return ptr,
};
if tracked_layout(size, flags).is_none() {
return ptr;
}
if let Ok(mut devres_map) = DEVRES_MAP.lock() {
devres_map
.entry(dev as usize)
.or_default()
.push(TrackedAlloc(ptr, layout));
.push(TrackedAlloc(ptr));
}
ptr
@@ -1,5 +1,87 @@
use std::ptr;
fn firmware_search_roots() -> Vec<std::path::PathBuf> {
let mut roots = Vec::new();
if let Some(root) = std::env::var_os("REDBEAR_LINUX_KPI_FIRMWARE_ROOT") {
roots.push(root.into());
}
roots.push("/scheme/firmware".into());
roots.push("/lib/firmware".into());
roots
}
fn firmware_name(name: *const u8) -> Result<String, i32> {
if name.is_null() {
return Err(-22);
}
let name_str = unsafe {
let len = {
let mut l = 0;
while *name.add(l) != 0 {
l += 1;
}
l
};
let slice = std::slice::from_raw_parts(name, len);
match std::str::from_utf8(slice) {
Ok(s) => s.to_string(),
Err(_) => return Err(-22),
}
};
Ok(name_str)
}
fn load_firmware_bytes(name: &str) -> Result<Vec<u8>, i32> {
for root in firmware_search_roots() {
let candidate = root.join(name);
match std::fs::read(&candidate) {
Ok(bytes) => {
log::info!(
"request_firmware: loaded '{}' via {}",
name,
candidate.display()
);
return Ok(bytes);
}
Err(err) if err.kind() == std::io::ErrorKind::NotFound => continue,
Err(err) => {
log::error!(
"request_firmware: failed to load '{}' via {}: {}",
name,
candidate.display(),
err
);
return Err(-5);
}
}
}
log::error!("request_firmware: failed to locate '{}'", name);
Err(-2)
}
fn install_firmware(fw: *mut *mut Firmware, data: Vec<u8>) -> i32 {
let size = data.len();
let layout = match std::alloc::Layout::from_size_align(size, 1) {
Ok(l) => l,
Err(_) => return -12,
};
let ptr = unsafe { std::alloc::alloc(layout) };
if ptr.is_null() {
return -12;
}
unsafe { ptr::copy_nonoverlapping(data.as_ptr(), ptr, size) };
let firmware = Box::new(Firmware {
size,
data: ptr as *const u8,
});
unsafe { *fw = Box::into_raw(firmware) };
0
}
#[repr(C)]
pub struct Firmware {
pub size: usize,
@@ -35,54 +117,68 @@ pub extern "C" fn request_firmware(fw: *mut *mut Firmware, name: *const u8, _dev
return -22;
}
let name_str = unsafe {
let len = {
let mut l = 0;
while *name.add(l) != 0 {
l += 1;
}
l
};
let slice = std::slice::from_raw_parts(name, len);
match std::str::from_utf8(slice) {
Ok(s) => s,
Err(_) => return -22,
}
let name_str = match firmware_name(name) {
Ok(name_str) => name_str,
Err(err) => return err,
};
let firmware_path = format!("/scheme/firmware/{}", name_str);
log::info!(
"request_firmware: loading '{}' via {}",
name_str,
firmware_path
);
let data = match std::fs::read(&firmware_path) {
Ok(d) => d,
Err(e) => {
log::error!("request_firmware: failed to load '{}': {}", name_str, e);
return -2;
}
};
let size = data.len();
let layout = match std::alloc::Layout::from_size_align(size, 1) {
Ok(l) => l,
Err(_) => return -12,
};
let ptr = unsafe { std::alloc::alloc(layout) };
if ptr.is_null() {
return -12;
match load_firmware_bytes(&name_str) {
Ok(data) => install_firmware(fw, data),
Err(err) => err,
}
unsafe { ptr::copy_nonoverlapping(data.as_ptr(), ptr, size) };
}
let firmware = Box::new(Firmware {
size,
data: ptr as *const u8,
#[no_mangle]
pub extern "C" fn request_firmware_direct(
fw: *mut *mut Firmware,
name: *const u8,
dev: *mut u8,
) -> i32 {
request_firmware(fw, name, dev)
}
#[no_mangle]
pub extern "C" fn request_firmware_nowait(
_dev: *mut u8,
_uevent: i32,
name: *const u8,
context: *mut u8,
cont: Option<extern "C" fn(*const Firmware, *mut u8)>,
) -> i32 {
let Some(cont) = cont else {
return -22;
};
let name_str = match firmware_name(name) {
Ok(name_str) => name_str,
Err(err) => return err,
};
let fw_ptr = match load_firmware_bytes(&name_str) {
Ok(data) => {
let mut fw_ptr: *mut Firmware = ptr::null_mut();
let rc = install_firmware(&mut fw_ptr, data);
if rc != 0 {
return rc;
}
fw_ptr
}
Err(err) => {
log::warn!(
"request_firmware_nowait: unable to pre-load '{}': {}",
name_str,
err
);
ptr::null_mut()
}
};
let fw_addr = fw_ptr as usize;
let context_addr = context as usize;
std::thread::spawn(move || {
cont(fw_addr as *const Firmware, context_addr as *mut u8);
});
unsafe { *fw = Box::into_raw(firmware) };
log::info!("request_firmware: loaded {} bytes for '{}'", size, name_str);
0
}
@@ -93,3 +189,89 @@ pub extern "C" fn release_firmware(fw: *mut Firmware) {
}
unsafe { drop(Box::from_raw(fw)) };
}
#[cfg(test)]
mod tests {
use super::*;
use std::ffi::CString;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Mutex;
use std::time::{SystemTime, UNIX_EPOCH};
static TEST_ENV_LOCK: std::sync::LazyLock<Mutex<()>> =
std::sync::LazyLock::new(|| Mutex::new(()));
fn temp_root(prefix: &str) -> std::path::PathBuf {
let stamp = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_nanos();
let path = std::env::temp_dir().join(format!("{prefix}-{stamp}"));
std::fs::create_dir_all(&path).unwrap();
path
}
#[test]
fn request_firmware_direct_uses_override_root() {
let _guard = TEST_ENV_LOCK.lock().unwrap();
let root = temp_root("rbos-linux-kpi-fw");
std::fs::write(root.join("iwlwifi-test.ucode"), [1u8, 2, 3]).unwrap();
unsafe {
std::env::set_var("REDBEAR_LINUX_KPI_FIRMWARE_ROOT", &root);
}
let mut fw: *mut Firmware = ptr::null_mut();
let name = CString::new("iwlwifi-test.ucode").unwrap();
let rc = request_firmware_direct(&mut fw, name.as_ptr().cast::<u8>(), ptr::null_mut());
assert_eq!(rc, 0);
assert!(!fw.is_null());
assert_eq!(unsafe { (*fw).size }, 3);
release_firmware(fw);
unsafe {
std::env::remove_var("REDBEAR_LINUX_KPI_FIRMWARE_ROOT");
}
}
#[test]
fn request_firmware_nowait_invokes_callback() {
let _guard = TEST_ENV_LOCK.lock().unwrap();
let root = temp_root("rbos-linux-kpi-fw-nowait");
std::fs::write(root.join("iwlwifi-test-async.ucode"), [9u8, 8, 7]).unwrap();
unsafe {
std::env::set_var("REDBEAR_LINUX_KPI_FIRMWARE_ROOT", &root);
}
static CALLED: AtomicBool = AtomicBool::new(false);
extern "C" fn callback(fw: *const Firmware, _context: *mut u8) {
assert!(!fw.is_null());
CALLED.store(true, Ordering::Release);
release_firmware(fw as *mut Firmware);
}
let name = CString::new("iwlwifi-test-async.ucode").unwrap();
let rc = request_firmware_nowait(
ptr::null_mut(),
0,
name.as_ptr().cast::<u8>(),
ptr::null_mut(),
Some(callback),
);
assert_eq!(rc, 0);
for _ in 0..100 {
if CALLED.load(Ordering::Acquire) {
unsafe {
std::env::remove_var("REDBEAR_LINUX_KPI_FIRMWARE_ROOT");
}
return;
}
std::thread::sleep(std::time::Duration::from_millis(5));
}
unsafe {
std::env::remove_var("REDBEAR_LINUX_KPI_FIRMWARE_ROOT");
}
panic!("request_firmware_nowait callback was not invoked");
}
}
@@ -72,6 +72,24 @@ fn dma32_alloc(size: usize) -> *mut u8 {
let phys = virt_to_phys(candidate as usize);
if phys == 0 {
#[cfg(all(test, not(target_os = "redox")))]
let host_test_fallback = true;
#[cfg(not(all(test, not(target_os = "redox"))))]
let host_test_fallback = false;
if host_test_fallback {
log::debug!(
"dma32_alloc: host test fallback for virt={:#x} without translation",
candidate as usize
);
if let Ok(mut tracker) = DMA32_TRACKER.lock() {
tracker.insert(SendU8Ptr(candidate), layout);
return candidate;
}
unsafe { dealloc(candidate, layout) };
return ptr::null_mut();
}
log::warn!(
"dma32_alloc: virt_to_phys failed for {:#x}",
candidate as usize
@@ -2,6 +2,8 @@ pub mod device;
pub mod dma;
pub mod drm_shim;
pub mod firmware;
pub mod mac80211;
pub mod net;
pub mod idr;
pub mod io;
pub mod irq;
@@ -10,4 +12,5 @@ pub mod pci;
pub mod sync;
pub mod timer;
pub mod wait;
pub mod wireless;
pub mod workqueue;
@@ -2,9 +2,7 @@ use std::os::raw::c_ulong;
use std::ptr;
use std::sync::Mutex;
use redox_driver_sys::pci::{
enumerate_pci_class, PciDevice, PciDeviceInfo, PciLocation, PCI_CLASS_DISPLAY,
};
use redox_driver_sys::pci::{enumerate_pci_all, PciDevice, PciDeviceInfo, PciLocation};
const EINVAL: i32 = 22;
const ENODEV: i32 = 19;
@@ -354,7 +352,7 @@ pub extern "C" fn pci_register_driver(drv: *mut PciDriver) -> i32 {
}
};
let devices = match enumerate_pci_class(PCI_CLASS_DISPLAY) {
let devices = match enumerate_pci_all() {
Ok(devices) => devices,
Err(error) => {
log::warn!("pci_register_driver: PCI enumeration failed: {}", error);
@@ -365,7 +363,7 @@ pub extern "C" fn pci_register_driver(drv: *mut PciDriver) -> i32 {
let Some((info, id_ptr)) = devices.into_iter().find_map(|candidate| {
matching_id_entry(&candidate, driver.id_table).map(|id_ptr| (candidate, id_ptr))
}) else {
log::info!("pci_register_driver: no matching PCI display device found");
log::info!("pci_register_driver: no matching PCI device found");
return -ENODEV;
};
@@ -32,6 +32,22 @@ pub extern "C" fn mutex_lock(m: *mut LinuxMutex) {
}
}
#[no_mangle]
pub extern "C" fn mutex_trylock(m: *mut LinuxMutex) -> i32 {
if m.is_null() {
return 0;
}
if unsafe { &*m }
.state
.compare_exchange(UNLOCKED, LOCKED, Ordering::Acquire, Ordering::Relaxed)
.is_ok()
{
1
} else {
0
}
}
#[no_mangle]
pub extern "C" fn mutex_unlock(m: *mut LinuxMutex) {
if m.is_null() {
@@ -121,6 +137,18 @@ pub extern "C" fn local_irq_restore(flags: u64) {
IRQ_DEPTH.store(flags as u32, Ordering::Release);
}
#[no_mangle]
pub extern "C" fn local_irq_disable() {
IRQ_DEPTH.fetch_add(1, Ordering::Acquire);
}
#[no_mangle]
pub extern "C" fn local_irq_enable() {
let _ = IRQ_DEPTH.fetch_update(Ordering::AcqRel, Ordering::Relaxed, |depth| {
Some(depth.saturating_sub(1))
});
}
#[no_mangle]
pub extern "C" fn irqs_disabled() -> bool {
IRQ_DEPTH.load(Ordering::Acquire) > 0
@@ -175,3 +203,29 @@ pub extern "C" fn reinit_completion(c: *mut Completion) {
}
unsafe { &*c }.done.store(0, Ordering::Release);
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn mutex_trylock_reflects_lock_state() {
let mut lock = LinuxMutex {
state: AtomicU8::new(UNLOCKED),
};
assert_eq!(mutex_trylock(&mut lock), 1);
assert_eq!(mutex_trylock(&mut lock), 0);
mutex_unlock(&mut lock);
assert_eq!(mutex_trylock(&mut lock), 1);
}
#[test]
fn local_irq_disable_enable_tracks_depth() {
IRQ_DEPTH.store(0, Ordering::Release);
local_irq_disable();
assert!(irqs_disabled());
local_irq_enable();
assert!(!irqs_disabled());
}
}
@@ -1,6 +1,6 @@
use std::collections::HashMap;
use std::mem;
use std::os::raw::c_int;
use std::os::raw::{c_int, c_ulong};
use std::ptr;
use std::sync::atomic::{AtomicBool, AtomicPtr, AtomicU64, Ordering};
use std::sync::{Arc, Mutex, OnceLock};
@@ -117,8 +117,8 @@ fn join_all_handles(entry: &TimerEntry) {
#[no_mangle]
pub extern "C" fn setup_timer(
timer: *mut TimerList,
function: extern "C" fn(*mut u8),
data: *mut u8,
function: extern "C" fn(c_ulong),
data: c_ulong,
) {
if timer.is_null() {
return;
@@ -131,13 +131,13 @@ pub extern "C" fn setup_timer(
TimerList {
expires: AtomicU64::new(0),
function: AtomicPtr::new(function_ptr),
data: AtomicPtr::new(data),
data: AtomicPtr::new(data as usize as *mut u8),
active: AtomicBool::new(false),
},
);
}
reset_timer_entry(timer, function_ptr, data);
reset_timer_entry(timer, function_ptr, data as usize as *mut u8);
}
#[no_mangle]
@@ -185,8 +185,8 @@ pub extern "C" fn mod_timer(timer: *mut TimerList, expires: u64) -> i32 {
}
let function =
unsafe { std::mem::transmute::<usize, extern "C" fn(*mut u8)>(function_addr) };
function(data_addr as *mut u8);
unsafe { std::mem::transmute::<usize, extern "C" fn(c_ulong)>(function_addr) };
function(data_addr as c_ulong);
if entry_for_thread.generation.load(Ordering::Acquire) == generation {
entry_for_thread.active.store(false, Ordering::Release);