实验五 输入
QEMU的virt机器默认没有键盘作为输入设备,但当我们执行QEMU使用 -nographic 参数(disable graphical output and redirect serial I/Os to console)时QEMU会将串口重定向到控制台,因此我们可以使用UART作为输入设备。
tock-registers库
在 实验四 中断 中,针对GICD,GICC,TIMER等硬件我们定义了大量的常量和寄存器值,这在使用时过于繁琐也容易出错。因此我们决定采用 tock-registers 库 [1]。
在 Cargo.toml 中的 [dependencies]
节中加入依赖:
tock-registers = { version = "0.7.x", default-features = false, features = ["register_types"], optional = true }
为了不至于使 uart_console.rs 文件过长,我们选择重构 uart_console.rs。首先创建 src/uart_console 目录,并将原 uart_console.rs 更名为 mod.rs ,且置于 src/uart_console 目录下, 最后新建 src/uart_console/pl011.rs 文件。目录结构看起来像这样:
.
|____virt.dts
|____virt.dtb
|____Cargo.toml
|____Cargo.lock
|____.cargo
| |____config.toml
|____aarch64-qemu.ld
|____.vscode
| |____launch.json
|____aarch64-unknown-none-softfloat.json
|____src
| |____panic.rs
| |____start.s
| |____interrupts.rs
| |____main.rs
| |____uart_console
| | |____mod.rs
| | |____pl011.rs
| |____exception.s
然后依据tock_registers库的要求对pl011所涉及到的寄存器 [2] 进行描述,内容为:
use tock_registers::{registers::{ReadOnly, ReadWrite, WriteOnly}, register_bitfields, register_structs};
pub const PL011REGS: *mut PL011Regs = (0x0900_0000) as *mut PL011Regs;
register_bitfields![
u32,
pub UARTDR [
DATA OFFSET(0) NUMBITS(8) []
],
/// Flag Register
pub UARTFR [
/// Transmit FIFO full. The meaning of this bit depends on the
/// state of the FEN bit in the UARTLCR_ LCRH Register. If the
/// FIFO is disabled, this bit is set when the transmit
/// holding register is full. If the FIFO is enabled, the TXFF
/// bit is set when the transmit FIFO is full.
TXFF OFFSET(6) NUMBITS(1) [],
/// Receive FIFO empty. The meaning of this bit depends on the
/// state of the FEN bit in the UARTLCR_H Register. If the
/// FIFO is disabled, this bit is set when the receive holding
/// register is empty. If the FIFO is enabled, the RXFE bit is
/// set when the receive FIFO is empty.
RXFE OFFSET(4) NUMBITS(1) []
],
/// Integer Baud rate divisor
pub UARTIBRD [
/// Integer Baud rate divisor
IBRD OFFSET(0) NUMBITS(16) []
],
/// Fractional Baud rate divisor
pub UARTFBRD [
/// Fractional Baud rate divisor
FBRD OFFSET(0) NUMBITS(6) []
],
/// Line Control register
pub UARTLCR_H [
/// Parity enable. If this bit is set to 1, parity checking and generation
/// is enabled, else parity is disabled and no parity bit added to the data frame.
PEN OFFSET(1) NUMBITS(1) [
Disabled = 0,
Enabled = 1
],
/// Two stop bits select. If this bit is set to 1, two stop bits are transmitted
/// at the end of the frame.
STP2 OFFSET(3) NUMBITS(1) [
Stop1 = 0,
Stop2 = 1
],
/// Enable FIFOs.
FEN OFFSET(4) NUMBITS(1) [
Disabled = 0,
Enabled = 1
],
/// Word length. These bits indicate the number of data bits
/// transmitted or received in a frame.
WLEN OFFSET(5) NUMBITS(2) [
FiveBit = 0b00,
SixBit = 0b01,
SevenBit = 0b10,
EightBit = 0b11
]
],
/// Control Register
pub UARTCR [
/// Receive enable. If this bit is set to 1, the receive
/// section of the UART is enabled. Data reception occurs for
/// UART signals. When the UART is disabled in the middle of
/// reception, it completes the current character before
/// stopping.
RXE OFFSET(9) NUMBITS(1) [
Disabled = 0,
Enabled = 1
],
/// Transmit enable. If this bit is set to 1, the transmit
/// section of the UART is enabled. Data transmission occurs
/// for UART signals. When the UART is disabled in the middle
/// of transmission, it completes the current character before
/// stopping.
TXE OFFSET(8) NUMBITS(1) [
Disabled = 0,
Enabled = 1
],
/// UART enable
UARTEN OFFSET(0) NUMBITS(1) [
/// If the UART is disabled in the middle of transmission
/// or reception, it completes the current character
/// before stopping.
Disabled = 0,
Enabled = 1
]
],
pub UARTIMSC [
RXIM OFFSET(4) NUMBITS(1) [
Disabled = 0,
Enabled = 1
]
],
/// Interupt Clear Register
pub UARTICR [
/// Meta field for all pending interrupts
ALL OFFSET(0) NUMBITS(11) [
Clear = 0x7ff
]
]
];
register_structs! {
pub PL011Regs {
(0x00 => pub dr: ReadWrite<u32, UARTDR::Register>), // 0x00
(0x04 => __reserved_0), // 0x04
(0x18 => pub fr: ReadOnly<u32, UARTFR::Register>), // 0x18
(0x1c => __reserved_1), // 0x1c
(0x24 => pub ibrd: WriteOnly<u32, UARTIBRD::Register>), // 0x24
(0x28 => pub fbrd: WriteOnly<u32, UARTFBRD::Register>), // 0x28
(0x2C => pub lcr_h: WriteOnly<u32, UARTLCR_H::Register>), // 0x2C
(0x30 => pub cr: WriteOnly<u32, UARTCR::Register>), // 0x30
(0x34 => __reserved_2), // 0x34
(0x38 => pub imsc: ReadWrite<u32, UARTIMSC::Register>), // 0x38
(0x44 => pub icr: WriteOnly<u32, UARTICR::Register>), // 0x44
(0x48 => @END),
}
}
看起来好像比 实验四 中断 中对应的寄存器描述部分要复杂,但如果你熟悉了之后,基本上可以依据技术参考手册中的寄存器描述无脑写了。
提示
register_bitfields 宏按照寄存器的位结构进行描述,注意最后要加分号“;”,只要注册自己想处理的位即可。
register_structs 宏最后需加上(0x** => @END),表示结束。
数据接收中断
在 src/uart_console/mod.rs 中引入库
use tock_registers::{interfaces::Writeable};
pub mod pl011;
use pl011::*;
lazy_static! {
/// A global `Writer` instance that can be used for printing to the VGA text buffer.
///
/// Used by the `print!` and `println!` macros.
pub static ref WRITER: Mutex<Writer> = Mutex::new(Writer::new());
}
同时为 Writer
结构实现构造函数如下:
pub fn new() -> Writer{
unsafe {
// pl011 device registers
let pl011r: &PL011Regs = &*PL011REGS;
// 禁用pl011
pl011r.cr.write(UARTCR::TXE::Disabled + UARTCR::RXE::Disabled + UARTCR::UARTEN::Disabled);
// 清空中断状态
pl011r.icr.write(UARTICR::ALL::Clear);
// 设定中断mask,需要使能的中断
pl011r.imsc.write(UARTIMSC::RXIM::Enabled);
// IBRD = UART_CLK / (16 * BAUD_RATE)
// FBRD = ROUND((64 * MOD(UART_CLK,(16 * BAUD_RATE))) / (16 * BAUD_RATE))
// UART_CLK = 24M
// BAUD_RATE = 115200
pl011r.ibrd.write(UARTIBRD::IBRD.val(13));
pl011r.fbrd.write(UARTFBRD::FBRD.val(1));
// 8N1 FIFO enable
pl011r.lcr_h.write(UARTLCR_H::WLEN::EightBit + UARTLCR_H::PEN::Disabled + UARTLCR_H::STP2::Stop1
+ UARTLCR_H::FEN::Enabled);
// enable pl011
pl011r.cr.write(UARTCR::UARTEN::Enabled + UARTCR::RXE::Enabled + UARTCR::TXE::Enabled);
}
Writer
}
修改 write_byte
函数使用我们通过宏描述的寄存器:
pub fn write_byte(&mut self, byte: u8) {
// const UART0: *mut u8 = 0x0900_0000 as *mut u8;
unsafe {
// pl011 device registers
let pl011r: &PL011Regs = &*PL011REGS;
// ptr::write_volatile(UART0, byte);
pl011r.dr.write(UARTDR::DATA.val(byte as u32));
}
}
在 src/interrupts.rs 中的 init_gicv2
函数中对UART的数据接收中断进行初始化:
// 初始化UART0 中断
// interrupts = <0x00 0x01 0x04>; SPI, 0x01, level
set_config(UART0_IRQ, ICFGR_LEVEL); //电平触发
set_priority(UART0_IRQ, 0); //优先级设定
// set_core(TIMER_IRQ, 0x1); // 单核实现无需设置中断目标核
clear(UART0_IRQ); //清除中断请求
enable(UART0_IRQ); //使能中断
然后对UART的数据接收中断进行处理,并修改timer中断的处理方法,使之每隔2秒输出一个点。
const UART0_IRQ: u32 = 33;
fn handle_irq_lines(ctx: &mut ExceptionCtx, _core_num: u32, irq_num: u32) {
if irq_num == TIMER_IRQ {
handle_timer_irq(ctx);
}else if irq_num == UART0_IRQ {
handle_uart0_rx_irq(ctx);
}
else{
catch(ctx, EL1_IRQ);
}
}
fn handle_timer_irq(_ctx: &mut ExceptionCtx){
crate::print!(".");
// 每2秒产生一次中断
unsafe {
asm!("mrs x1, CNTFRQ_EL0");
asm!("add x1, x1, x1");
asm!("msr CNTP_TVAL_EL0, x1");
}
}
fn handle_uart0_rx_irq(_ctx: &mut ExceptionCtx){
use crate::uart_console::pl011::*;
// crate::print!("R");
unsafe{
// pl011 device registers
let pl011r: &PL011Regs = &*PL011REGS;
let mut flag = pl011r.fr.read(UARTFR::RXFE);
while flag != 1 {
let value = pl011r.dr.read(UARTDR::DATA);
crate::print!("{}", value as u8 as char);
flag = pl011r.fr.read(UARTFR::RXFE);
}
}
}
#[no_mangle]
unsafe extern "C" fn el1_irq(ctx: &mut ExceptionCtx) {
// reads this register to obtain the interrupt ID of the signaled interrupt.
// This read acts as an acknowledge for the interrupt.
// 中断确认
let value: u32 = ptr::read_volatile(GICC_IAR);
let irq_num: u32 = value & 0x1ff;
let core_num: u32 = value & 0xe00;
// 实际处理中断
handle_irq_lines(ctx, core_num, irq_num);
// catch(ctx, EL1_IRQ);
// A processor writes to this register to inform the CPU interface either:
// • that it has completed the processing of the specified interrupt
// • in a GICv2 implementation, when the appropriate GICC_CTLR.EOImode bit is set to 1, to indicate that the interface should perform priority drop for the specified interrupt.
// 标记中断完成,清除相应中断位
ptr::write_volatile(GICC_EOIR, core_num | irq_num);
clear(irq_num);
}