mirror of
https://github.com/sgmarz/osblog.git
synced 2024-11-27 20:03:32 +04:00
123 lines
3.8 KiB
Rust
123 lines
3.8 KiB
Rust
|
// uart.rs
|
||
|
// UART routines and driver
|
||
|
|
||
|
use core::{convert::TryInto,
|
||
|
fmt::{Error, Write}};
|
||
|
|
||
|
pub struct Uart {
|
||
|
base_address: usize,
|
||
|
}
|
||
|
|
||
|
impl Write for Uart {
|
||
|
fn write_str(&mut self, out: &str) -> Result<(), Error> {
|
||
|
for c in out.bytes() {
|
||
|
self.put(c);
|
||
|
}
|
||
|
Ok(())
|
||
|
}
|
||
|
}
|
||
|
|
||
|
impl Uart {
|
||
|
pub fn new(base_address: usize) -> Self {
|
||
|
Uart { base_address }
|
||
|
}
|
||
|
|
||
|
pub fn init(&mut self) {
|
||
|
let ptr = self.base_address 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: u8 = (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);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
pub fn put(&mut self, c: u8) {
|
||
|
let ptr = self.base_address as *mut u8;
|
||
|
unsafe {
|
||
|
ptr.add(0).write_volatile(c);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
pub fn get(&mut self) -> Option<u8> {
|
||
|
let ptr = self.base_address as *mut u8;
|
||
|
unsafe {
|
||
|
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())
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|