mirror of
https://github.com/sgmarz/osblog.git
synced 2024-11-27 20:03:32 +04:00
163 lines
6.0 KiB
Rust
163 lines
6.0 KiB
Rust
// plic.rs
|
|
// Platform Level Interrupt Controller (PLIC)
|
|
// Stephen Marz
|
|
// 1 Nov 2019
|
|
|
|
use crate::uart::Uart;
|
|
use crate::virtio;
|
|
|
|
const PLIC_PRIORITY: usize = 0x0c00_0000;
|
|
const PLIC_PENDING: usize = 0x0c00_1000;
|
|
const PLIC_INT_ENABLE: usize = 0x0c00_2000;
|
|
const PLIC_THRESHOLD: usize = 0x0c20_0000;
|
|
const PLIC_CLAIM: usize = 0x0c20_0004;
|
|
|
|
// Each register is 4-bytes (u32)
|
|
// The PLIC is an external interrupt controller. The one
|
|
// used by QEMU virt is the same as the SiFive PLIC.
|
|
// https://sifive.cdn.prismic.io/sifive%2F834354f0-08e6-423c-bf1f-0cb58ef14061_fu540-c000-v1.0.pdf
|
|
|
|
// Chapter 10 explains the priority, pending, interrupt enable, threshold and claims
|
|
|
|
// The virt machine has the following external interrupts (from Qemu source):
|
|
// Interrupt 0 is a "null" interrupt and is hardwired to 0.
|
|
// VIRTIO = [1..8]
|
|
// UART0 = 10
|
|
// PCIE = [32..35]
|
|
|
|
|
|
/// Get the next available interrupt. This is the "claim" process.
|
|
/// The plic will automatically sort by priority and hand us the
|
|
/// ID of the interrupt. For example, if the UART is interrupting
|
|
/// and it's next, we will get the value 10.
|
|
pub fn next() -> Option<u32> {
|
|
let claim_reg = PLIC_CLAIM as *const u32;
|
|
let claim_no;
|
|
// The claim register is filled with the highest-priority, enabled interrupt.
|
|
unsafe {
|
|
claim_no = claim_reg.read_volatile();
|
|
}
|
|
if claim_no == 0 {
|
|
// The interrupt 0 is hardwired to 0, which tells us that there is no
|
|
// interrupt to claim, hence we return None.
|
|
None
|
|
}
|
|
else {
|
|
// If we get here, we've gotten a non-0 interrupt.
|
|
Some(claim_no)
|
|
}
|
|
}
|
|
|
|
/// Complete a pending interrupt by id. The id should come
|
|
/// from the next() function above.
|
|
pub fn complete(id: u32) {
|
|
let complete_reg = PLIC_CLAIM as *mut u32;
|
|
unsafe {
|
|
// We actually write a u32 into the entire complete_register.
|
|
// This is the same register as the claim register, but it can
|
|
// differentiate based on whether we're reading or writing.
|
|
complete_reg.write_volatile(id);
|
|
}
|
|
}
|
|
|
|
/// Set the global threshold. The threshold can be a value [0..7].
|
|
/// The PLIC will mask any interrupts at or below the given threshold.
|
|
/// This means that a threshold of 7 will mask ALL interrupts and
|
|
/// a threshold of 0 will allow ALL interrupts.
|
|
pub fn set_threshold(tsh: u8) {
|
|
// We do tsh because we're using a u8, but our maximum number
|
|
// is a 3-bit 0b111. So, we and with 7 (0b111) to just get the
|
|
// last three bits.
|
|
let actual_tsh = tsh & 7;
|
|
let tsh_reg = PLIC_THRESHOLD as *mut u32;
|
|
unsafe {
|
|
tsh_reg.write_volatile(actual_tsh as u32);
|
|
}
|
|
}
|
|
|
|
/// See if a given interrupt id is pending.
|
|
pub fn is_pending(id: u32) -> bool {
|
|
let pend = PLIC_PENDING as *const u32;
|
|
let actual_id = 1 << id;
|
|
let pend_ids;
|
|
unsafe {
|
|
pend_ids = pend.read_volatile();
|
|
}
|
|
actual_id & pend_ids != 0
|
|
}
|
|
|
|
/// Enable a given interrupt id
|
|
pub fn enable(id: u32) {
|
|
let enables = PLIC_INT_ENABLE as *mut u32;
|
|
let actual_id = 1 << id;
|
|
unsafe {
|
|
// Unlike the complete and claim registers, the plic_int_enable
|
|
// register is a bitset where the id is the bit index. The register
|
|
// is a 32-bit register, so that gives us enables for interrupts
|
|
// 31 through 1 (0 is hardwired to 0).
|
|
enables.write_volatile(enables.read_volatile() | actual_id);
|
|
}
|
|
}
|
|
|
|
/// Set a given interrupt priority to the given priority.
|
|
/// The priority must be [0..7]
|
|
pub fn set_priority(id: u32, prio: u8) {
|
|
let actual_prio = prio as u32 & 7;
|
|
let prio_reg = PLIC_PRIORITY as *mut u32;
|
|
unsafe {
|
|
// The offset for the interrupt id is:
|
|
// PLIC_PRIORITY + 4 * id
|
|
// Since we're using pointer arithmetic on a u32 type,
|
|
// it will automatically multiply the id by 4.
|
|
prio_reg.add(id as usize).write_volatile(actual_prio);
|
|
}
|
|
}
|
|
|
|
pub fn handle_interrupt() {
|
|
if let Some(interrupt) = next() {
|
|
// If we get here, we've got an interrupt from the claim register. The PLIC will
|
|
// automatically prioritize the next interrupt, so when we get it from claim, it
|
|
// will be the next in priority order.
|
|
match interrupt {
|
|
1..=8 => {
|
|
virtio::handle_interrupt(interrupt);
|
|
},
|
|
10 => { // Interrupt 10 is the UART interrupt.
|
|
// We would typically set this to be handled out of the interrupt context,
|
|
// but we're testing here! C'mon!
|
|
// We haven't yet used the singleton pattern for my_uart, but remember, this
|
|
// just simply wraps 0x1000_0000 (UART).
|
|
let mut my_uart = Uart::new(0x1000_0000);
|
|
// If we get here, the UART better have something! If not, what happened??
|
|
if let Some(c) = my_uart.get() {
|
|
// If you recognize this code, it used to be in the lib.rs under kmain(). That
|
|
// was because we needed to poll for UART data. Now that we have interrupts,
|
|
// here it goes!
|
|
match c {
|
|
8 => {
|
|
// This is a backspace, so we
|
|
// essentially have to write a space and
|
|
// backup again:
|
|
print!("{} {}", 8 as char, 8 as char);
|
|
},
|
|
10 | 13 => {
|
|
// Newline or carriage-return
|
|
println!();
|
|
},
|
|
_ => {
|
|
print!("{}", c as char);
|
|
},
|
|
}
|
|
}
|
|
|
|
},
|
|
_ => {
|
|
println!("Unknown external interrupt: {}", interrupt);
|
|
}
|
|
}
|
|
// We've claimed it, so now say that we've handled it. This resets the interrupt pending
|
|
// and allows the UART to interrupt again. Otherwise, the UART will get "stuck".
|
|
complete(interrupt);
|
|
}
|
|
}
|