summaryrefslogtreecommitdiff
path: root/doc/os.phil-opp.com_vga-text-mode.txt
blob: 466153f8e4f9a0cc5ff4b3b7037397990824387b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
   #[1]RSS feed for os.phil-opp.com

[2]Writing an OS in Rust

   Philipp Oppermann's blog

   [3]« All Posts

Table of Contents

    1. [4]The VGA Text Buffer
    2. [5]A Rust Module
         1. [6]Colors
         2. [7]Text Buffer
         3. [8]Printing
         4. [9]Volatile
         5. [10]Formatting Macros
         6. [11]Newlines
    3. [12]A Global Interface
         1. [13]Lazy Statics
         2. [14]Spinlocks
         3. [15]Safety
         4. [16]A println Macro
         5. [17]Hello World using println
         6. [18]Printing Panic Messages
    4. [19]Summary
    5. [20]What's next?
    6. [21]Comments

VGA Text Mode

   Feb 26, 2018

   The [22]VGA text mode is a simple way to print text to the screen. In
   this post, we create an interface that makes its usage safe and simple
   by encapsulating all unsafety in a separate module. We also implement
   support for Rust's [23]formatting macros.

   This blog is openly developed on [24]GitHub. If you have any problems
   or questions, please open an issue there. You can also leave comments
   [25]at the bottom. The complete source code for this post can be found
   in the [26]post-03 branch.
   Table of Contents
     * [27]The VGA Text Buffer
     * [28]A Rust Module
          + [29]Colors
          + [30]Text Buffer
          + [31]Printing
          + [32]Volatile
          + [33]Formatting Macros
          + [34]Newlines
     * [35]A Global Interface
          + [36]Lazy Statics
          + [37]Spinlocks
          + [38]Safety
          + [39]A println Macro
          + [40]Hello World using println
          + [41]Printing Panic Messages
     * [42]Summary
     * [43]What's next?
     * [44]Comments

The VGA Text Buffer

   To print a character to the screen in VGA text mode, one has to write
   it to the text buffer of the VGA hardware. The VGA text buffer is a
   two-dimensional array with typically 25 rows and 80 columns, which is
   directly rendered to the screen. Each array entry describes a single
   screen character through the following format:
   Bit(s)      Value
   0-7    ASCII code point
   8-11   Foreground color
   12-14  Background color
   15     Blink

   The first byte represents the character that should be printed in the
   [45]ASCII encoding. To be more specific, it isn't exactly ASCII, but a
   character set named [46]code page 437 with some additional characters
   and slight modifications. For simplicity, we will proceed to call it an
   ASCII character in this post.

   The second byte defines how the character is displayed. The first four
   bits define the foreground color, the next three bits the background
   color, and the last bit whether the character should blink. The
   following colors are available:
   Number   Color    Number + Bright Bit Bright Color
   0x0    Black      0x8                 Dark Gray
   0x1    Blue       0x9                 Light Blue
   0x2    Green      0xa                 Light Green
   0x3    Cyan       0xb                 Light Cyan
   0x4    Red        0xc                 Light Red
   0x5    Magenta    0xd                 Pink
   0x6    Brown      0xe                 Yellow
   0x7    Light Gray 0xf                 White

   Bit 4 is the bright bit, which turns, for example, blue into light
   blue. For the background color, this bit is repurposed as the blink
   bit.

   The VGA text buffer is accessible via [47]memory-mapped I/O to the
   address 0xb8000. This means that reads and writes to that address don't
   access the RAM but directly access the text buffer on the VGA hardware.
   This means we can read and write it through normal memory operations to
   that address.

   Note that memory-mapped hardware might not support all normal RAM
   operations. For example, a device could only support byte-wise reads
   and return junk when a u64 is read. Fortunately, the text buffer
   [48]supports normal reads and writes, so we don't have to treat it in a
   special way.

A Rust Module

   Now that we know how the VGA buffer works, we can create a Rust module
   to handle printing:
//in src/main.rs
mod vga_buffer;

   For the content of this module, we create a new src/vga_buffer.rs file.
   All of the code below goes into our new module (unless specified
   otherwise).

Colors

   First, we represent the different colors using an enum:
// in src/vga_buffer.rs

