This page has been machine-translated from the original page.
I’m reading xv6 OS inspired by Your First OS Code Reading: Learning Kernel Internals with UNIX V6.
Because UNIX V6 itself does not run on x86 CPUs, I decided to read the source code of kash1064/xv6-public: xv6 OS, a fork of the xv6 OS repository that adapts UNIXv6 to run on the x86 architecture.
Last time, we looked at the behavior of the consoleinit function executed from main.
This time, I’ll follow the behavior of the uartinit function.
Table of Contents
The uartinit function
The uartinit function is defined in uart.c, and it performs initialization related to the serial port.
#define COM1 0x3f8
static int uart; // is there a uart?
void uartinit(void)
{
char *p;
// Turn off the FIFO
outb(COM1+2, 0);
// 9600 baud, 8 data bits, 1 stop bit, parity off.
outb(COM1+3, 0x80); // Unlock divisor
outb(COM1+0, 115200/9600);
outb(COM1+1, 0);
outb(COM1+3, 0x03); // Lock divisor, 8 data bits.
outb(COM1+4, 0);
outb(COM1+1, 0x01); // Enable receive interrupts.
// If status is 0xFF, no serial port.
if(inb(COM1+5) == 0xFF) return;
uart = 1;
// Acknowledge pre-existing interrupt conditions;
// enable interrupts.
inb(COM1+2);
inb(COM1+0);
ioapicenable(IRQ_COM1, 0);
// Announce that we're here.
for(p="xv6...\n"; *p; p++) uartputc(*p);
}What is UART
To begin with, UART stands for **Universal Asynchronous Receiver/Transmitter**.
UART is a protocol for exchanging serial data between two devices.
Reference: Overview of UART | Rohde & Schwarz
Reference: Universal asynchronous receiver-transmitter - Wikipedia
It does not seem to be used very much these days, but UART appears to have been the protocol used for communication over serial ports.
The uartinit function seems to be setting up what is usually called a COM port.
Using the serial port for communication
If an OS uses a serial port for communication, it first needs to initialize the serial port.
Reference: Serial Ports - OSDev Wiki
Let’s look through the source code in order.
#define COM1 0x3f8
// Turn off the FIFO
outb(COM1+2, 0);COM1 is defined as 0x3f8.
This is the fixed I/O port address of the COM1 port.
Each COM port register can be accessed as an offset from the I/O port address.
COM1+2 points to the Interrupt Identification and FIFO control registers.
Here, setting 0 disables FIFO operation inside the UART.
When FIFO is disabled, received data is passed to the Receiver buffer register.
Next, let’s look at the following code.
// 9600 baud, 8 data bits, 1 stop bit, parity off.
outb(COM1+3, 0x80); // Unlock divisor
outb(COM1+0, 115200/9600);
outb(COM1+1, 0);
outb(COM1+3, 0x03); // Lock divisor, 8 data bits.
outb(COM1+4, 0);
outb(COM1+1, 0x01); // Enable receive interrupts.COM1+3 corresponds to the Line control register.
This register changes the communication parameters according to the bits that are set.
Reference image: Serial UART, an in depth tutorial - Lammert Bies
outb(COM1+3, 0x80); sets the eighth bit and enables DLAB.
As a result, the following writes to COM1+0 and COM1+1 are redirected to the Divisor latch registers (R/W).
Writing 115200/9600 and 0 there sets the UART speed to 9,600 bps.
The following outb(COM1+3, 0x03); clears DLAB in the LCR while also setting 8 data bits.
After that, it writes 0 to the Modem control register and enables interrupts.
After going through the full initialization process, it checks the Line status register to confirm that the serial port is available.
// If status is 0xFF, no serial port.
if(inb(COM1+5) == 0xFF) return;
uart = 1;Finally, it checks the current interrupt state by reading the RBR and IIR information, enables interrupts, and finishes.
// Acknowledge pre-existing interrupt conditions;
// enable interrupts.
inb(COM1+2);
inb(COM1+0);
ioapicenable(IRQ_COM1, 0);Summary
This time, I traced through the serial-port initialization process.
Next time, I’ll look at the pinit function.