iT邦幫忙

0

[6.1810][code] xv6 的 Device (一) : uart

  • 分享至 

  • xImage
  •  

系列文章 : [6.1810] 跟著 MIT 6.1810 學習基礎作業系統觀念

大綱

  • kernel/uart.c/uartinit
  • kernel/uart.c/uartwrite
  • kernel/uart.c/uartputc_sync
  • kernel/uart.c/uartgetc
  • kernel/uart.c/uartintr

kernel/uart.c/uartinit

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 的模式。當我們設定這個模式後,

  • RHR/THR →DLL
  • IER →DLM

Divior 本身是 16 bits,所以需要由 DLL ( 8 bits ) 跟 DLM ( 8 bits ) 組成。

  • Divisor = (DLM << 8) + DLL

目前假設 uart 的 clk 是 standard PC COM port frequency ( 1843200 Hz, 1.8432 MHz )
則設定 baud rate 的公式是

  • Divisor = 1843200 / (16 * (Desired Baud Rate))

xv6-riscv 的預期 baud rate 是 38.4 K ( 38400 ),於是我們的 divisor 需要設為

  • 1843200 / (16 * (38400)) = 3

 // 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 清掉,回到一般的模式。

  • DLL → RHR/THR
  • DLM → IER

 // reset and enable FIFOs.
  WriteReg(FCR, FCR_FIFO_ENABLE | FCR_FIFO_CLEAR);
  • enable FIFO 模式,這讓輸入 ( receive ) 跟輸出 ( transmit ) 會有 16 bytes 的 buffer。
  • reset recieve-fifo 以及 transmit-fifo,把 fifo 清空。

  // enable transmit and receive interrupts.
  WriteReg(IER, IER_TX_ENABLE | IER_RX_ENABLE);
  • IER_RX_ENABLE : 當有資料可以被讀取的時候,發 interrupt 給 CPU
  • IER_TX_ENABLE : 當我們可以輸出的時候,發 interrupt 給 CPU

  initlock(&tx_lock, "uart");
}

初始化 tx_lock 這個 spinlock。
所以想要寫入 THR register 的 CPU 都需要拿到這個 spinlock,以免多個 CPU 同時寫 THR register。



kernel/uart.c/uartwrite

// 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);
}
  • 因為這個 function 會 sleep,所以不行在 interrupt handler 裡面被使用,只能在 system call 裡使用。
  • 每次寫入一個字元後,就會把 tx_busy 標示為 1,並等待 uart 的 interrupt handler ( uartintr ) 將 tx_busy 清為 0。


kernel/uart.c/uartputc_sync

// 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(;;)
      ;
  }
  • panicking / panicked : 已經遇到無法挽回的錯誤,準備進入 infinite loop
    • panicking : 正在輸出 panic 訊息
    • panicked : 輸出完 panic 訊息,準備進入無窮迴圈
  • push_off : 用於 disable interrupt

 // 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。



kernel/uart.c/uartgetc

// 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;
  }
}
  • LSR_RX_READY 表示 RHR 有資料可以讀取
  • 假如沒有資料可以讀取,就直接 return -1


kernel/uart.c/uartintr

// 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 的作用。


圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言