系列文章 : [6.1810] 跟著 MIT 6.1810 學習基礎作業系統觀念
void
uartinit(void)
{
// disable interrupts.
WriteReg(IER, 0x00);
在剛開始初始化的時候,先把 UART 本身的 interrupt disable,以免初始化到一半,UART 突然打 interrupt 給 CPU。
// special mode to set baud rate.
WriteReg(LCR, LCR_BAUD_LATCH);
// Divisor = (DLM << 8) + DLL
// Divisor = 1843200 / (16 * (Desired Baud Rate))
// standard PC COM port frequency is 1843200 Hz ( 1.8432 MHz )
//
// In this example :
// 3 = (1843200) / (16 * (Desired Baud Rate))
// Desired Baud Rate = 1843200 / 48 = 38400 = 38.4 K
//
// LSB for baud rate of 38.4K.
WriteReg(0, 0x03);
// MSB for baud rate of 38.4K.
WriteReg(1, 0x00);
設定 LCR resgister,讓我們進入設定 baud rate 的模式。當我們設定這個模式後,
Divior 本身是 16 bits,所以需要由 DLL ( 8 bits ) 跟 DLM ( 8 bits ) 組成。
目前假設 uart 的 clk 是 standard PC COM port frequency ( 1843200 Hz, 1.8432 MHz )
則設定 baud rate 的公式是
xv6-riscv 的預期 baud rate 是 38.4 K ( 38400 ),於是我們的 divisor 需要設為
// leave set-baud mode,
// and set word length to 8 bits, no parity.
WriteReg(LCR, LCR_EIGHT_BITS);
這邊將每一個 character 的 word length 設為 8 bits
並順便將 LCR_BAUD_LATCH 清掉,回到一般的模式。
// reset and enable FIFOs.
WriteReg(FCR, FCR_FIFO_ENABLE | FCR_FIFO_CLEAR);
// enable transmit and receive interrupts.
WriteReg(IER, IER_TX_ENABLE | IER_RX_ENABLE);
initlock(&tx_lock, "uart");
}
初始化 tx_lock 這個 spinlock。
所以想要寫入 THR register 的 CPU 都需要拿到這個 spinlock,以免多個 CPU 同時寫 THR register。
// transmit buf[] to the uart. it blocks if the
// uart is busy, so it cannot be called from
// interrupts, only from write() system calls.
void
uartwrite(char buf[], int n)
{
acquire(&tx_lock);
int i = 0;
while(i < n){
while(tx_busy != 0){
// wait for a UART transmit-complete interrupt
// to set tx_busy to 0.
sleep(&tx_chan, &tx_lock);
}
WriteReg(THR, buf[i]);
i += 1;
tx_busy = 1;
}
release(&tx_lock);
}
uartintr ) 將 tx_busy 清為 0。// write a byte to the uart without using
// interrupts, for use by kernel printf() and
// to echo characters. it spins waiting for the uart's
// output register to be empty.
void
uartputc_sync(int c)
{
if(panicking == 0)
push_off();
if(panicked){
for(;;)
;
}
// wait for UART to set Transmit Holding Empty in LSR.
while((ReadReg(LSR) & LSR_TX_IDLE) == 0)
;
WriteReg(THR, c);
每次要送出一個字元的時候,就不斷的去讀取 LSR,看 LSR_TX_IDLE bit 是否有空閒。
if(panicking == 0)
pop_off();
}
用 re-enable 來嘗試 re-enable interrupt。
// try to read one input character from the UART.
// return -1 if none is waiting.
int
uartgetc(void)
{
if(ReadReg(LSR) & LSR_RX_READY){
// input data is ready.
return ReadReg(RHR);
} else {
return -1;
}
}
// handle a uart interrupt, raised because input has
// arrived, or the uart is ready for more output, or
// both. called from devintr().
void
uartintr(void)
{
ReadReg(ISR); // acknowledge the interrupt
讀取 ISR 來 asknowledge interrupt。
Q: 不讀取會怎麼樣呢 ? 拿掉之後也是能正常 boot to shell。
acquire(&tx_lock);
if(ReadReg(LSR) & LSR_TX_IDLE){
// UART finished transmitting; wake up sending thread.
tx_busy = 0;
wakeup(&tx_chan);
}
release(&tx_lock);
檢查 LSR_TX_IDLE,假如該 bit 被 assert 的話,表示 transmit 已經完成,可以開始 transmit 下一筆資料,於是會用 wakeup 叫醒所有因為 tx_busy 而睡著的人。
// read and process incoming characters, if any.
while(1){
int c = uartgetc();
if(c == -1)
break;
consoleintr(c);
}
}
嘗試使用 uartgetc 去讀取字元,假如沒有讀取成功 ( 拿到 -1 ),就直接跳出迴圈。
有讀取成功就丟給 consoleintr,目前還不知道 consoleintr 的作用。