#[allow(dead_code)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum Color {
    Black = 0,
    Blue = 1,
    Green = 2,
    Cyan = 3,
    Red = 4,
    Magenta = 5,
    Brown = 6,
    LightGray = 7,
    DarkGray = 8,
    LightBlue = 9,
    LightGreen = 10,
    LightCyan = 11,
    LightRed = 12,
    Pink = 13,
    Yellow = 14,
    White = 15,
}

   We use a [49]C-like enum here to explicitly specify the number for each
   color. Because of the repr(u8) attribute, each enum variant is stored
   as a u8. Actually 4 bits would be sufficient, but Rust doesn't have a
   u4 type.

   Normally the compiler would issue a warning for each unused variant. By
   using the #[allow(dead_code)] attribute, we disable these warnings for
   the Color enum.

   By [50]deriving the [51]Copy, [52]Clone, [53]Debug, [54]PartialEq, and
   [55]Eq traits, we enable [56]copy semantics for the type and make it
   printable and comparable.

   To represent a full color code that specifies foreground and background
   color, we create a [57]newtype on top of u8:
// in src/vga_buffer.rs

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(transparent)]
struct ColorCode(u8);

impl ColorCode {
    fn new(foreground: Color, background: Color) -> ColorCode {
        ColorCode((background as u8) << 4 | (foreground as u8))
    }
}

   The ColorCode struct contains the full color byte, containing
   foreground and background color. Like before, we derive the Copy and
   Debug traits for it. To ensure that the ColorCode has the exact same
   data layout as a u8, we use the [58]repr(transparent) attribute.

Text Buffer

   Now we can add structures to represent a screen character and the text
   buffer:
// in src/vga_buffer.rs

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(C)]
struct ScreenChar {
    ascii_character: u8,
    color_code: ColorCode,
}

const BUFFER_HEIGHT: usize = 25;
const BUFFER_WIDTH: usize = 80;

#[repr(transparent)]
struct Buffer {
    chars: [[ScreenChar; BUFFER_WIDTH]; BUFFER_HEIGHT],
}

   Since the field ordering in default structs is undefined in Rust, we
   need the [59]repr(C) attribute. It guarantees that the struct's fields
   are laid out exactly like in a C struct and thus guarantees the correct
   field ordering. For the Buffer struct, we use [60]repr(transparent)
   again to ensure that it has the same memory layout as its single field.

   To actually write to screen, we now create a writer type:
// in src/vga_buffer.rs

pub struct Writer {
    column_position: usize,
    color_code: ColorCode,
    buffer: &'static mut Buffer,
}

   The writer will always write to the last line and shift lines up when a
   line is full (or on \n). The column_position field keeps track of the
   current position in the last row. The current foreground and background
   colors are specified by color_code and a reference to the VGA buffer is
   stored in buffer. Note that we need an [61]explicit lifetime here to
   tell the compiler how long the reference is valid. The [62]'static
   lifetime specifies that the reference is valid for the whole program
   run time (which is true for the VGA text buffer).

Printing

   Now we can use the Writer to modify the buffer's characters. First we
   create a method to write a single ASCII byte:
// in src/vga_buffer.rs

impl Writer {
    pub fn write_byte(&mut self, byte: u8) {
        match byte {
            b'\n' => self.new_line(),
            byte => {
                if self.column_position >= BUFFER_WIDTH {
                    self.new_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;
            }
        }
    }

    fn new_line(&mut self) {/* TODO */}
}

   If the byte is the [63]newline byte \n, the writer does not print
   anything. Instead, it calls a new_line method, which we'll implement
   later. Other bytes get printed to the screen in the second match case.

   When printing a byte, the writer checks if the current line is full. In
   that case, a new_line call is used to wrap the line. Then it writes a
   new ScreenChar to the buffer at the current position. Finally, the
   current column position is advanced.

   To print whole strings, we can convert them to bytes and print them
   one-by-one:
// in src/vga_buffer.rs

impl Writer {
    pub fn write_string(&mut self, s: &str) {
        for byte in s.bytes() {
            match byte {
                // printable ASCII byte or newline
                0x20..=0x7e | b'\n' => self.write_byte(byte),
                // not part of printable ASCII range
                _ => self.write_byte(0xfe),
            }

        }
    }
}

   The VGA text buffer only supports ASCII and the additional bytes of
   [64]code page 437. Rust strings are [65]UTF-8 by default, so they might
   contain bytes that are not supported by the VGA text buffer. We use a
   match to differentiate printable ASCII bytes (a newline or anything in
   between a space character and a ~ character) and unprintable bytes. For
   unprintable bytes, we print a fS character, which has the hex code 0xfe
   on the VGA hardware.

Try it out!

   To write some characters to the screen, you can create a temporary
   function:
// in src/vga_buffer.rs

pub fn print_something() {
    let mut writer = Writer {
        column_position: 0,
        color_code: ColorCode::new(Color::Yellow, Color::Black),
        buffer: unsafe { &mut *(0xb8000 as *mut Buffer) },
    };

    writer.write_byte(b'H');
    writer.write_string("ello ");
    writer.write_string("Wörld!");
}

