说明

UART全称是Universal Asynchronous Receiver/Transmitter,这里它表示的是一种实现串口通信的芯片,在整个串口系统中它的位置如下图所示:

RS232      +-----------+   +-----------+   +-----------+   +-----------+
Interface  | Line      |   |           |   | Interface |   |           |
-----------+ Driver /  +---+   UART    +---+ Logic     +---+    CPU    || Receiver  |   |           |   |           |   |           |+-----------+   +-----+-----+   +-----+-----+   +-----------+|               ||               |+-----+-----+         || Baud Rate |         || Generator +---------+|           |+-----------+

目前常用的UART芯片是8250系列的芯片。

UART寄存器

UART芯片有如下的寄存器:

Offset DLAB Access Abbr Name
0 0 Write THR Transmit Holding Register
0 0 Read RBR Receive Buffer Register
0 1 Read/Write DLL Divisor Latch LSB
1 0 Read/Write DLM Divisor Latch MSB
1 1 Read/Write IER Interrupt Enable Register
2 x Read IIR Interrupt Identification Register
2 x Write FCR FIFO Cotrol Register
3 x Read/Write LCR Line Control Register
4 x Read/Write MCR Modem Control Register
5 x Read LSR Line Status Register
6 x Read MSR Modem Status Register
7 x Read/Write SCR Scratch Pad Register

第一列是寄存器的偏移,访问它们的方式可以是IO也可以是MMIO(最早的都是IO的,现在也有MMIO的)。

从上表可以看到UART芯片总共有12个寄存器,但是只有8个端口可以使用,因此中间存在的复用。复用的方式有不同的情况,有些是读写使用同一个,另外是根据“Divisor Latch Access Bit”(该BIT后续会介绍)的值有不同的作用(0,1表示有效,x就表示不受影响)。

第二、三列的作用前面已经说明了。

第四行是缩写,在代码里面会用到宏名称中。

第五列是串口寄存器的名称。

THR/RBR:用来存放或者收取数据的寄存器,对于早期的UART芯片,就是一个个收发的,现在的芯片(比如16550)则内部有一个缓存空间,可以存放(甚至同时)16个字节(或更多字节)的发送或接收的数据(FIFO)。这两个寄存器是UART中最重要的部分。

DLL/DLM:这两个寄存器是用来设定波特率的,放到里面的数据是115200/BaudRate,下面是目前所有可能的值:

IER:这个是中断相关的寄存器,每个BIT的意义如下:

IIR:这个是读有效的寄存器,读出来的内容描述了这个UART的特性(有跟中断相关,也有其它的),下面是这个寄存器的各个BIT的说明:

FCR:FIFO是在后续的8250芯片中引入的,它是一个写有效寄存器,用来控制FIFO特性,各个BIT如下所示:

LCR:这个寄存器有两个作用,一个是设置DLAB(前面已经提到过这个BIT),另一个是设置模式(如8-1-None,5-2-Even等),下面是各个BIT的意义:

MCR:这个寄存器用来设定硬件上的控制,各个BIT的意义如下:

LSR:这个寄存器用来说明UART芯片发生的错误,各个BIT意义如下:

MSR:这个寄存器用来说明UART芯片的状态:

SCR:这个不好说,作用不明。不过可以用来测试芯片是否存在。

UART编程

要对UART编程(这里只针对x86平台),首先需要知道的就是基地址,有了基地址才可以访问上面介绍到的所有这些寄存器。

关于基地址如何设置,x86平台有自己的一套,不再本主题中,这里简单的说明一下:

1. 对于早期的UART芯片,通过IO访问,地址基本上是固定的,有以下的几个:

2. x86的PCH上会封装一些UART被包装成PCI设备,通过MMIO来访问。在PCI扫描之前,地址是固定的MMIO,之后就使用PCI扫描到的MMIO地址。

上述两者的区别,除了地址不一样,访问的尺寸也不一样,一个是1字节的,一个基本是4字节的。

初始化

BIOS下UART的初始化有不少的库或者代码会涉及到,但是都大同小异。

这里以MdeModulePkg\Library\BaseSerialPortLib16550\BaseSerialPortLib16550.inf为例说明。

以下是SerialPortInitialize()函数的代码说明:

  //// Perform platform specific initialization required to enable use of the 16550 device// at the location specified by PcdSerialUseMmio and PcdSerialRegisterBase.//Status = PlatformHookSerialPortInitialize ();if (RETURN_ERROR (Status)) {return Status;}

