use core::fmt::{Error, Write}; /// Initialize the UART driver by setting /// the word length, FIFOs, and interrupts pub fn uart_init(base_addr: usize) { let ptr = base_addr as *mut u8; unsafe { // First, set the word length, which // are bits 0, and 1 of the line control register (LCR) // which is at base_address + 3 // We can easily write the value 3 here or 0b11, but I'm // extending it so that it is clear we're setting two individual // fields // Word 0 Word 1 // ~~~~~~ ~~~~~~ let lcr = (1 << 0) | (1 << 1); ptr.add(3).write_volatile(lcr); // Now, enable the FIFO, which is bit index 0 of the FIFO // control register (FCR at offset 2). // Again, we can just write 1 here, but when we use left shift, // it's easier to see that we're trying to write bit index #0. ptr.add(2).write_volatile(1 << 0); // Enable receiver buffer interrupts, which is at bit index // 0 of the interrupt enable register (IER at offset 1). ptr.add(1).write_volatile(1 << 0); // If we cared about the divisor, the code below would set the divisor // from a global clock rate of 22.729 MHz (22,729,000 cycles per second) // to a signaling rate of 2400 (BAUD). We usually have much faster signalling // rates nowadays, but this demonstrates what the divisor actually does. // The formula given in the NS16500A specification for calculating the divisor // is: // divisor = ceil( (clock_hz) / (baud_sps x 16) ) // So, we substitute our values and get: // divisor = ceil( 22_729_000 / (2400 x 16) ) // divisor = ceil( 22_729_000 / 38_400 ) // divisor = ceil( 591.901 ) = 592 // The divisor register is two bytes (16 bits), so we need to split the value // 592 into two bytes. Typically, we would calculate this based on measuring // the clock rate, but again, for our purposes [qemu], this doesn't really do // anything. let divisor: u16 = 592; let divisor_least: u8 = (divisor & 0xff).try_into().unwrap(); let divisor_most: u8 = (divisor >> 8).try_into().unwrap(); // Notice that the divisor register DLL (divisor latch least) and DLM (divisor // latch most) have the same base address as the receiver/transmitter and the // interrupt enable register. To change what the base address points to, we // open the "divisor latch" by writing 1 into the Divisor Latch Access Bit // (DLAB), which is bit index 7 of the Line Control Register (LCR) which // is at base_address + 3. ptr.add(3).write_volatile(lcr | 1 << 7); // Now, base addresses 0 and 1 point to DLL and DLM, respectively. // Put the lower 8 bits of the divisor into DLL ptr.add(0).write_volatile(divisor_least); ptr.add(1).write_volatile(divisor_most); // Now that we've written the divisor, we never have to touch this again. In // hardware, this will divide the global clock (22.729 MHz) into one suitable // for 2,400 signals per second. So, to once again get access to the // RBR/THR/IER registers, we need to close the DLAB bit by clearing it to 0. ptr.add(3).write_volatile(lcr); } } fn uart_get(base_addr: usize) -> Option { let ptr = base_addr as *mut u8; unsafe { // Bit index #5 is the Line Control Register. if ptr.add(5).read_volatile() & 1 == 0 { // The DR bit is 0, meaning no data None } else { // The DR bit is 1, meaning data! Some(ptr.add(0).read_volatile()) } } } fn uart_put(base_addr: usize, c: u8) { let ptr = base_addr as *mut u8; unsafe { // If we get here, the transmitter is empty, so transmit // our stuff! ptr.add(0).write_volatile(c); } } pub struct Uart { base_address: usize, } impl Uart { pub fn new(base_address: usize) -> Self { Uart { base_address } } pub fn get(&self) -> Option { uart_get(self.base_address) } pub fn put(&self, c: u8) { uart_put(self.base_address, c); } pub fn init(&self) { uart_init(self.base_address); } } // This is a slightly different syntax. Write is this "trait", meaning it is much like // an interface where we're just guaranteeing a certain function signature. In the Write // trait, one is absolutely required to be implemented, which is write_str. There are other // functions, but they all rely on write_str(), so their default implementation is OK for now. impl Write for Uart { // The trait Write expects us to write the function write_str // which looks like: fn write_str(&mut self, s: &str) -> Result<(), Error> { for c in s.bytes() { self.put(c); } // Return that we succeeded. Ok(()) } }