Screen C
Screen C
h"
2 #include "libc/pio.h"
3 #include "libc/memory.h"
4
5 /*
6 When most computers boot, despite that they may infact have more advanced graphics
hardware,
7 they begin in a simple Video Graphics Array (VGA) colour text mode with dimmensions
80x25 characters.
8
9 In text mode, the programmer does not need to render
10 individual pixels to describe specific characters,
11 since a simple font is already defined
12 in the internal memory of the VGA display device.
13
14 Instead, each character cell of the screen
15 is represented by two bytes in memory:
16 the first byte is the ASCII code of the character to be displayed,
17 and the second byte encodes the characters attributes
18 such as the foreground and background colour and if the character should be
blinking.
19
20 So, if we’d like to display a character on the screen,
21 then we need to set its ASCII code and attributes
22 at the correct memory address for the current VGA mode,
23 which is usually at address 0xb8000
24
25 We use friendlier form of hardware I/O, known as memory-mapped I/O,
26 whereby data written directly to a certain address range in main memory
27 is written to the device’s internal memory buffer.
28 Since port I/O involves reading or writing individual bytes or words, the transfer
of large
29 amounts of data between a disk device and memory could potentially take up a great
30 deal of better-spent CPU time. And an interrupt is raised
31 when either the work is finished
32 or if there was some error
33 that is stopping the work from being finished.
34 */
35
36 // display buffer
37 #define VIDEO_ADDRESS 0xb8000
38 #define MAX_ROWS 25
39 #define MAX_COLS 80
40
41 // Screen device I/O ports
42 #define REG_SCREEN_CTRL 0x3D4
43 #define REG_SCREEN_DATA 0x3D5
44
45 // Attribute byte for our default colour scheme .
46 #define WHITE_ON_BLACK 0x0f
47
48 #define GET_SCREEN_OFFSET(COL, ROW) (2 * ((ROW) * MAX_COLS + (COL)))
49 #define GET_OFFSET_ROW(OFFSET) (OFFSET / (2 * MAX_COLS))
50 #define GET_OFFSET_COL(OFFSET) ((OFFSET - (GET_OFFSET_ROW(OFFSET) * 2 * MAX_COLS)) /
2)
51
52 int get_cursor_offset()
53 {
54 /**
55 * The device uses its control register as an index
56 * to select its internal registers, of which we are
57 * interested in:
58 * reg 14: which is the high byte of the cursor's offset
59 * reg 15: which is the low byte of the cursor's offset
60 * Once the internal register has been selected,
61 * we may read or write a byte on the data register
62 */
63 out_byte(REG_SCREEN_CTRL, 14);
64 int offset = in_byte(REG_SCREEN_DATA) << 8; /* High byte: << 8 */
65 out_byte(REG_SCREEN_CTRL, 15);
66 offset += in_byte(REG_SCREEN_DATA);
67 /**
68 * Since the cursor offset reported by the VGA hardware is the number of
characters,
69 * we multiply by two to convert it to a character cell offset.
70 */
71 return offset * 2;
72 }
73
74 void set_cursor_offset(int offset)
75 {
76 // Convert from cell offset to char offset
77 offset /= 2;
78 // This is similar to get_cursor,
79 // only now we write bytes to those internal device registers
80 out_byte(REG_SCREEN_CTRL, 14);
81 out_byte(REG_SCREEN_DATA, (byte)(offset >> 8));
82 out_byte(REG_SCREEN_CTRL, 15);
83 out_byte(REG_SCREEN_DATA, (byte)(offset & 0xff));
84 }
85
86 /* Advance the text cursor , scrolling the video buffer if necessary . */
87 int handle_scrolling(int cursor_offset)
88 {
89 // If the cursor is within the screen , return it unmodified .
90 if (cursor_offset < MAX_ROWS * MAX_COLS * 2)
91 {
92 return cursor_offset;
93 }
94 /* Shuffle the rows back one . */
95 for (nat32 i = 1; i < MAX_ROWS; i++)
96 {
97 memory_copy((byte *)(VIDEO_ADDRESS + GET_SCREEN_OFFSET(0, i)),
98 (byte *)(VIDEO_ADDRESS + GET_SCREEN_OFFSET(0, i - 1)),
99 MAX_COLS * 2);
100 }
101 /* Blank the last line by setting all bytes to 0 */
102 byte *last_line = (byte *)(VIDEO_ADDRESS + GET_SCREEN_OFFSET(0, MAX_ROWS - 1));
103 for (nat32 i = 0; i < MAX_COLS * 2; i++)
104 {
105 last_line[i] = 0;
106 }
107 // Move the offset back one row , such that it is now on the last
108 // row , rather than off the edge of the screen .
109 cursor_offset -= 2 * MAX_COLS;
110 // Return the updated cursor position .
111 return cursor_offset;
112 }
113
114 /* Print a char on the screen at col , row , or at cursor position */
115 void print_char(char character, int col, int row, char attribute_byte)
116 {
117 /* Create a byte (char) pointer to the start of video memory
118 * and point it to the first text cell (i.e. top-left of the screen)
119 */
120 unsigned char *vidmem = (unsigned char *)VIDEO_ADDRESS;
121
122 /* If attribute byte is zero , assume the blank style . */
123 if (!attribute_byte)
124 {
125 attribute_byte = WHITE_ON_BLACK;
126 }
127
128 /* Get the video memory offset for the screen location */
129 int offset;
130 /* If col and row are non - negative , use them for offset . */
131 if (col >= 0 && row >= 0)
132 {
133 offset = GET_SCREEN_OFFSET(col, row);
134 }
135 /* Otherwise , use the current cursor position . */
136 else
137 {
138 offset = get_cursor_offset();
139 }
140 // If we see a newline character , set offset to the end of
141 // current row , so it will be advanced to the first col
142 // of the next row.
143 if (character == '\n')
144 {
145 int rows = offset / (2 * MAX_COLS);
146 offset = GET_SCREEN_OFFSET(79, rows);
147 }
148 // Otherwise , write the character and its attribute byte to
149 // video memory at our calculated offset.
150 else
151 {
152 vidmem[offset] = character;
153 vidmem[offset + 1] = attribute_byte;
154 }
155
156 // Update the offset to the next character cell, which is
157 // two bytes ahead of the current cell .
158 offset += 2;
159
160 // Make scrolling adjustment , for when we reach the bottom
161 // of the screen.
162 offset = handle_scrolling(offset);
163
164 // Update the cursor position on the screen device .
165 set_cursor_offset(offset);
166 }
167
168 extern void print(char *string)
169 {
170 // Loop through each char of the message and print it.
171 for (; *string; string++)
172 {
173 // print the character pointed to by string
174 print_char(*string, -1, -1, 0);
175 }
176 }
177
178 extern void print_at(char *string, int col, int row)
179 {
180 // Update the cursor if col and row not negative .
181 if (col >= 0 && row >= 0)
182 {
183 set_cursor_offset(GET_SCREEN_OFFSET(col, row));
184 }
185 print(string);
186 }
187
188 extern void clear()
189 {
190 byte blank[2] = {' ', WHITE_ON_BLACK};
191 int screen_size = MAX_COLS * MAX_ROWS;
192 memory_fill((byte *)VIDEO_ADDRESS, screen_size * 2, blank, 2);
193 set_cursor_offset(GET_SCREEN_OFFSET(0, 0));
194 }