   It first creates a new Writer that points to the VGA buffer at 0xb8000.
   The syntax for this might seem a bit strange: First, we cast the
   integer 0xb8000 as a mutable [66]raw pointer. Then we convert it to a
   mutable reference by dereferencing it (through *) and immediately
   borrowing it again (through &mut). This conversion requires an
   [67]unsafe block, since the compiler can't guarantee that the raw
   pointer is valid.

   Then it writes the byte b'H' to it. The b prefix creates a [68]byte
   literal, which represents an ASCII character. By writing the strings
   "ello " and "Wörld!", we test our write_string method and the handling
   of unprintable characters. To see the output, we need to call the
   print_something function from our _start function:
// in src/main.rs

#[no_mangle]
pub extern "C" fn _start() -> ! {
    vga_buffer::print_something();

    loop {}
}

   When we run our project now, a Hello WfSfSrld! should be printed in the
   lower left corner of the screen in yellow:

   QEMU output with a yellow Hello WfSfSrld! in the lower left corner

   Notice that the ö is printed as two fS characters. That's because ö is
   represented by two bytes in [69]UTF-8, which both don't fall into the
   printable ASCII range. In fact, this is a fundamental property of
   UTF-8: the individual bytes of multi-byte values are never valid ASCII.

Volatile

   We just saw that our message was printed correctly. However, it might
   not work with future Rust compilers that optimize more aggressively.

   The problem is that we only write to the Buffer and never read from it
   again. The compiler doesn't know that we really access VGA buffer
   memory (instead of normal RAM) and knows nothing about the side effect
   that some characters appear on the screen. So it might decide that
   these writes are unnecessary and can be omitted. To avoid this
   erroneous optimization, we need to specify these writes as
   [70]volatile. This tells the compiler that the write has side effects
   and should not be optimized away.

   In order to use volatile writes for the VGA buffer, we use the
   [71]volatile library. This crate (this is how packages are called in
   the Rust world) provides a Volatile wrapper type with read and write
   methods. These methods internally use the [72]read_volatile and
   [73]write_volatile functions of the core library and thus guarantee
   that the reads/writes are not optimized away.

   We can add a dependency on the volatile crate by adding it to the
   dependencies section of our Cargo.toml:
# in Cargo.toml

[dependencies]
volatile = "0.2.6"

   Make sure to specify volatile version 0.2.6. Newer versions of the
   crate are not compatible with this post. 0.2.6 is the [74]semantic
   version number. For more information, see the [75]Specifying
   Dependencies guide of the cargo documentation.

   Let's use it to make writes to the VGA buffer volatile. We update our
   Buffer type as follows:
// in src/vga_buffer.rs

use volatile::Volatile;

struct Buffer {
    chars: [[Volatile<ScreenChar>; BUFFER_WIDTH]; BUFFER_HEIGHT],
}

   Instead of a ScreenChar, we're now using a Volatile<ScreenChar>. (The
   Volatile type is [76]generic and can wrap (almost) any type). This
   ensures that we can't accidentally write to it "normally". Instead, we
   have to use the write method now.

   This means that we have to update our Writer::write_byte method:
// in src/vga_buffer.rs

impl Writer {
    pub fn write_byte(&mut self, byte: u8) {
        match byte {
            b'\n' => self.new_line(),
            byte => {
                ...

                self.buffer.chars[row][col].write(ScreenChar {
                    ascii_character: byte,
                    color_code,
                });
                ...
            }
        }
    }
    ...
}

   Instead of a typical assignment using =, we're now using the write
   method. Now we can guarantee that the compiler will never optimize away
   this write.

Formatting Macros

   It would be nice to support Rust's formatting macros, too. That way, we
   can easily print different types, like integers or floats. To support
   them, we need to implement the [77]core::fmt::Write trait. The only
   required method of this trait is write_str, which looks quite similar
   to our write_string method, just with a fmt::Result return type:
// in src/vga_buffer.rs

use core::fmt;

impl fmt::Write for Writer {
    fn write_str(&mut self, s: &str) -> fmt::Result {
        self.write_string(s);
        Ok(())
    }
}

   The Ok(()) is just a Ok Result containing the () type.

   Now we can use Rust's built-in write!/writeln! formatting macros:
// in src/vga_buffer.rs

