Files
RedBear-OS/local/recipes/drivers/linux-kpi/source/src/rust_impl/irq.rs
T

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
}
}