OS in Rust: Incorporate VGA buffer: Part-7

Knoldus Blog Audio
Reading Time: 4 minutes

This series pertains to create a basic Operating System using Rust Programming Language. This series aims to learn and understand the basics of the Operating System.
Through this, you will get some ideas about the internal components of the Operating System and how they interact with each other.

This article pertains to adding few more functionalities to VGA text mode in our kernel.

If you are new to this series, then it’d be great if you have a look at the previous posts so that you can get an idea of where we started and how much we have covered till now.

In the previous article, we have incorporated the VGA text mode to print something on the screen, but the scope of that article was restricted to print a single byte at a time only.
So here, we’ll try to incorporate the features which will allow us to print a whole string, help us to change the line if needed, and also help us to clear data from the screen.

In this article we’ll incorporate following functionality:

  • Print whole string
  • Change row
  • Clear row

Print whole string

As we already declared a function write_byte() in our previous article which allows us to write a byte in the screen, so for this function we’ll leverage the functionality of write_byte().
The easiest way to write the whole string is by converting it into bytes and print them one by one by simply invoking the write_byte()function. Let’s try to implement it.

pub fn write_string(&mut self, string_data: &str) {
    for byte in string_data.bytes() {
        match byte {
            0x20..=0x7e | b'\n' => self.write_byte(byte),
            _ => self.write_byte(0xfe),
        }
    }
}

As we can see above, we’ve converted string into bytes and with the help of match expression, we differentiate the ASCII bytes because Rust strings are UTF-8 by default, as provided string might contain bytes that are not supported by the VGA test buffer.
And for the unsupported bytes, we just provided 0xfe hex code.

Change row

Till now our kernel is not capable to handle the scenario where the current row reaches its limit as in the VGA text mode we can have a maximum of 80 columns. As of now, in this scenario, our kernel won’t be able to print the rest of the content. So for this, we’ll provide a change_line() function which will print the rest of the content in the new line.
If we remember behavior of VGA text mode, it prints the data from the beginning of the last line, so for this function what we’ll do is, we’ll move every character one line up and will start from the beginning of the last line again.

fn change_line(&mut self) {
    for row in 1..BUFFER_HEIGHT{
        for col in 0..BUFFER_WIDTH {
            let character = self.buffer.chars[row][col];
            self.buffer.chars[row-1][col] = character;
        }
    }
    self.clear_row(BUFFER_HEIGHT - 1);
    self.column_position = 0;
}

In this function, we’ve iterated over all the screen characters(first loop iterates over lines and another loop iterates over each character of the line) and just move the character one line up and the rest of the code is self-explanatory except for two things, the first is we’ve iterated our first loop from 1 instead of 0 because it’s the row that is shifted off the screen and the second thing is we’ve used clear_row() function which we’ll implement in our next feature. And the purpose of using clear_row() is, we need to clear the last row.

Clear row

fn clear_row(&mut self, row: usize) {
    let blank = ScreenChar {
        ascii_character: b' ',
        color_code: self.color_code,
    };
    for col in 0..BUFFER_WIDTH {
        self.buffer.chars[row][col] = blank;
    }
}

This is how we cleared the last row, by overwriting all of the characters with a space character.

As we have implemented the change line functionality for our kernel, so we need to update our write_byte() as well to make use of these additional features.

pub fn write_byte(&mut self, byte: u8) {
    match byte {
        b'\n' => self.change_line(),
        byte => {
            if self.column_position >= BUFFER_WIDTH {
                self.change_line();
            }

            let row = BUFFER_HEIGHT - 1;
            let col = self.column_position;

            let color_code = self.color_code;
            self.buffer.chars[row][col] = ScreenChar {
                ascii_character: byte,
                color_code,
            };
            self.column_position += 1;
        }
    }
}

In this, we made few changes like provided a match expression to figure out the new line character and provided a check for validating the column position so that we can invoke the change_line() in both the scenarios.

That’s all for this article, thanks for reading…

In the next article, we’ll try to incorporate few more features to expand the scope of our kernel.

Stay tuned!!!

References:

Blogs of this series:

If you want to read more content like this?  Subscribe Rust Times Newsletter and receive insights and latest updates, bi-weekly, straight into your inbox. Subscribe Rust Times Newsletter: https://bit.ly/2Vdlld7 .


Knoldus-blog-footer-image

Written by 

Pawan Singh Bisht is a Software Consultant at Knoldus Software LLP, having a strong experience of more than a year in the technology field. He has been well versed in the core implementation of Rust and Java. He loves to contribute towards the community which he attained from the community.