pub fn print_something() {
    use core::fmt::Write;
    let mut writer = Writer {
        column_position: 0,
        color_code: ColorCode::new(Color::Yellow, Color::Black),
        buffer: unsafe { &mut *(0xb8000 as *mut Buffer) },
    };

    writer.write_byte(b'H');
    writer.write_string("ello! ");
    write!(writer, "The numbers are {} and {}", 42, 1.0/3.0).unwrap();
}

   Now you should see a Hello! The numbers are 42 and 0.3333333333333333
   at the bottom of the screen. The write! call returns a Result which
   causes a warning if not used, so we call the [78]unwrap function on it,
   which panics if an error occurs. This isn't a problem in our case,
   since writes to the VGA buffer never fail.

Newlines

   Right now, we just ignore newlines and characters that don't fit into
   the line anymore. Instead, we want to move every character one line up
   (the top line gets deleted) and start at the beginning of the last line
   again. To do this, we add an implementation for the new_line method of
   Writer:
// in src/vga_buffer.rs

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

    fn clear_row(&mut self, row: usize) {/* TODO */}
}

   We iterate over all the screen characters and move each character one
   row up. Note that the upper bound of the range notation (..) is
   exclusive. We also omit the 0th row (the first range starts at 1)
   because it's the row that is shifted off screen.

   To finish the newline code, we add the clear_row method:
// in src/vga_buffer.rs

impl Writer {
    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].write(blank);
        }
    }
}

   This method clears a row by overwriting all of its characters with a
   space character.

A Global Interface

   To provide a global writer that can be used as an interface from other
   modules without carrying a Writer instance around, we try to create a
   static WRITER:
// in src/vga_buffer.rs

pub static WRITER: Writer = Writer {
    column_position: 0,
    color_code: ColorCode::new(Color::Yellow, Color::Black),
    buffer: unsafe { &mut *(0xb8000 as *mut Buffer) },
};

   However, if we try to compile it now, the following errors occur:
error[E0015]: calls in statics are limited to constant functions, tuple structs
and tuple variants
 --> src/vga_buffer.rs:7:17
  |
7 |     color_code: ColorCode::new(Color::Yellow, Color::Black),
  |                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

error[E0396]: raw pointers cannot be dereferenced in statics
 --> src/vga_buffer.rs:8:22
  |
8 |     buffer: unsafe { &mut *(0xb8000 as *mut Buffer) },
  |                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ dereference of raw point
er in constant

error[E0017]: references in statics may only refer to immutable values
 --> src/vga_buffer.rs:8:22
  |
8 |     buffer: unsafe { &mut *(0xb8000 as *mut Buffer) },
  |                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ statics require immutabl
e values

error[E0017]: references in statics may only refer to immutable values
 --> src/vga_buffer.rs:8:13
  |
8 |     buffer: unsafe { &mut *(0xb8000 as *mut Buffer) },
  |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ statics require immuta
ble values

   To understand what's happening here, we need to know that statics are
   initialized at compile time, in contrast to normal variables that are
   initialized at run time. The component of the Rust compiler that
   evaluates such initialization expressions is called the "[79]const
   evaluator". Its functionality is still limited, but there is ongoing
   work to expand it, for example in the "[80]Allow panicking in
   constants" RFC.

   The issue with ColorCode::new would be solvable by using [81]const
   functions, but the fundamental problem here is that Rust's const
   evaluator is not able to convert raw pointers to references at compile
   time. Maybe it will work someday, but until then, we have to find
   another solution.

Lazy Statics

   The one-time initialization of statics with non-const functions is a
   common problem in Rust. Fortunately, there already exists a good
   solution in a crate named [82]lazy_static. This crate provides a
   lazy_static! macro that defines a lazily initialized static. Instead of
   computing its value at compile time, the static lazily initializes
   itself when accessed for the first time. Thus, the initialization
   happens at runtime, so arbitrarily complex initialization code is
   possible.

   Let's add the lazy_static crate to our project:
# in Cargo.toml

[dependencies.lazy_static]
version = "1.0"
features = ["spin_no_std"]

   We need the spin_no_std feature, since we don't link the standard
   library.

   With lazy_static, we can define our static WRITER without problems:
// in src/vga_buffer.rs

use lazy_static::lazy_static;

lazy_static! {
    pub static ref WRITER: Writer = Writer {
        column_position: 0,
        color_code: ColorCode::new(Color::Yellow, Color::Black),
        buffer: unsafe { &mut *(0xb8000 as *mut Buffer) },
    };
}

   However, this WRITER is pretty useless since it is immutable. This
   means that we can't write anything to it (since all the write methods
   take &mut self). One possible solution would be to use a [83]mutable
   static. But then every read and write to it would be unsafe since it
   could easily introduce data races and other bad things. Using static
   mut is highly discouraged. There were even proposals to [84]remove it.
   But what are the alternatives? We could try to use an immutable static
   with a cell type like [85]RefCell or even [86]UnsafeCell that provides
   [87]interior mutability. But these types aren't [88]Sync (with good
   reason), so we can't use them in statics.

