Make your own OS (Part 3)
In this article we are going to do more about output management and mainly focus how to display text and how to write data to a serial port.
previous article how to build OS part 2
Interacting with the Hardware
There are two methods to interact with the hardware. First method is memory-mapped I/O and second method is I/O ports . Memory-mapped I/O using a specific memory address to write data. frame-buffer is a a hardware device that is capable of displaying a buffer of memory on the screen is one example. It has 80 columns and 25 rows, labeling starting in 0. (rows labeled 0–24)
frame-buffer use specific memory address to write data and hardware will be updated with the new data.0x000B8000
is the starting address. memory address divided into 16-bit cells, which include character, the foreground color, and the background color according to below chart.
Bit: | 15 14 13 12 11 10 9 8 | 7 6 5 4 | 3 2 1 0 |
Content: | ASCII | FG | BG |
There are 16 colors we can use for both background and foreground text.
So, if we want to write letter A to 0,0 place , we can use below assembly code. (letter “A” = 0x41 , red foreground (4) and background (8) ). By adding this code to loader.s file, we can see letter A on booting screen.
mov [0x000B8000], 0x4148
But in previous article, we learn how to use c language to build OS. So, let’s use C language to do above task.
first we are going to modify kmain.c
file and add below function to it.
above function display text using framebuffer. So, then we need to call this function. let’s use below code.
#define FB_GREEN 2
#define FB_DARK_GREY 8
fb_write_cell(0, 'A', FB_GREEN, FB_DARK_GREY);
we can use color code (number) directly, instead of using defined variable names. then we can get below result wen we boot our OS.
fb_write_cell(0, 'A', 2, 8);
Now, kmain.c
file look like below.
now we can show letter, let’s try to show a text.
Moving the Cursor
For showing a text we need to move cursor. Framebuffer use 2 I/O ports to move cursor. let’s use below code to make io.s
and io.h
then we need c function, lets save below code as framebuffer.c.
Then , let’s edit makefile file to run above codes. we needs modify first line of makefile like this.
OBJECTS = loader.o kmain.o io.o
Now we can run our codes using make run command and we can see blinking underscore in OS booting screen.
let’s use below code in kmain.c file.
so, we can have below result after success cording
so, using above method we can display any text we want to display.
However, our kmain.c file little bit complicated. So let’s make it simple by moving I/O related functions to header file. Also functions in framebuffer.c add to that header file. then our files look like below.
The Serial Ports
The serial port is a interface which communicating between hardware devices.If a computer supports a serial port, it usually supports multiple serial ports; however, we will only use one of the ports. This is due to the fact that we will only use the serial ports for logging. Furthermore, the serial ports will only be used for output, not input. I/O ports completely control the serial ports. Using qemu we can save outputs.
Configuring the Serial Port
first we needs to send configuring data to serial port. To connect two hardware devices, there are some conditions to satisfy,
- The speed used for sending data (bit or baud rate)
- If any error checking should be used for the data (parity bit, stop bits)
- The number of bits that represent a unit of data (data bits)
Configuring the Line
Configuring the line means determining how data is sent over the line. The serial port has an I/O port for configuration, the line command port.
First, the data transmission speed will be determined. The internal clock of the serial port is set to 115200 Hz.
Setting the speed involves sending a divisor to the serial port, such as 2, which results in a speed of 115200 / 2 = 57600 Hz.
We can only send 8 bits at a time because the divisor is a 16 bit number. As a result, we must send an instruction to the serial port instructing it to expect the highest 8 bits first, followed by the lowest 8 bits. Sending 0x80 to the line command port accomplishes this.
Furthermore, the data transmission method must be configured. This is also accomplished by sending a byte to the command line port. The 8 bits are organized as follows:
Bit: | 7 | 6 | 5 4 3 | 2 | 1 0 |
Content: | d | b | prty | s | dl |
A description for each name can be found in the table below
d Enables (d = 1) or disables (d = 0) DLAB
b If break control is enabled (b = 1) or disabled (b = 0)
prty The number of parity bits to use
s The number of stop bits to use (s = 0 equals 1, s = 1 equals 1.5 or 2)
dl Describes the length of the data
Configuring the Buffers
When data is sent or received via the serial port, it is placed in buffers.
If you send data to the serial port faster than it can send it over the wire, it will be buffered.However, if you send too much data too quickly, the buffer will fill up and data will be lost.In other words, the buffers are FIFO queues.
The FIFO queue configuration byte looks like this:
Bit: | 7 6 | 5 | 4 | 3 | 2 | 1 | 0 |
Content: | lvl | bs | r | dma | clt | clr | e |
A description for each name can be found in the table below
lvl How many bytes should be stored in the FIFO buffers
bs If the buffers should be 16 or 64 bytes large
r Reserved for future use
dma How the serial port data should be accessed
clt Clear the transmission FIFO buffer
clr Clear the receiver FIFO buffer
e If the FIFO buffer should be enabled or not
We use the value 0xC7 = 11000111 that:
- Enables FIFO
- Clear both receiver and transmission FIFO queues
- Use 14 bytes as size of queue
Configuring the Modem
The Ready To Transmit (RTS) and Data Terminal Ready (DTR) pins of the modem control register are used for very simple hardware flow control. RTS and DTR should be set to 1 when configuring the serial port, indicating that we are ready to send data.The following figure depicts the modem configuration byte:
Bit: | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
Content: | r | r | af | lb | ao2 | ao1 | rts | dtr |
A description for each name can be found in the table below
r Reserved
af Autoflow control enabled
lb Loopback mode (used for debugging serial ports)
ao2 Auxiliary output 2, used for receiving interrupts
ao1 Auxiliary output 1
rts Ready To Transmit
dtr Data Terminal Ready
So, let’s look codes.. above mentions all task done by functions and we add that all functions to serial_port.h file.
Writing Data to the Serial Port
Data writing done by I/O port, but before it make sure the transmit FIFO queue is empty (all previous writes must have finished). The transmit FIFO queue is empty if bit 5 of the line status I/O port is equal to one. for that task we used below function in serial_port.h file
int serial_is_transmit_fifo_empty(unsigned int com) { /* 0x20 = 0010 0000 */ return inb(SERIAL_LINE_STATUS_PORT(com)) & 0x20; }
Configuring makefile
To get output we need to add more instructions to qemu. Scince we use makefile to enter instructions, we mast modify makefile. so let’s modify makefile like below.
output file will save as os.out .
So, Lets write data.. for that let’s edit kmain.c file.
Finally, we can see result of codes.
You can get full codes by visiting github using below link.
Thank you..