本文主要是介绍Xv6驱动(二):UART串口,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
阅读材料
- Xv6代码:uart.c
- 教材第5章
- 课程视频Lecture9-Interrupts
- 16550 UART手册:ByteRunner.com | TECHNICAL DATA ON 16550
UART寄存器布局
#define RHR 0 // receive holding register (for input bytes)
#define THR 0 // transmit holding register (for output bytes)
#define IER 1 // interrupt enable register
#define IER_RX_ENABLE (1 << 0)
#define IER_TX_ENABLE (1 << 1)
#define FCR 2 // FIFO control register
#define FCR_FIFO_ENABLE (1 << 0)
#define FCR_FIFO_CLEAR (3 << 1) // clear the content of the two FIFOs
#define ISR 2 // interrupt status register
#define LCR 3 // line control register
#define LCR_EIGHT_BITS (3 << 0)
#define LCR_BAUD_LATCH (1 << 7) // special mode to set baud rate
#define LSR 5 // line status register
#define LSR_RX_READY (1 << 0) // input is waiting to be read from RHR
#define LSR_TX_IDLE (1 << 5) // THR can accept another character to send
UART每个寄存器栈1byte,寄存器布局可以参考手册
UART驱动
uartinit函数
函数调用链条:main()--->consoleinit()--->uartinit()
UART是整个内核第一个初始化的组件,因为初始化UART以后,就可以调用printf函数进行输出了
- 关闭UART中断,因为内核初始化UART时不希望被UART的中断打断
- 设置波特率
- 设置传输的word的长度为8bits
- 情况并使能FIFO,注意这个FIFO是UART内部的,对程序员不可见
- 使能中断
- 初始化锁
void uartinit(void)
{// disable interrupts.WriteReg(IER, 0x00);// special mode to set baud rate.WriteReg(LCR, LCR_BAUD_LATCH);// LSB for baud rate of 38.4K.WriteReg(0, 0x03);// MSB for baud rate of 38.4K.WriteReg(1, 0x00);// leave set-baud mode,// and set word length to 8 bits, no parity.WriteReg(LCR, LCR_EIGHT_BITS);// 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(&uart_tx_lock, "uart");
}
uartputc_sync函数
这个函数只会被内核panic函数调用,panic函数是当内核发生严重错误时调用,打印出错信息。
当panic函数被调用时,会设置panicked = 1,这样当其他核心调用printf/panic函数时,会陷入死循环,起到了阻塞输出的作用
该函数会等待THR寄存器为空,然后将字符写入该寄存器中。
void uartputc_sync(int c)
{push_off();if (panicked){for (;;);}// wait for Transmit Holding Empty to be set in LSR.while ((ReadReg(LSR) & LSR_TX_IDLE) == 0);WriteReg(THR, c);pop_off();
}
uartputc函数
函数调用链条:write()--->consolewrite()--->uartputc()
- 判断缓冲区是否已满,如果满了的化调用sleep函数
- 否则,将字符放入缓冲区中,并更新缓冲区索引
- 调用uartstart函数开始执行UART字符传输
void uartputc(int c)
{acquire(&uart_tx_lock);if (panicked){for (;;);}while (uart_tx_w == uart_tx_r + UART_TX_BUF_SIZE){// buffer is full.// wait for uartstart() to open up space in the buffer.sleep(&uart_tx_r, &uart_tx_lock);}uart_tx_buf[uart_tx_w % UART_TX_BUF_SIZE] = c;uart_tx_w += 1;uartstart();release(&uart_tx_lock);
}
uartstart函数
- 首先判断THR是否为满,如果满的化会直接返回
- 移动uart_tx_r索引,标识已经发送一个字符
- 唤醒其他因为缓存区满而sleep的进程
- 发送该字符
void uartstart()
{while (1){if (uart_tx_w == uart_tx_r){// transmit buffer is empty.ReadReg(ISR);return;}if ((ReadReg(LSR) & LSR_TX_IDLE) == 0){// the UART transmit holding register is full,// so we cannot give it another byte.// it will interrupt when it's ready for a new byte.return;}int c = uart_tx_buf[uart_tx_r % UART_TX_BUF_SIZE];uart_tx_r += 1;// maybe uartputc() is waiting for space in the buffer.wakeup(&uart_tx_r);WriteReg(THR, c);}
}
uartgetc函数
该函数首先判断RHR寄存器是否有字符,有的化读取并返回该字符;否则返回-1
int uartgetc(void)
{if (ReadReg(LSR) & 0x01){// input data is ready.return ReadReg(RHR);}else{return -1;}
}
uartintr函数
有两种情况UART会产生中断
- 有新的字符到达RHR寄存器
- THR发送完毕,现在THR已经空了,可以接受新的字符
该函数会判断这两种情况,并调用相应的函数来处理
void uartintr(void)
{// read and process incoming characters.while (1){int c = uartgetc();if (c == -1)break;consoleintr(c);}// send buffered characters.acquire(&uart_tx_lock);uartstart();release(&uart_tx_lock);
}
参考资料
The xv6 Kernel-18 uart.c and console.c_哔哩哔哩_bilibili
The xv6 Kernel-18 uart.c and console.c_哔哩哔哩_bilibili
这篇关于Xv6驱动(二):UART串口的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!