Operating Systems, Dealing with Outputs

Hello Readers, Welcome back to another article in the Developing an Operating Systems series. Today I’m going to talk about how to configure your OS to display output on the screen. Up to this point, we only did changes to CPU registers and none of them was displayed on the screen, so we had to check the log files to see if they have worked. So, after this, we can write out results directly into the screen.

The framebuffer

A framebuffer is a small part of the RAM which contains the bitmap that goes into the video display. It is a memory buffer containing information representing all the pixels in a complete video frame. Video cards in computers contain circuitry that can convert this data to a video signal that can be displayed on a computer monitor. The information in the buffer typically consists of colour values for every pixel to be shown on the display. Colour values are commonly stored in 1-bit binary (monochrome), 4-bit palettized, 8-bit palettized, 16-bit high colour and 24-bit true-colour formats. An additional alpha channel is sometimes used to retain information about pixel transparency. The total amount of memory required for the framebuffer depends on the resolution of the output signal, and the colour depth or palette size.

Memory-mapped I/O & Port-mapped I/O

Memory-mapped I/O (MMIO) and port-mapped I/O (PMIO) are two methods of performing input/output (I/O) between the central processing unit (CPU) and peripheral devices in a computer.

Memory-mapped I/O uses the same address space to address both memory and I/O devices. The memory and registers of the I/O devices are mapped to address values. So, a memory address may refer to either a part of physical RAM or instead to the memory of the I/O device. Therefore, the CPU instructions used to access the physical RAM can also be used for accessing devices. Each I/O device monitors the CPU’s address bus and responds to any CPU access of an address assigned to that device, connecting the data bus to the desired device’s hardware register. To accommodate the I/O devices, areas of the addresses used by the CPU must be reserved for I/O and must not be available for normal physical memory.

Port-mapped I/O often uses a special class of CPU instructions designed specifically for performing I/O, such as the in and out instructions found on microprocessors based on the x86 and x86–64 architectures. Different forms of these two instructions can copy one, two or four bytes (outb, outw and outl, respectively) between the EAX register or one of that register’s subdivisions on the CPU and a specified I/O port which is assigned to an I/O device. I/O devices have separate address space from general memory, either accomplished by an extra “I/O” pin on the CPU’s physical interface, or an entire bus dedicated to I/O. Because the address space for I/O is isolated from that for main memory, this is sometimes referred to as isolated I/O.

Serial port

A serial port is a serial communication interface through which information transfers in or out sequentially one bit at a time. This is in contrast to a parallel port, which communicates multiple bits simultaneously in parallel. Throughout most of the history of personal computers, data has been transferred through serial ports to devices such as modems, terminals, various peripherals, and directly between computers. While interfaces such as Ethernet, FireWire, and USB also send data as a serial stream, the term serial port usually denotes hardware compliant with RS-232 or a related standard, such as RS-485 or RS-422. Modern consumer PCs have largely replaced serial ports with higher-speed standards, primarily USB. However it is available on almost all motherboards, but not exposed to the user in the form of a DE-9 connector.

My development experience

Writing Text

So the first part of getting an output from the OS is using the frame buffer to write a single character on the screen. The framebuffer for the memory Memory-mapped I/O is divided into 16bit cells. The first 8 bits are used to represent the character in ASCII, the next 4 bits represent the background colour and the next 4 represent the foreground colour.

Bit representation in framebuffer

Since colours are represented by 4bits there are 2⁴=16 possible colours that can be used with outputs. These are represented in the following table.

Colours used in framebuffer

The starting address for the framebuffer is 0x000B8000 so displaying the letter A is just as easy as changing its value to 0x41 which is the hexadecimal representation of the letter “A” followed by a foreground and background colour. So that value will look something like 0x4130. This can be easily done using assembly but since it will require writing a new command for each letter we will use C to do this.
This is done by using a char pointer to point to the address 0x000B8000 then using a function to change the value of the bits following it.

C code to write values into the frame buffer
Using extern and call functions in loader.s

Then I used the extern and call commands in the loader.s file to call the kmain function using assembly. Then compile it using the makefile and finally, I had the letter “A” displayed on the screen with a Cyan background.

The letter “A” displayed in the screen with Black foreground and Cyan Background

Moving the Cursor

Moving the cursor in the framebuffer is done using two different I/O ports and the position of the cursor is determined using a 16bit integer. Since the out assembly code argument is only 8bits the position of the cursor has to be sent in two turns. The frame buffer has two I/O ports, one to accept the data and another to describe the data being received.