首先是一些OEM的操作,通常可以直接返回成功。

  //// Calculate divisor for baud generator//    Ref_Clk_Rate / Baud_Rate / 16//Divisor = PcdGet32 (PcdSerialClockRate) / (PcdGet32 (PcdSerialBaudRate) * 16);if ((PcdGet32 (PcdSerialClockRate) % (PcdGet32 (PcdSerialBaudRate) * 16)) >= PcdGet32 (PcdSerialBaudRate) * 8) {Divisor++;}

配置用于波特率设置的值,之前已经介绍过,它的值通常是(115200/波特率)。

  //// Get the base address of the serial port in either I/O or MMIO space//SerialRegisterBase = GetSerialRegisterBase ();if (SerialRegisterBase ==0) {return RETURN_DEVICE_ERROR;}

获取UART基地址。

  //// See if the serial port is already initialized//Initialized = TRUE;if ((SerialPortReadRegister (SerialRegisterBase, R_UART_LCR) & 0x3F) != (PcdGet8 (PcdSerialLineControl) & 0x3F)) {Initialized = FALSE;}SerialPortWriteRegister (SerialRegisterBase, R_UART_LCR, (UINT8)(SerialPortReadRegister (SerialRegisterBase, R_UART_LCR) | B_UART_LCR_DLAB));CurrentDivisor =  SerialPortReadRegister (SerialRegisterBase, R_UART_BAUD_HIGH) << 8;CurrentDivisor |= (UINT32) SerialPortReadRegister (SerialRegisterBase, R_UART_BAUD_LOW);SerialPortWriteRegister (SerialRegisterBase, R_UART_LCR, (UINT8)(SerialPortReadRegister (SerialRegisterBase, R_UART_LCR) & ~B_UART_LCR_DLAB));if (CurrentDivisor != Divisor) {Initialized = FALSE;}if (Initialized) {return RETURN_SUCCESS;}

判断UART是否已经初始化了,如果是就直接返回成功。这里的SerialPortReadRegister()就是普通的IO或者MMIO读操作,包括写也是。

  //// Wait for the serial port to be ready.// Verify that both the transmit FIFO and the shift register are empty.//while ((SerialPortReadRegister (SerialRegisterBase, R_UART_LSR) & (B_UART_LSR_TEMT | B_UART_LSR_TXRDY)) != (B_UART_LSR_TEMT | B_UART_LSR_TXRDY));

等UART正常可用。

  //// Configure baud rate//SerialPortWriteRegister (SerialRegisterBase, R_UART_LCR, B_UART_LCR_DLAB);SerialPortWriteRegister (SerialRegisterBase, R_UART_BAUD_HIGH, (UINT8) (Divisor >> 8));SerialPortWriteRegister (SerialRegisterBase, R_UART_BAUD_LOW, (UINT8) (Divisor & 0xff));

设置波特率相关寄存器。

  //// Clear DLAB and configure Data Bits, Parity, and Stop Bits.// Strip reserved bits from PcdSerialLineControl//SerialPortWriteRegister (SerialRegisterBase, R_UART_LCR, (UINT8)(PcdGet8 (PcdSerialLineControl) & 0x3F));

设置串口模式。

  //// Enable and reset FIFOs// Strip reserved bits from PcdSerialFifoControl//SerialPortWriteRegister (SerialRegisterBase, R_UART_FCR, 0x00);SerialPortWriteRegister (SerialRegisterBase, R_UART_FCR, (UINT8)(PcdGet8 (PcdSerialFifoControl) & (B_UART_FCR_FIFOE | B_UART_FCR_FIFO64)));

初始化FIFO。

  //// Put Modem Control Register(MCR) into its reset state of 0x00.//  SerialPortWriteRegister (SerialRegisterBase, R_UART_MCR, 0x00);

设置MCR到reset状态。

之后UART就可以使用了。

UART读操作

BaseSerialPortLib16550.inf模块中读操作函数如下所示:

/**Reads data from a serial device into a buffer.@param  Buffer           Pointer to the data buffer to store the data read from the serial device.@param  NumberOfBytes    Number of bytes to read from the serial device.@retval 0                NumberOfBytes is 0.@retval >0               The number of bytes read from the serial device.  If this value is less than NumberOfBytes, then the read operation failed.**/
UINTN
EFIAPI
SerialPortRead (OUT UINT8     *Buffer,IN  UINTN     NumberOfBytes)

参数就是需要读的字节以及字节个数。