Spinlocks

   To get synchronized interior mutability, users of the standard library
   can use [89]Mutex. It provides mutual exclusion by blocking threads
   when the resource is already locked. But our basic kernel does not have
   any blocking support or even a concept of threads, so we can't use it
   either. However, there is a really basic kind of mutex in computer
   science that requires no operating system features: the [90]spinlock.
   Instead of blocking, the threads simply try to lock it again and again
   in a tight loop, thus burning CPU time until the mutex is free again.

   To use a spinning mutex, we can add the [91]spin crate as a dependency:
# in Cargo.toml
[dependencies]
spin = "0.5.2"

   Then we can use the spinning mutex to add safe [92]interior mutability
   to our static WRITER:
// in src/vga_buffer.rs

use spin::Mutex;
...
lazy_static! {
    pub static ref WRITER: Mutex<Writer> = Mutex::new(Writer {
        column_position: 0,
        color_code: ColorCode::new(Color::Yellow, Color::Black),
        buffer: unsafe { &mut *(0xb8000 as *mut Buffer) },
    });
}

   Now we can delete the print_something function and print directly from
   our _start function:
// in src/main.rs
#[no_mangle]
pub extern "C" fn _start() -> ! {
    use core::fmt::Write;
    vga_buffer::WRITER.lock().write_str("Hello again").unwrap();
    write!(vga_buffer::WRITER.lock(), ", some numbers: {} {}", 42, 1.337).unwrap
();

    loop {}
}

   We need to import the fmt::Write trait in order to be able to use its
   functions.

Safety

   Note that we only have a single unsafe block in our code, which is
   needed to create a Buffer reference pointing to 0xb8000. Afterwards,
   all operations are safe. Rust uses bounds checking for array accesses
   by default, so we can't accidentally write outside the buffer. Thus, we
   encoded the required conditions in the type system and are able to
   provide a safe interface to the outside.

A println Macro

   Now that we have a global writer, we can add a println macro that can
   be used from anywhere in the codebase. Rust's [93]macro syntax is a bit
   strange, so we won't try to write a macro from scratch. Instead, we
   look at the source of the [94]println! macro in the standard library:
#[macro_export]
macro_rules! println {
    () => (print!("\n"));
    ($($arg:tt)*) => (print!("{}\n", format_args!($($arg)*)));
}

   Macros are defined through one or more rules, similar to match arms.
   The println macro has two rules: The first rule is for invocations
   without arguments, e.g., println!(), which is expanded to print!("\n")
   and thus just prints a newline. The second rule is for invocations with
   parameters such as println!("Hello") or println!("Number: {}", 4). It
   is also expanded to an invocation of the print! macro, passing all
   arguments and an additional newline \n at the end.

   The #[macro_export] attribute makes the macro available to the whole
   crate (not just the module it is defined in) and external crates. It
   also places the macro at the crate root, which means we have to import
   the macro through use std::println instead of std::macros::println.

   The [95]print! macro is defined as:
#[macro_export]
macro_rules! print {
    ($($arg:tt)*) => ($crate::io::_print(format_args!($($arg)*)));
}

   The macro expands to a call of the [96]_print function in the io
   module. The [97]$crate variable ensures that the macro also works from
   outside the std crate by expanding to std when it's used in other
   crates.

   The [98]format_args macro builds a [99]fmt::Arguments type from the
   passed arguments, which is passed to _print. The [100]_print function
   of libstd calls print_to, which is rather complicated because it
   supports different Stdout devices. We don't need that complexity since
   we just want to print to the VGA buffer.

   To print to the VGA buffer, we just copy the println! and print!
   macros, but modify them to use our own _print function:
// in src/vga_buffer.rs

#[macro_export]
macro_rules! print {
    ($($arg:tt)*) => ($crate::vga_buffer::_print(format_args!($($arg)*)));
}

#[macro_export]
macro_rules! println {
    () => ($crate::print!("\n"));
    ($($arg:tt)*) => ($crate::print!("{}\n", format_args!($($arg)*)));
}