The out assembly code can’t be directly executed in C therefore it has to be wrapped in an assembly function and called in C using cdecl calling.

This function will be stored in a separate file io.s.

io.s file

A header file with the instructions to use the outb function is also created so it can be easily accessed from a c file.

io header file

Then finally the C function for moving the cursor can be created and called in the kmain.c

C function for moving the cursor
Calling the move_cursor function in the kmain

After this is done before compiling the code another object called io.o has to be added in the object parameters of the makefile. Then finally after running the make file you should get a blinking cursor at the row 0 column 0 position. (I have removed the previous fb_write function so the cursor can be visible)

Cursor at the (0,0) postiton of the console

Configuring the serial ports

This is the final and maybe the hardest part of this chapter. Before the serial ports can be used they have to be configured. These configuration data includes The speed used for sending data (bit or baud rate), If any error checking should be used for the data (parity bit, stop bits) and The number of bits that represent a unit of data (data bits).

The serial port has an IO port called the line command port which is used to configure it.

First the speed for sending data will be set. The serial port has an internal clock that runs at 115200 Hz. Setting the speed means sending a divisor to the serial port, for example sending 2 results in a speed of 115200 / 2 = 57600 Hz.

The divisor is a 16 bit number but since we can only send 8 bits at a time. We must therefore send an instruction telling the serial port to first expect the highest 8 bits, then the lowest 8 bits. This is done by sending 0x80 to the line command port.

Then the way that data should be sent must be configured. This is also done via the line command port by sending a byte. The layout of the byte will describe the way data is sent.

The layout of the byte

Here I’ll use the mostly standard value used for the way that data should be sent which is 0x03. meaning a length of 8 bits, no parity bit, one stop bit and break control disabled.

Configuring Serial line speed and the way that data should be sent

When data is transmitted via the serial port it is placed in buffers, both when receiving and sending data. This way, 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 fast the buffer will be full and data will be lost. In other words, the buffers are FIFO queues.

The modem control register is used for very simple hardware flow control via the Ready To Transmit (RTS) and Data Terminal Ready (DTR) pins. When configuring the serial port we want RTS and DTR to be 1, which means that we are ready to send data.

Configuring the buffer and the modem

Writing data to the serial port is done using the data I/O port. But before writing the data the FIFO buffer has to be empty. This can be checked by the 5th bit of the line status I/O port is 1. Reading the data from the status I/O port can be done by using the in assembly function but just like the out function, it can’t directly execute with C. So it also has to be wrapped in an assembly function and called in C using cdecl calling just like the out function.

The assembly function to get input values
C function to call assembly inb command

Then we can use a C function to check if the transmit FIFO is empty.

C function to check the transmit FIFO

After the function to write to serial is implemented it is better to write a function to easily access the serial write command. Then all of the c functions related to the serial ports is stored on a separate serial.h.

The code to write to serial port and the driver

The same is done for the previously written code for the frame buffer and they are also stored in a separate framebuffer.h file.(Also note that since the framebuffer is 16bits and the original fb_write_cellfunction only advanced the buffer by 8 bits i had to change it’s value from i to 2i)

And finally the argument com1: enabled=1, mode=file, dev=com1.out is added to the end of the bochsrc.txt file to save the output from serial port.

The driver for the frame buffer

This is where the tutorial on the littleosbook ends but I have added an extra code to use these output functions by writing the string “Welcome to MemoOS” into the framebuffer and serial ports.

Code to print the string to framebuffer and the serial port
The output displayed on the screen
The output in the com1.out file

References

Helin E, Renberg A. (2015). The little book about OS development: https://littleosbook.github.io/

Thank you for reading and I will be back next week with the next article.

--

--

--

Software Engineering Undergraduate of University of Kelaniya, Sri Lanka.

Love podcasts or audiobooks? Learn on the go with our new app.

Recommended from Medium

AWS Global Infrastructure 101: Learning About Regions and Availability Zones

How to use the VLOOKUP function to get values from the left of the key column.

Playing with Data Structures in Swift

Career Takeoff — Interview with Cloud Engineer Marc-Enzo Bonnafon

Custom Annotation To Handle Authorisation In Spring Boot

5 Linux Utilities That Will Make You Smile

Run C or Python Programs Using Discord Bot

Dynamic Programming for dummies

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Mahela Dissanayake

Mahela Dissanayake

Software Engineering Undergraduate of University of Kelaniya, Sri Lanka.

More from Medium

A Thousand Sparks! — Quotidian — 025

Leaders Measure the Right Things

What Are Data Bricks And How Do You Use Them?

Why deployment speed is vital in maximising ROI