字节数对应到一个for循环:

  for (Result = 0; NumberOfBytes-- != 0; Result++, Buffer++) {

循环里面就是每一个字节的写操作:

    //// Wait for the serial port to have some data.//while ((SerialPortReadRegister (SerialRegisterBase, R_UART_LSR) & B_UART_LSR_RXRDY) == 0) {if (PcdGetBool (PcdSerialUseHardwareFlowControl)) {//// Set RTS to let the peer send some data//SerialPortWriteRegister (SerialRegisterBase, R_UART_MCR, (UINT8)(Mcr | B_UART_MCR_RTS));}}

这里就是读LSR寄存器中的BIT0:

这里有一个PCD,PcdSerialUseHardwareFlowControl,表示的是是否设置Hardware Flow Control,关于什么是Hardware Flow Control,首先需要了解Flow Control。

由于UART是低速的设备,因此存在数据处理不过来的情况,此时需要告诉对方再延迟发送数据,这被操作被称为Flow Control。如何实现Flow Control呢?有两种方式,一种是硬件的一种是软件的。

Hardware Flow Control需要额外的硬件来实现,使用的是RTS/CTS两个PIN:

而对应的寄存器是在MCR/MSR上:

回到上述的代码,在Hardware Control Flow判断里面就是去写RTS让对方发送数据过来。

当有数据之后,会先清RTS:

    if (PcdGetBool (PcdSerialUseHardwareFlowControl)) {//// Clear RTS to prevent peer from sending data//SerialPortWriteRegister (SerialRegisterBase, R_UART_MCR, Mcr);}

然后就是读数据:

    //// Read byte from the receive buffer.//*Buffer = SerialPortReadRegister (SerialRegisterBase, R_UART_RXBUF);

以上就是读的过程。

不过对于Software Control Flow代码中没有特别的代码支持,如名字所说,它不需要硬件上的支持,实际上它将需要告诉对方的内容放到了传输的数据中,这个数据(实际是两个数据)是XOFF和XON。

为了让对方不要发数据了,接收方就发一个XOFF过去,反之则发一个XON过去,它们都是ASCII码

关于Control Flow,在串口工具中一般都会有配置:

UART写操作

BaseSerialPortLib16550.inf模块中写操作函数如下所示:

/**Write data from buffer to serial device. Writes NumberOfBytes data bytes from Buffer to the serial device.  The number of bytes actually written to the serial device is returned.If the return value is less than NumberOfBytes, then the write operation failed.If Buffer is NULL, then ASSERT(). If NumberOfBytes is zero, then return 0.@param  Buffer           Pointer to the data buffer to be written.@param  NumberOfBytes    Number of bytes to written to the serial device.@retval 0                NumberOfBytes is 0.@retval >0               The number of bytes written to the serial device.  If this value is less than NumberOfBytes, then the write operation failed.**/
UINTN
EFIAPI
SerialPortWrite (IN UINT8     *Buffer,IN UINTN     NumberOfBytes)

参数就是需要写的字节以及字节个数。

写之前首先需要获取到FIFO的值:

  //// Compute the maximum size of the Tx FIFO//FifoSize = 1;if ((PcdGet8 (PcdSerialFifoControl) & B_UART_FCR_FIFOE) != 0) {if ((PcdGet8 (PcdSerialFifoControl) & B_UART_FCR_FIFO64) == 0) {FifoSize = 16;} else {FifoSize = PcdGet32 (PcdSerialExtendedTxFifoSize);}}

之所以需要获取FIFO的值,是因为UART芯片中存在缓存,可以一次写多个值。

之后就是一个循环,来写入所有的字节:

while (NumberOfBytes != 0) {

在循环中就是处理所有的字符。

    //// Wait for the serial port to be ready, to make sure both the transmit FIFO// and shift register empty.//while ((SerialPortReadRegister (SerialRegisterBase, R_UART_LSR) & B_UART_LSR_TEMT) == 0);

首先就是等待FIFO清空,然后就是写FifoSize个字符:

    //// Fill then entire Tx FIFO//for (Index = 0; Index < FifoSize && NumberOfBytes != 0; Index++, NumberOfBytes--, Buffer++) {//// Wait for the hardware flow control signal//while (!SerialPortWritable (SerialRegisterBase));//// Write byte to the transmit buffer.//SerialPortWriteRegister (SerialRegisterBase, R_UART_TXBUF, *Buffer);}

这里需要说明的是SerialPortWritable()这个函数:

BOOLEAN
SerialPortWritable (UINTN  SerialRegisterBase)
{if (PcdGetBool (PcdSerialUseHardwareFlowControl)) {if (PcdGetBool (PcdSerialDetectCable)) {//// Wait for both DSR and CTS to be set//   DSR is set if a cable is connected.//   CTS is set if it is ok to transmit data////   DSR  CTS  Description                               Action//   ===  ===  ========================================  ========//    0    0   No cable connected.                       Wait//    0    1   No cable connected.                       Wait//    1    0   Cable connected, but not clear to send.   Wait//    1    1   Cable connected, and clear to send.       Transmit//return (BOOLEAN) ((SerialPortReadRegister (SerialRegisterBase, R_UART_MSR) & (B_UART_MSR_DSR | B_UART_MSR_CTS)) == (B_UART_MSR_DSR | B_UART_MSR_CTS));} else {//// Wait for both DSR and CTS to be set OR for DSR to be clear.  //   DSR is set if a cable is connected.//   CTS is set if it is ok to transmit data////   DSR  CTS  Description                               Action//   ===  ===  ========================================  ========//    0    0   No cable connected.                       Transmit//    0    1   No cable connected.                       Transmit//    1    0   Cable connected, but not clear to send.   Wait//    1    1   Cable connected, and clar to send.        Transmit//return (BOOLEAN) ((SerialPortReadRegister (SerialRegisterBase, R_UART_MSR) & (B_UART_MSR_DSR | B_UART_MSR_CTS)) != (B_UART_MSR_DSR));}}return TRUE;
}

基本上看注释就明白了,这里不再说明。

以上就是写的过程。

PciSioSerialDxe

前面介绍的内容是最普通的UART的初始化、读和写。

它们通常被用在DEBUG这个宏的最终实现部分。

而这里要介绍的是UART(或者说串口)在UEFI BIOS下的编程模型,它是整个UEFI BIOS输入输出协议栈中的一部分,关于这个协议栈在【UEFI实战】EFI System Table中的输入输出有一些基本的介绍,主要就是gEfiSerialIoProtocolGuid的安装。

UART的编程模型如下所示:

SERIAL_DEV  gSerialDevTemplate = {SERIAL_DEV_SIGNATURE,NULL,{SERIAL_IO_INTERFACE_REVISION,SerialReset,SerialSetAttributes,SerialSetControl,SerialGetControl,SerialWrite,SerialRead,NULL},                                       // SerialIo{SERIAL_PORT_SUPPORT_CONTROL_MASK,SERIAL_PORT_DEFAULT_TIMEOUT,0,16,0,0,0},                                       // SerialModeNULL,                                    // DevicePathNULL,                                    // ParentDevicePath{{MESSAGING_DEVICE_PATH,MSG_UART_DP,{(UINT8) (sizeof (UART_DEVICE_PATH)),(UINT8) ((sizeof (UART_DEVICE_PATH)) >> 8)}},0, 0, 0, 0, 0},                                       // UartDevicePath0,                                       // BaseAddressFALSE,                                   // MmioAccess1,                                       // RegisterStride0,                                       // ClockRate16,                                      // ReceiveFifoDepth{ 0, 0 },                                // Receive;16,                                      // TransmitFifoDepth{ 0, 0 },                                // Transmit;FALSE,                                   // SoftwareLoopbackEnable;FALSE,                                   // HardwareFlowControl;NULL,                                    // *ControllerNameTable;FALSE,                                   // ContainsControllerNode;0,                                       // Instance;NULL                                     // *PciDeviceInfo;
};

这里对于UART的初始化(SerialReset)、读(SerialRead)和写(SerialWrite)的实现都已经有了,位于SerialIo.c这个文件中。对应的结构体如下:

typedef struct {UINT32                   Signature;EFI_HANDLE               Handle;EFI_SERIAL_IO_PROTOCOL   SerialIo;EFI_SERIAL_IO_MODE       SerialMode;EFI_DEVICE_PATH_PROTOCOL *DevicePath;EFI_DEVICE_PATH_PROTOCOL *ParentDevicePath;UART_DEVICE_PATH         UartDevicePath;EFI_PHYSICAL_ADDRESS     BaseAddress;       ///< UART base addressBOOLEAN                  MmioAccess;        ///< TRUE for MMIO, FALSE for IOUINT8                    RegisterStride;    ///< UART Register StrideUINT32                   ClockRate;         ///< UART clock rateUINT16                   ReceiveFifoDepth;  ///< UART receive FIFO depth in bytes.SERIAL_DEV_FIFO          Receive;           ///< The FIFO used to store received dataUINT16                   TransmitFifoDepth; ///< UART transmit FIFO depth in bytes.SERIAL_DEV_FIFO          Transmit;          ///< The FIFO used to store to-transmit dataBOOLEAN                  SoftwareLoopbackEnable;BOOLEAN                  HardwareFlowControl;EFI_UNICODE_STRING_TABLE *ControllerNameTable;BOOLEAN                  ContainsControllerNode; ///< TRUE if the device produced contains Controller nodeUINT32                   Instance;PCI_DEVICE_INFO          *PciDeviceInfo;
} SERIAL_DEV;

比较重要的Protocol是EFI_SERIAL_IO_PROTOCOL,其中有SerialReset是初始化,没有什么好说的,剩下的SerialRead和SerialWrite会在后面介绍。

除此之外最重要的是SERIAL_DEV_FIFO:

typedef struct {UINT16  Head;                       ///< Head pointer of the FIFO. Empty when (Head == Tail).UINT16  Tail;                       ///< Tail pointer of the FIFO. Full when ((Tail + 1) % SERIAL_MAX_FIFO_SIZE == Head).UINT8   Data[SERIAL_MAX_FIFO_SIZE]; ///< Store the FIFO data.
} SERIAL_DEV_FIFO;

这里有一个对应到UART芯片的FIFO缓冲区,SerialReceiveTransmit()会操作这个缓冲区。

SerialWrite

原型如下:

/**Write the specified number of bytes to serial device.@param This               Pointer to EFI_SERIAL_IO_PROTOCOL@param  BufferSize         On input the size of Buffer, on output the amount ofdata actually written@param  Buffer             The buffer of data to write@retval EFI_SUCCESS        The data were written successfully@retval EFI_DEVICE_ERROR   The device reported an error@retval EFI_TIMEOUT        The write operation was stopped due to timeout**/
EFI_STATUS
EFIAPI
SerialWrite (IN EFI_SERIAL_IO_PROTOCOL  *This,IN OUT UINTN               *BufferSize,IN VOID                    *Buffer)

参数基本上一致,没有特别好说明的。

该函数的核心部分如下:

  for (Index = 0; Index < *BufferSize; Index++) {SerialFifoAdd (&SerialDevice->Transmit, CharBuffer[Index]);while (SerialReceiveTransmit (SerialDevice) != EFI_SUCCESS || !SerialFifoEmpty (&SerialDevice->Transmit)) {////  Unsuccessful write so check if timeout has expired, if not,//  stall for a bit, increment time elapsed, and try again//if (Elapsed >= Timeout) {*BufferSize = ActualWrite;gBS->RestoreTPL (Tpl);return EFI_TIMEOUT;}gBS->Stall (TIMEOUT_STALL_INTERVAL);Elapsed += TIMEOUT_STALL_INTERVAL;}ActualWrite++;////  Successful write so reset timeout//Elapsed = 0;}

它分为几个步骤:

1. 将字符串放到FIFO中,即SerialFifoAdd(),它的实现就是往SerialDevice->Transmit里面的一个16个字节的数组中放数据,模拟UART芯片中的缓冲区;

2. SerialReceiveTransmit()就是传递数据的函数;

3. 传递完了之后需要判断放到SerialDevice->Transmit里面的数据是否被清空了,它通过SerialFifoEmpty()来判断,必须要空了才正常,至于它为什么会空,重点就在于SerialReceiveTransmit()会去清除SerialDevice->Transmit里面的数据,使用的是SerialFifoRemove()这个函数。

SerialRead

原型如下:

/**Read the specified number of bytes from serial device.@param This               Pointer to EFI_SERIAL_IO_PROTOCOL@param BufferSize         On input the size of Buffer, on output the amount ofdata returned in buffer@param Buffer             The buffer to return the data into@retval EFI_SUCCESS        The data were read successfully@retval EFI_DEVICE_ERROR   The device reported an error@retval EFI_TIMEOUT        The read operation was stopped due to timeout**/
EFI_STATUS
EFIAPI
SerialRead (IN EFI_SERIAL_IO_PROTOCOL  *This,IN OUT UINTN               *BufferSize,OUT VOID                   *Buffer)

首先做一次收发:

  Status  = SerialReceiveTransmit (SerialDevice);if (EFI_ERROR (Status)) {*BufferSize = 0;REPORT_STATUS_CODE_WITH_DEVICE_PATH (EFI_ERROR_CODE,EFI_P_EC_INPUT_ERROR | EFI_PERIPHERAL_SERIAL_PORT,SerialDevice->DevicePath);gBS->RestoreTPL (Tpl);return EFI_DEVICE_ERROR;}

如果有数据的话,这个数据会被写入到SerialDevice->Receive中的数组中。

然后就读取每一个数据:

  for (Index = 0; Index < *BufferSize; Index++) {while (SerialFifoRemove (&SerialDevice->Receive, &(CharBuffer[Index])) != EFI_SUCCESS) {////  Unsuccessful read so check if timeout has expired, if not,//  stall for a bit, increment time elapsed, and try again//  Need this time out to get conspliter to work.//if (Elapsed >= This->Mode->Timeout) {*BufferSize = Index;gBS->RestoreTPL (Tpl);return EFI_TIMEOUT;}gBS->Stall (TIMEOUT_STALL_INTERVAL);Elapsed += TIMEOUT_STALL_INTERVAL;Status = SerialReceiveTransmit (SerialDevice);if (Status == EFI_DEVICE_ERROR) {*BufferSize = Index;gBS->RestoreTPL (Tpl);return EFI_DEVICE_ERROR;}}////  Successful read so reset timeout//Elapsed = 0;}

读的动作实际在SerialFifoRemove()中完成,因为SerialReceiveTransmit()的时候已经将数据放到SerialDevice->Receive中的数组。

最后又做了一次收发:

SerialReceiveTransmit (SerialDevice);

该函数前后做了两次SerialReceiveTransmit(),就是为了接受数据。

SerialReceiveTransmit

无论是读还是写,都使用到了SerialReceiveTransmit(),这里重点就放到了SerialReceiveTransmit()这个函数,这个函数分为两个部分,第一部分如下:

  if (SerialDevice->SoftwareLoopbackEnable) {do {ReceiveFifoFull = SerialFifoFull (&SerialDevice->Receive);if (!SerialFifoEmpty (&SerialDevice->Transmit)) {SerialFifoRemove (&SerialDevice->Transmit, &Data);if (ReceiveFifoFull) {return EFI_OUT_OF_RESOURCES;}SerialFifoAdd (&SerialDevice->Receive, Data);}} while (!SerialFifoEmpty (&SerialDevice->Transmit));}

通常这个正常情况下是不会使用的,因为就是一个软件上的测试用的。

第二部分才是实际使用的,下面具体说明代码:

    ReceiveFifoFull = SerialFifoFull (&SerialDevice->Receive);//// For full handshake flow control, tell the peer to send data// if receive buffer is available.//if (SerialDevice->HardwareFlowControl &&!FeaturePcdGet(PcdSerialUseHalfHandshake)&&!ReceiveFifoFull) {Mcr.Data     = READ_MCR (SerialDevice);Mcr.Bits.Rts = 1;WRITE_MCR (SerialDevice, Mcr.Data);}

1. 首先是判断SerialDevice->Receive中的缓冲区是否满了,如果没有满就通知对方发送数据(前提是使能Hardware Control Flow)。

      Lsr.Data = READ_LSR (SerialDevice);

2. 读取LSR的值,它表示的是UART芯片的状态(多是错误状态),前面已经介绍过LSR,因为它的很多BIT都会用到,所以这里再列举一下:

      //// Flush incomming data to prevent a an overrun during a long write//if ((Lsr.Bits.Dr == 1) && !ReceiveFifoFull) {

3. 判断是有可读取的数据存在,以及SerialDevice->Receive的缓冲区是否满,如果满了那就报错了:

        ReceiveFifoFull = SerialFifoFull (&SerialDevice->Receive);if (!ReceiveFifoFull) {// Other operations.} else {REPORT_STATUS_CODE_WITH_DEVICE_PATH (EFI_PROGRESS_CODE,EFI_P_SERIAL_PORT_PC_CLEAR_BUFFER | EFI_PERIPHERAL_SERIAL_PORT,SerialDevice->DevicePath);}

4. 如果没有满就执行上面的(Other operations),就判断LSR的其它几个BIT:

          if (Lsr.Bits.FIFOe == 1 || Lsr.Bits.Oe == 1 || Lsr.Bits.Pe == 1 || Lsr.Bits.Fe == 1 || Lsr.Bits.Bi == 1) {REPORT_STATUS_CODE_WITH_DEVICE_PATH (EFI_ERROR_CODE,EFI_P_EC_INPUT_ERROR | EFI_PERIPHERAL_SERIAL_PORT,SerialDevice->DevicePath);if (Lsr.Bits.FIFOe == 1 || Lsr.Bits.Pe == 1|| Lsr.Bits.Fe == 1 || Lsr.Bits.Bi == 1) {Data = READ_RBR (SerialDevice);continue;}}

然后还是读取了数据,不过这个数据是有问题的,所以没有放到SerialDevice->Receive的缓冲区中,之后就返回到第2步。

5. 如果往下走,就表示没有出错,那么就读取数据,然后放到SerialDevice->Receive的缓冲区:

          Data = READ_RBR (SerialDevice);SerialFifoAdd (&SerialDevice->Receive, Data);

6. 之后通知对方不要发数据了:

          //// For full handshake flow control, if receive buffer full// tell the peer to stop sending data.//if (SerialDevice->HardwareFlowControl &&!FeaturePcdGet(PcdSerialUseHalfHandshake)   &&SerialFifoFull (&SerialDevice->Receive)) {Mcr.Data     = READ_MCR (SerialDevice);Mcr.Bits.Rts = 0;WRITE_MCR (SerialDevice, Mcr.Data);}continue;

然后再回到第2步。

7. 以上的内容都是跟读有关的,直到SerialDevice->Receive的缓冲区满了为止,才会往下执行后面的代码。

      //// Do the write//if (Lsr.Bits.Thre == 1 && !SerialFifoEmpty (&SerialDevice->Transmit)) {// Write data to UART.}

8. 上面的代码中Thre==1表示UART芯片可以接收数据了,而同时如果SerialDevice->Transmit缓冲区不是空的,那就写数据到UART中,不过这里也分两种情况,对于非Hardware Control Flow,直接写寄存器:

          SerialFifoRemove (&SerialDevice->Transmit, &Data);WRITE_THR (SerialDevice, Data);

否则有更多的操作:

        if (SerialDevice->HardwareFlowControl) {//// For half handshake flow control assert RTS before sending.//if (FeaturePcdGet(PcdSerialUseHalfHandshake)) {Mcr.Data     = READ_MCR (SerialDevice);Mcr.Bits.Rts= 0;WRITE_MCR (SerialDevice, Mcr.Data);}//// Wait for CTS//TimeOut   = 0;Msr.Data  = READ_MSR (SerialDevice);while ((Msr.Bits.Dcd == 1) && ((Msr.Bits.Cts == 0) ^ FeaturePcdGet(PcdSerialUseHalfHandshake))) {gBS->Stall (TIMEOUT_STALL_INTERVAL);TimeOut++;if (TimeOut > 5) {break;}Msr.Data = READ_MSR (SerialDevice);}if ((Msr.Bits.Dcd == 0) || ((Msr.Bits.Cts == 1) ^ FeaturePcdGet(PcdSerialUseHalfHandshake))) {SerialFifoRemove (&SerialDevice->Transmit, &Data);WRITE_THR (SerialDevice, Data);}//// For half handshake flow control, tell DCE we are done.//if (FeaturePcdGet(PcdSerialUseHalfHandshake)) {Mcr.Data = READ_MCR (SerialDevice);Mcr.Bits.Rts = 1;WRITE_MCR (SerialDevice, Mcr.Data);}}

9. 最后还是一个判断是否要发送数据:

while (Lsr.Bits.Thre == 1 && !SerialFifoEmpty (&SerialDevice->Transmit));

如果要发送数据,再回到第2步。

以上是SerialReceiveTransmit()的所有内容。

最简单的情况下,我们不使用Hardware Control Flow,则操作简化为如下的形式:

  ReceiveFifoFull = SerialFifoFull (&SerialDevice->Receive);do {Lsr.Data = READ_LSR (SerialDevice);if ((Lsr.Bits.Dr == 1) && !ReceiveFifoFull) {ReceiveFifoFull = SerialFifoFull (&SerialDevice->Receive);if (!ReceiveFifoFull) {Data = READ_RBR (SerialDevice);SerialFifoAdd (&SerialDevice->Receive, Data);continue;} else {REPORT_STATUS_CODE_WITH_DEVICE_PATH (EFI_PROGRESS_CODE,EFI_P_SERIAL_PORT_PC_CLEAR_BUFFER | EFI_PERIPHERAL_SERIAL_PORT,SerialDevice->DevicePath);}}if (Lsr.Bits.Thre == 1 && !SerialFifoEmpty (&SerialDevice->Transmit)) {SerialFifoRemove (&SerialDevice->Transmit, &Data);WRITE_THR (SerialDevice, Data);}} while (Lsr.Bits.Thre == 1 && !SerialFifoEmpty (&SerialDevice->Transmit));

以上是UART相关的介绍。

【UEFI实战】UART的初始化相关推荐

  1. 【UEFI实战】EDK的编译流程说明

    前言 使用EDK进行UEFI开发,开始的时候很容易遇到的问题就是编译不过,并非代码的问题,而是编译环境存在异常. 本文主要介绍EDK是如何进行编译的,使用的平台是Windows.这里还想说一点,事实上 ...

  2. 【UEFI实战】LinuxBoot

    综述 LinuxBoot是一个开源的固件,用来替代UEFI BIOS加载Linux的系统. 官网是LinuxBoot. 对应的代码库位于LinuxBoot · GitHub. 另外,本文是在[UEFI ...

  3. 【UEFI实战】OS下如何查看系统相关信息

    说明 本文主要介绍OS下如何来查看系统信息,这些系统信息大多是通过BIOS上传的.这里的OS主要分为Linux和Windows两个部分来说明,前者使用的发行版系统是Ubuntu18.04,后者使用的是 ...

  4. 【UEFI实战】FSP简介

    说明 在[UEFI实战]SlimBootloader简介中有说到,编译Slim Bootloader的时候需要使用到FSP,本文就是用来介绍FSP是什么,它的作用,以及如何编译等内容. 什么是FSP ...

  5. 汇编级UART串口初始化与打印

    用于新PCB板调试开发,在系统最开始(内存初始化之前),尽快打印字符,验证CPU是否正常启动. 以freescale QorIQ 处理器兼容的UART为例,符合16550串口标准: /*UART DE ...

  6. 【UEFI实战】SlimBootloader中调用FSP

    综述 FSP的全称是Firmware Support Package.FSP有以下的特性: FSP提供了Intel重要组件(包括处理器.内存控制器.芯片组等)的初始化: FSP被编译成独立的二进制,并 ...

  7. 【UEFI实战】UEFI用户交互界面基础说明

    前言 本文以vUDK2017: https://github.com/tianocore/edk2.git Tag vUDK2017.中的代码为例说明UEFI用户交互界面的实现. 这里UEFI用户交互 ...

  8. 【UEFI实战】HII之FrontPage

    写在前面 UEFI有自己的用户交互界面,它的实现基础被称为HII(Human Interface Infrastructure),本文是一系列介绍HII实现的第一篇.这里从开源EDK代码中的界面(称为 ...

  9. 【UEFI实战】Secure Boot

    说明 Secure Boot,顾名思义就是用来保证启动安全的一套措施. Secure Boot是一个比较普通的说法,使用的场景也很多,所以这里要特别说明一下,这里指的是UEFI BIOS下的,用来启动 ...

最新文章

  1. MVCC在MySQL的InnoDB中的实现
  2. Pipeline模式(netty源码死磕6)
  3. 特征工程(part2)--数值型数据
  4. 小票上为啥指甲能划出印_指甲上出现竖纹,除遗传问题,或是身体在向你拉警报了,别忽视...
  5. nginx反向代理和shiro权限校验产生的404问题
  6. python做oa系统_浅谈python进行webapp,oa系统开发 (更新中) | 学步园
  7. Adobe发布基于HTML5技术的网络开发工具以解决跨平台问题
  8. linux挂载目录已存在可以么,Linux如何更改硬盘已挂载目录
  9. BPTT算法推导以及LSTM是如何解决梯度消失的
  10. 爬虫实例5 爬取58房源信息(xpath)
  11. pyqt5优秀项目python_Python优秀开源项目Rich源码解析
  12. java 随机数的判断
  13. Ubuntu24.04下向日葵,CUDA,cuDNN的详细安装,亲测有效
  14. linux命令行打开写字板,在Linux操作系统中使用手写板
  15. 安卓非微信内置浏览器中的网页调起微信支付的方案研究
  16. 最低成本的ARM调试解决方案——有关于Wiggler、H-Jtag、OpenOCD、GDB
  17. 从 DP 到 DDP 到 apex
  18. 路由器wan和lan口
  19. mac连接蓝牙耳机自动打开iTunes问题解决
  20. 使用服务器训练模型详解

热门文章

  1. 06-ECharts
  2. cocoscreator 中 spine局部换皮
  3. java-net-php-python-jspm高校食堂点餐系统演示录像2019计算机毕业设计程序
  4. word在写论文的一些技巧
  5. 哈佛大学计算机科学专业怎样,美国哈佛大学cs研究生专业排名如何?要怎样申请?...
  6. Hadoop整体框架
  7. SM1,SM2,SM3,SM4 介绍
  8. 2188 -找树根 ---树
  9. 热丰网配资带你了解散户炒股有哪些误区?
  10. 【小白学前端】JS案例:表单全选取消全选