#[doc(hidden)]
pub fn _print(args: fmt::Arguments) {
    use core::fmt::Write;
    WRITER.lock().write_fmt(args).unwrap();
}

   One thing that we changed from the original println definition is that
   we prefixed the invocations of the print! macro with $crate too. This
   ensures that we don't need to import the print! macro too if we only
   want to use println.

   Like in the standard library, we add the #[macro_export] attribute to
   both macros to make them available everywhere in our crate. Note that
   this places the macros in the root namespace of the crate, so importing
   them via use crate::vga_buffer::println does not work. Instead, we have
   to do use crate::println.

   The _print function locks our static WRITER and calls the write_fmt
   method on it. This method is from the Write trait, which we need to
   import. The additional unwrap() at the end panics if printing isn't
   successful. But since we always return Ok in write_str, that should not
   happen.

   Since the macros need to be able to call _print from outside of the
   module, the function needs to be public. However, since we consider
   this a private implementation detail, we add the [101]doc(hidden)
   attribute to hide it from the generated documentation.

Hello World using println

   Now we can use println in our _start function:
// in src/main.rs

#[no_mangle]
pub extern "C" fn _start() {
    println!("Hello World{}", "!");

    loop {}
}

   Note that we don't have to import the macro in the main function,
   because it already lives in the root namespace.

   As expected, we now see a "Hello World!" on the screen:

   QEMU printing "Hello World!"

Printing Panic Messages

   Now that we have a println macro, we can use it in our panic function
   to print the panic message and the location of the panic:
// in main.rs

/// This function is called on panic.
#[panic_handler]
fn panic(info: &PanicInfo) -> ! {
    println!("{}", info);
    loop {}
}

   When we now insert panic!("Some panic message"); in our _start
   function, we get the following output:

   QEMU printing "panicked at `Some panic message', src/main.rs:28:5

   So we know not only that a panic has occurred, but also the panic
   message and where in the code it happened.

Summary

   In this post, we learned about the structure of the VGA text buffer and
   how it can be written through the memory mapping at address 0xb8000. We
   created a Rust module that encapsulates the unsafety of writing to this
   memory-mapped buffer and presents a safe and convenient interface to
   the outside.

   Thanks to cargo, we also saw how easy it is to add dependencies on
   third-party libraries. The two dependencies that we added, lazy_static
   and spin, are very useful in OS development and we will use them in
   more places in future posts.

What's next?

   The next post explains how to set up Rust's built-in unit test
   framework. We will then create some basic unit tests for the VGA buffer
   module from this post.

Support Me

   Creating and [102]maintaining this blog and the associated libraries is
   a lot of work, but I really enjoy doing it. By supporting me, you allow
   me to invest more time in new content, new features, and continuous
   maintenance.

   The best way to support me is to [103]sponsor me on GitHub, since they
   don't charge any fees. If you prefer other platforms, I also have
   [104]Patreon and [105]Donorbox accounts. The latter is the most
   flexible as it supports multiple currencies and one-time contributions.

   Thank you!
     __________________________________________________________________

   [106]« A Minimal Rust Kernel [107]Testing »
     __________________________________________________________________

Comments

   Do you have a problem, want to share feedback, or discuss further
   ideas? Feel free to leave a comment here! Please stick to English and
   follow Rust's [108]code of conduct. This comment thread directly maps
   to a [109]discussion on GitHub, so you can also comment there if you
   prefer.

   Instead of authenticating the [110]giscus application, you can also
   comment directly [111]on GitHub.

Other Languages

     * [112]Chinese (simplified)
     * [113]Japanese
     * [114]Persian
     * [115]Korean
     __________________________________________________________________

   © 2022. All rights reserved. [116]License [117]Contact

