e2d2fd138c
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
229 lines
5.7 KiB
Rust
229 lines
5.7 KiB
Rust
use std::collections::HashMap;
|
|
use std::fs::File;
|
|
use std::sync::atomic::{AtomicBool, Ordering};
|
|
use std::sync::{Arc, Mutex};
|
|
|
|
struct SendU8Ptr(*mut u8);
|
|
|
|
impl SendU8Ptr {
|
|
fn as_ptr(&self) -> *mut u8 {
|
|
self.0
|
|
}
|
|
}
|
|
|
|
unsafe impl Send for SendU8Ptr {}
|
|
|
|
pub type IrqHandler = extern "C" fn(i32, *mut u8) -> u32;
|
|
|
|
struct IrqEntry {
|
|
cancel: Arc<AtomicBool>,
|
|
masked: Arc<AtomicBool>,
|
|
fd: Option<File>,
|
|
handle: Option<std::thread::JoinHandle<()>>,
|
|
}
|
|
|
|
lazy_static::lazy_static! {
|
|
static ref IRQ_TABLE: Mutex<HashMap<u32, IrqEntry>> = Mutex::new(HashMap::new());
|
|
}
|
|
|
|
#[no_mangle]
|
|
pub extern "C" fn request_irq(
|
|
irq: u32,
|
|
handler: IrqHandler,
|
|
_flags: u32,
|
|
_name: *const u8,
|
|
dev_id: *mut u8,
|
|
) -> i32 {
|
|
let path = format!("/scheme/irq/{}", irq);
|
|
let fd = match std::fs::File::open(&path) {
|
|
Ok(f) => f,
|
|
Err(e) => {
|
|
log::error!("request_irq: failed to open {} : {}", path, e);
|
|
return -22;
|
|
}
|
|
};
|
|
|
|
let thread_fd = match fd.try_clone() {
|
|
Ok(f) => f,
|
|
Err(e) => {
|
|
log::error!("request_irq: failed to clone {} : {}", path, e);
|
|
return -22;
|
|
}
|
|
};
|
|
|
|
let cancel = Arc::new(AtomicBool::new(false));
|
|
let masked = Arc::new(AtomicBool::new(false));
|
|
let cancel_clone = Arc::clone(&cancel);
|
|
let masked_clone = Arc::clone(&masked);
|
|
let send_dev_id = SendU8Ptr(dev_id);
|
|
|
|
let handle = std::thread::spawn(move || {
|
|
use std::io::Read;
|
|
let mut fd = thread_fd;
|
|
let mut buf = [0u8; 8];
|
|
loop {
|
|
if cancel_clone.load(Ordering::Acquire) {
|
|
break;
|
|
}
|
|
|
|
match fd.read(&mut buf) {
|
|
Ok(0) | Err(_) => break,
|
|
Ok(_) => {
|
|
if cancel_clone.load(Ordering::Acquire) {
|
|
break;
|
|
}
|
|
if masked_clone.load(Ordering::Acquire) {
|
|
continue;
|
|
}
|
|
handler(irq as i32, send_dev_id.as_ptr());
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
let entry = IrqEntry {
|
|
cancel: Arc::clone(&cancel),
|
|
masked: Arc::clone(&masked),
|
|
fd: Some(fd),
|
|
handle: Some(handle),
|
|
};
|
|
|
|
if let Ok(mut table) = IRQ_TABLE.lock() {
|
|
table.insert(irq, entry);
|
|
} else {
|
|
cancel.store(true, Ordering::Release);
|
|
let mut entry = entry;
|
|
let _ = entry.fd.take();
|
|
if let Some(handle) = entry.handle.take() {
|
|
let _ = handle.join();
|
|
}
|
|
log::error!("request_irq: failed to record handler for IRQ {}", irq);
|
|
return -22;
|
|
}
|
|
|
|
log::info!("request_irq: registered handler for IRQ {}", irq);
|
|
0
|
|
}
|
|
|
|
#[no_mangle]
|
|
pub extern "C" fn free_irq(irq: u32, _dev_id: *mut u8) {
|
|
let entry = if let Ok(mut table) = IRQ_TABLE.lock() {
|
|
let mut entry = table.remove(&irq);
|
|
if let Some(ref mut entry_ref) = entry {
|
|
entry_ref.cancel.store(true, Ordering::Release);
|
|
let _ = entry_ref.fd.take();
|
|
}
|
|
entry
|
|
} else {
|
|
None
|
|
};
|
|
|
|
if let Some(mut entry) = entry {
|
|
if let Some(handle) = entry.handle.take() {
|
|
let _ = handle.join();
|
|
}
|
|
}
|
|
log::info!("free_irq: released IRQ {}", irq);
|
|
}
|
|
|
|
#[no_mangle]
|
|
pub extern "C" fn disable_irq_nosync(irq: u32) {
|
|
disable_irq(irq)
|
|
}
|
|
|
|
#[no_mangle]
|
|
pub extern "C" fn enable_irq(irq: u32) {
|
|
if let Ok(table) = IRQ_TABLE.lock() {
|
|
if let Some(entry) = table.get(&irq) {
|
|
entry.masked.store(false, Ordering::Release);
|
|
log::trace!("enable_irq: unmasked IRQ {}", irq);
|
|
}
|
|
}
|
|
}
|
|
|
|
#[no_mangle]
|
|
pub extern "C" fn disable_irq(irq: u32) {
|
|
if let Ok(table) = IRQ_TABLE.lock() {
|
|
if let Some(entry) = table.get(&irq) {
|
|
entry.masked.store(true, Ordering::Release);
|
|
log::trace!("disable_irq: masked IRQ {}", irq);
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn request_irq_returns_error_without_scheme() {
|
|
let result = request_irq(99, test_handler, 0, std::ptr::null(), std::ptr::null_mut());
|
|
assert_eq!(
|
|
result, -22,
|
|
"request_irq should return -EINVAL without /scheme/irq/"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn free_irq_on_unregistered_is_safe() {
|
|
free_irq(42, std::ptr::null_mut());
|
|
}
|
|
|
|
#[test]
|
|
fn enable_irq_on_unregistered_is_noop() {
|
|
enable_irq(42);
|
|
}
|
|
|
|
#[test]
|
|
fn disable_irq_on_unregistered_is_noop() {
|
|
disable_irq(42);
|
|
}
|
|
|
|
#[test]
|
|
fn disable_irq_nosync_is_equivalent_to_disable_irq() {
|
|
// Insert a synthetic entry directly, then verify disable_irq_nosync
|
|
// sets the same masked state as disable_irq.
|
|
let cancel = Arc::new(AtomicBool::new(false));
|
|
let masked = Arc::new(AtomicBool::new(false));
|
|
|
|
let entry = IrqEntry {
|
|
cancel,
|
|
masked: Arc::clone(&masked),
|
|
fd: None,
|
|
handle: None,
|
|
};
|
|
|
|
{
|
|
let mut table = IRQ_TABLE.lock().unwrap();
|
|
table.insert(200, entry);
|
|
}
|
|
|
|
disable_irq_nosync(200);
|
|
assert!(
|
|
masked.load(Ordering::Acquire),
|
|
"disable_irq_nosync should set masked=true"
|
|
);
|
|
|
|
enable_irq(200);
|
|
assert!(
|
|
!masked.load(Ordering::Acquire),
|
|
"enable_irq should set masked=false"
|
|
);
|
|
|
|
disable_irq(200);
|
|
assert!(
|
|
masked.load(Ordering::Acquire),
|
|
"disable_irq should set masked=true"
|
|
);
|
|
|
|
{
|
|
let mut table = IRQ_TABLE.lock().unwrap();
|
|
table.remove(&200);
|
|
}
|
|
}
|
|
|
|
extern "C" fn test_handler(_irq: i32, _dev_id: *mut u8) -> u32 {
|
|
0
|
|
}
|
|
}
|