References

   Visible links:
   1. https://os.phil-opp.com/rss.xml
   2. https://os.phil-opp.com/
   3. https://os.phil-opp.com/
   4. https://os.phil-opp.com/vga-text-mode/#the-vga-text-buffer
   5. https://os.phil-opp.com/vga-text-mode/#a-rust-module
   6. https://os.phil-opp.com/vga-text-mode/#colors
   7. https://os.phil-opp.com/vga-text-mode/#text-buffer
   8. https://os.phil-opp.com/vga-text-mode/#printing
   9. https://os.phil-opp.com/vga-text-mode/#volatile
  10. https://os.phil-opp.com/vga-text-mode/#formatting-macros
  11. https://os.phil-opp.com/vga-text-mode/#newlines
  12. https://os.phil-opp.com/vga-text-mode/#a-global-interface
  13. https://os.phil-opp.com/vga-text-mode/#lazy-statics
  14. https://os.phil-opp.com/vga-text-mode/#spinlocks
  15. https://os.phil-opp.com/vga-text-mode/#safety
  16. https://os.phil-opp.com/vga-text-mode/#a-println-macro
  17. https://os.phil-opp.com/vga-text-mode/#hello-world-using-println
  18. https://os.phil-opp.com/vga-text-mode/#printing-panic-messages
  19. https://os.phil-opp.com/vga-text-mode/#summary
  20. https://os.phil-opp.com/vga-text-mode/#what-s-next
  21. https://os.phil-opp.com/vga-text-mode/#comments
  22. https://en.wikipedia.org/wiki/VGA-compatible_text_mode
  23. https://doc.rust-lang.org/std/fmt/#related-macros
  24. https://github.com/phil-opp/blog_os
  25. https://os.phil-opp.com/vga-text-mode/#comments
  26. https://github.com/phil-opp/blog_os/tree/post-03
  27. https://os.phil-opp.com/vga-text-mode/#the-vga-text-buffer
  28. https://os.phil-opp.com/vga-text-mode/#a-rust-module
  29. https://os.phil-opp.com/vga-text-mode/#colors
  30. https://os.phil-opp.com/vga-text-mode/#text-buffer
  31. https://os.phil-opp.com/vga-text-mode/#printing
  32. https://os.phil-opp.com/vga-text-mode/#volatile
  33. https://os.phil-opp.com/vga-text-mode/#formatting-macros
  34. https://os.phil-opp.com/vga-text-mode/#newlines
  35. https://os.phil-opp.com/vga-text-mode/#a-global-interface
  36. https://os.phil-opp.com/vga-text-mode/#lazy-statics
  37. https://os.phil-opp.com/vga-text-mode/#spinlocks
  38. https://os.phil-opp.com/vga-text-mode/#safety
  39. https://os.phil-opp.com/vga-text-mode/#a-println-macro
  40. https://os.phil-opp.com/vga-text-mode/#hello-world-using-println
  41. https://os.phil-opp.com/vga-text-mode/#printing-panic-messages
  42. https://os.phil-opp.com/vga-text-mode/#summary
  43. https://os.phil-opp.com/vga-text-mode/#what-s-next
  44. https://os.phil-opp.com/vga-text-mode/#comments
  45. https://en.wikipedia.org/wiki/ASCII
  46. https://en.wikipedia.org/wiki/Code_page_437
  47. https://en.wikipedia.org/wiki/Memory-mapped_I/O
  48. https://web.stanford.edu/class/cs140/projects/pintos/specs/freevga/vga/vgamem.htm#manip
  49. https://doc.rust-lang.org/rust-by-example/custom_types/enum/c_like.html
  50. https://doc.rust-lang.org/rust-by-example/trait/derive.html
  51. https://doc.rust-lang.org/nightly/core/marker/trait.Copy.html
  52. https://doc.rust-lang.org/nightly/core/clone/trait.Clone.html
  53. https://doc.rust-lang.org/nightly/core/fmt/trait.Debug.html
  54. https://doc.rust-lang.org/nightly/core/cmp/trait.PartialEq.html
  55. https://doc.rust-lang.org/nightly/core/cmp/trait.Eq.html
  56. https://doc.rust-lang.org/1.30.0/book/first-edition/ownership.html#copy-types
  57. https://doc.rust-lang.org/rust-by-example/generics/new_types.html
  58. https://doc.rust-lang.org/nomicon/other-reprs.html#reprtransparent
  59. https://doc.rust-lang.org/nightly/nomicon/other-reprs.html#reprc
  60. https://doc.rust-lang.org/nomicon/other-reprs.html#reprtransparent
  61. https://doc.rust-lang.org/book/ch10-03-lifetime-syntax.html#lifetime-annotation-syntax
  62. https://doc.rust-lang.org/book/ch10-03-lifetime-syntax.html#the-static-lifetime
  63. https://en.wikipedia.org/wiki/Newline
  64. https://en.wikipedia.org/wiki/Code_page_437
  65. https://www.fileformat.info/info/unicode/utf8.htm
  66. https://doc.rust-lang.org/book/ch19-01-unsafe-rust.html#dereferencing-a-raw-pointer
  67. https://doc.rust-lang.org/book/ch19-01-unsafe-rust.html
  68. https://doc.rust-lang.org/reference/tokens.html#byte-literals
  69. https://www.fileformat.info/info/unicode/utf8.htm
  70. https://en.wikipedia.org/wiki/Volatile_(computer_programming)
  71. https://docs.rs/volatile
  72. https://doc.rust-lang.org/nightly/core/ptr/fn.read_volatile.html
  73. https://doc.rust-lang.org/nightly/core/ptr/fn.write_volatile.html
  74. https://semver.org/
  75. https://doc.crates.io/specifying-dependencies.html
  76. https://doc.rust-lang.org/book/ch10-01-syntax.html
  77. https://doc.rust-lang.org/nightly/core/fmt/trait.Write.html
  78. https://doc.rust-lang.org/core/result/enum.Result.html#method.unwrap
  79. https://rustc-dev-guide.rust-lang.org/const-eval.html
  80. https://github.com/rust-lang/rfcs/pull/2345
  81. https://doc.rust-lang.org/reference/const_eval.html#const-functions
  82. https://docs.rs/lazy_static/1.0.1/lazy_static/
  83. https://doc.rust-lang.org/book/ch19-01-unsafe-rust.html#accessing-or-modifying-a-mutable-static-variable
  84. https://internals.rust-lang.org/t/pre-rfc-remove-static-mut/1437
  85. https://doc.rust-lang.org/book/ch15-05-interior-mutability.html#keeping-track-of-borrows-at-runtime-with-refcellt
  86. https://doc.rust-lang.org/nightly/core/cell/struct.UnsafeCell.html
  87. https://doc.rust-lang.org/book/ch15-05-interior-mutability.html
  88. https://doc.rust-lang.org/nightly/core/marker/trait.Sync.html
  89. https://doc.rust-lang.org/nightly/std/sync/struct.Mutex.html
  90. https://en.wikipedia.org/wiki/Spinlock
  91. https://crates.io/crates/spin
  92. https://doc.rust-lang.org/book/ch15-05-interior-mutability.html
  93. https://doc.rust-lang.org/nightly/book/ch19-06-macros.html#declarative-macros-with-macro_rules-for-general-metaprogramming
  94. https://doc.rust-lang.org/nightly/std/macro.println!.html
  95. https://doc.rust-lang.org/nightly/std/macro.print!.html
  96. https://github.com/rust-lang/rust/blob/29f5c699b11a6a148f097f82eaa05202f8799bbc/src/libstd/io/stdio.rs#L698
  97. https://doc.rust-lang.org/1.30.0/book/first-edition/macros.html#the-variable-crate
  98. https://doc.rust-lang.org/nightly/std/macro.format_args.html
  99. https://doc.rust-lang.org/nightly/core/fmt/struct.Arguments.html
 100. https://github.com/rust-lang/rust/blob/29f5c699b11a6a148f097f82eaa05202f8799bbc/src/libstd/io/stdio.rs#L698
 101. https://doc.rust-lang.org/nightly/rustdoc/write-documentation/the-doc-attribute.html#hidden
 102. https://os.phil-opp.com/status-update/
 103. https://github.com/sponsors/phil-opp
 104. https://www.patreon.com/phil_opp
 105. https://donorbox.org/phil-opp
 106. https://os.phil-opp.com/minimal-rust-kernel/
 107. https://os.phil-opp.com/testing/
 108. https://www.rust-lang.org/policies/code-of-conduct
 109. https://github.com/phil-opp/blog_os/discussions/categories/post-comments?discussions_q=%22VGA%20Text%20Mode%22%20in%3Atitle
 110. https://giscus.app/
 111. https://github.com/phil-opp/blog_os/discussions/categories/post-comments?discussions_q=%22VGA%20Text%20Mode%22%20in%3Atitle
 112. https://os.phil-opp.com/zh-CN/vga-text-mode/
 113. https://os.phil-opp.com/ja/vga-text-mode/
 114. https://os.phil-opp.com/fa/vga-text-mode/
 115. https://os.phil-opp.com/ko/vga-text-mode/
 116. https://github.com/phil-opp/blog_os#license
 117. https://os.phil-opp.com/contact/

   Hidden links:
 119. https://os.phil-opp.com/vga-text-mode/#the-vga-text-buffer
 120. https://os.phil-opp.com/vga-text-mode/#a-rust-module
 121. https://os.phil-opp.com/vga-text-mode/#colors
 122. https://os.phil-opp.com/vga-text-mode/#text-buffer
 123. https://os.phil-opp.com/vga-text-mode/#printing
 124. https://os.phil-opp.com/vga-text-mode/#try-it-out
 125. https://os.phil-opp.com/vga-text-mode/#volatile
 126. https://os.phil-opp.com/vga-text-mode/#formatting-macros
 127. https://os.phil-opp.com/vga-text-mode/#newlines
 128. https://os.phil-opp.com/vga-text-mode/#a-global-interface
 129. https://os.phil-opp.com/vga-text-mode/#lazy-statics
 130. https://os.phil-opp.com/vga-text-mode/#spinlocks
 131. https://os.phil-opp.com/vga-text-mode/#safety
 132. https://os.phil-opp.com/vga-text-mode/#a-println-macro
 133. https://os.phil-opp.com/vga-text-mode/#hello-world-using-println
 134. https://os.phil-opp.com/vga-text-mode/#printing-panic-messages
 135. https://os.phil-opp.com/vga-text-mode/#summary
 136. https://os.phil-opp.com/vga-text-mode/#what-s-next