From 2209b580b59d2d3e131afe12b69b7100636e5388 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Thu, 26 Jul 2018 22:42:08 +0200 Subject: [PATCH 01/18] Begin new post about hardware interrupts --- .../posts/08-hardware-interrupts/index.md | 90 +++++++++++++++++++ blog/templates/second-edition/index.html | 1 + 2 files changed, 91 insertions(+) create mode 100644 blog/content/second-edition/posts/08-hardware-interrupts/index.md diff --git a/blog/content/second-edition/posts/08-hardware-interrupts/index.md b/blog/content/second-edition/posts/08-hardware-interrupts/index.md new file mode 100644 index 00000000..6aa55976 --- /dev/null +++ b/blog/content/second-edition/posts/08-hardware-interrupts/index.md @@ -0,0 +1,90 @@ ++++ +title = "Hardware Interrupts" +order = 8 +path = "hardware-interrupts" +date = 2018-07-26 +template = "second-edition/page.html" ++++ + +In this post we set up the programmable interrupt controller to correctly forward hardware interrupts to the CPU. This allows us to create handler functions for these, which work in almost the same way as our exception handlers. We will then learn how to configure a hardware timer so that we get periodic interrupts and also how to add keyboard support. + + + +This blog is openly developed on [Github]. If you have any problems or questions, please open an issue there. You can also leave comments [at the bottom]. + +[Github]: https://github.com/phil-opp/blog_os +[at the bottom]: #comments + +## Overview + +Interrupts provide a way to notify the CPU from attached hardware devices. So instead of letting the kernel periodically check the keyboard for new characters (a process called [_polling_]), the keyboard can notify the kernel of each keypress. This is much more efficient because the kernel only needs to act when something happened. It also allows faster reaction times, because the kernel can react immediately and not only at the next poll. + +[_polling_]: https://en.wikipedia.org/wiki/Polling_(computer_science) + + +``` + ____________ _____ + Timer ------------> | | | | + Keyboard ---------> | Interrupt | --------> | CPU | + Other Hardware ---> | Controller | |_____| + Etc. -------------> |____________| + +``` + +Connecting all hardware devices directly to the CPU is not possible. Instead, a separate _interrupt controller_ aggregates the interrupts from all devices and then notifies the CPU. Most interrupt controllers are programmable, which means that they support different priority levels for interrupts. For example, we could give timer interrupts a higher priority than keyboard interrupts to ensure accurate timekeeping. + +Unlike exceptions, hardware interrupts occur asynchronously. This means that they are completely independent from the executed code and can occur at any time. Thus we suddenly have a form of concurrency in our kernel with all the potential concurrency-related bugs. Rust's strict ownership model helps us here because it forbids mutable global state. However, deadlocks are still possible, as we will see later in this post. + +## The 8259 PIC + +The [Intel 8259] is a programmable interrupt controller (PIC) introduced in 1976. It has long been replaced by the newer [APIC], but its interface is still supported on current systems for backwards compatibiliy reasons. The 8259 PIC is significantly easier to set up than the APIC, so we will use it to introduce ourselves to interrupts before we switch to the APIC in a later post. + +[APIC]: https://en.wikipedia.org/wiki/Intel_APIC_Architecture + +The 8259 has 8 interrupt lines and several lines for communicating with the CPU. The typical systems back then where equipped with two instances of the 8259 PIC, one acting as master and the other as slave connected to one of the masters interrupt lines: + +[Intel 8259]: https://en.wikipedia.org/wiki/Intel_8259 + +``` + ____________ ____________ +Real Time Clock --> | | Timer -------------> | | +ACPI -------------> | | Keyboard-----------> | | _____ +Available --------> | Slave |----------------------> | Master | | | +Available --------> | Interrupt | Serial Port 2 -----> | Interrupt | --> | CPU | +Mouse ------------> | Controller | Serial Port 1 -----> | Controller | |_____| +Co-Processor -----> | | Parallel Port 2/3 -> | | +Primary ATA ------> | | Floppy disk -------> | | +Secondary ATA ----> |____________| Parallel Port 1----> |____________| + +``` + +This graphic shows the typical assignment of interrupt lines. We see that most of the 15 lines have a fixed mapping, e.g. line 4 of the slave PIC is assigned to the mouse. + +Each controller can be configured through two I/O ports, one “command” port and one “data” port. For the master controller these ports are `0x20` (command) and `0x21` (data). For the slave they are `0xa0` (command) and `0xa1` (data). For more information on how they can be configured see the [article on osdev.org]. + +[article on osdev.org]: https://wiki.osdev.org/8259_PIC + +### Implementation + +The initial configuration of the PICs is not usable, because it sends interrupt vector numbers in the range 0–15 to the CPU. These numbers are already occupied by CPU exceptions, for example number 8 corresponds to a double fault. To fix this overlapping issue, we need to remap the PIC interrupts to different numbers. The actual range doesn't matter as long as it does not overlap with the exceptions, but typically the range 32–47 is chosen, because these are the first free numbers after the 32 exception slots. + +The configuration happens by writing special values to the command and data ports that we mentioned above. Fortunately there is already a crate called [`pic8259_simple`], so we don't need to write the initialization sequence ourselves. In case you are interested how it works, check out [its source code][pic crate source], it's fairly small and well documented. + +[pic crate source]: https://docs.rs/crate/pic8259_simple/0.1.1/source/src/lib.rs + +To add the crate as dependency, we add the following to our project: + +[`pic8259_simple`]: https://docs.rs/pic8259_simple/0.1.1/pic8259_simple/ + +```toml +# in Cargo.toml + +[dependencies] +pic8259_simple = "0.1.1" +``` + +```rust +// in lib.rs + +extern crate pic8259_simple; +``` \ No newline at end of file diff --git a/blog/templates/second-edition/index.html b/blog/templates/second-edition/index.html index 601192e8..1b4e34c2 100644 --- a/blog/templates/second-edition/index.html +++ b/blog/templates/second-edition/index.html @@ -38,6 +38,7 @@
{{ macros::post_link(page=posts.5) }} {{ macros::post_link(page=posts.6) }} + {{ macros::post_link(page=posts.7) }}
From ee7f0cecf892348050a3adf5414ee2f636d5c509 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Fri, 27 Jul 2018 12:37:58 +0200 Subject: [PATCH 02/18] Continue post --- .../posts/08-hardware-interrupts/index.md | 158 +++++++++++++++++- 1 file changed, 151 insertions(+), 7 deletions(-) diff --git a/blog/content/second-edition/posts/08-hardware-interrupts/index.md b/blog/content/second-edition/posts/08-hardware-interrupts/index.md index 6aa55976..e794310a 100644 --- a/blog/content/second-edition/posts/08-hardware-interrupts/index.md +++ b/blog/content/second-edition/posts/08-hardware-interrupts/index.md @@ -25,7 +25,7 @@ Interrupts provide a way to notify the CPU from attached hardware devices. So in ``` ____________ _____ Timer ------------> | | | | - Keyboard ---------> | Interrupt | --------> | CPU | + Keyboard ---------> | Interrupt |---------> | CPU | Other Hardware ---> | Controller | |_____| Etc. -------------> |____________| @@ -50,7 +50,7 @@ The 8259 has 8 interrupt lines and several lines for communicating with the CPU. Real Time Clock --> | | Timer -------------> | | ACPI -------------> | | Keyboard-----------> | | _____ Available --------> | Slave |----------------------> | Master | | | -Available --------> | Interrupt | Serial Port 2 -----> | Interrupt | --> | CPU | +Available --------> | Interrupt | Serial Port 2 -----> | Interrupt |---> | CPU | Mouse ------------> | Controller | Serial Port 1 -----> | Controller | |_____| Co-Processor -----> | | Parallel Port 2/3 -> | | Primary ATA ------> | | Floppy disk -------> | | @@ -60,15 +60,15 @@ Secondary ATA ----> |____________| Parallel Port 1----> |____________| This graphic shows the typical assignment of interrupt lines. We see that most of the 15 lines have a fixed mapping, e.g. line 4 of the slave PIC is assigned to the mouse. -Each controller can be configured through two I/O ports, one “command” port and one “data” port. For the master controller these ports are `0x20` (command) and `0x21` (data). For the slave they are `0xa0` (command) and `0xa1` (data). For more information on how they can be configured see the [article on osdev.org]. +Each controller can be configured through two I/O ports, one “command” port and one “data” port. For the master controller these ports are `0x20` (command) and `0x21` (data). For the slave they are `0xa0` (command) and `0xa1` (data). For more information on how the PICs can be configured see the [article on osdev.org]. [article on osdev.org]: https://wiki.osdev.org/8259_PIC ### Implementation -The initial configuration of the PICs is not usable, because it sends interrupt vector numbers in the range 0–15 to the CPU. These numbers are already occupied by CPU exceptions, for example number 8 corresponds to a double fault. To fix this overlapping issue, we need to remap the PIC interrupts to different numbers. The actual range doesn't matter as long as it does not overlap with the exceptions, but typically the range 32–47 is chosen, because these are the first free numbers after the 32 exception slots. +The default configuration of the PICs is not usable, because it sends interrupt vector numbers in the range 0–15 to the CPU. These numbers are already occupied by CPU exceptions, for example number 8 corresponds to a double fault. To fix this overlapping issue, we need to remap the PIC interrupts to different numbers. The actual range doesn't matter as long as it does not overlap with the exceptions, but typically the range 32–47 is chosen, because these are the first free numbers after the 32 exception slots. -The configuration happens by writing special values to the command and data ports that we mentioned above. Fortunately there is already a crate called [`pic8259_simple`], so we don't need to write the initialization sequence ourselves. In case you are interested how it works, check out [its source code][pic crate source], it's fairly small and well documented. +The configuration happens by writing special values to the command and data ports of the PICs. Fortunately there is already a crate called [`pic8259_simple`], so we don't need to write the initialization sequence ourselves. In case you are interested how it works, check out [its source code][pic crate source], it's fairly small and well documented. [pic crate source]: https://docs.rs/crate/pic8259_simple/0.1.1/source/src/lib.rs @@ -84,7 +84,151 @@ pic8259_simple = "0.1.1" ``` ```rust -// in lib.rs +// in src/lib.rs extern crate pic8259_simple; -``` \ No newline at end of file +``` + +The main abstraction provided by the crate is the [`ChainedPics`] struct that represents the master/slave PIC layout we saw above. It is designed to be used in the following way: + +[`ChainedPics`]: https://docs.rs/pic8259_simple/0.1.1/pic8259_simple/struct.ChainedPics.html + +```rust +// in src/lib.rs + +pub mod interrupts; + +// in src/interrupts.rs + +use pic8259::ChainedPics; +use spin; + +pub const PIC_1_OFFSET: u8 = 32; +pub const PIC_2_OFFSET: u8 = PIC_1_OFFSET + 8; + +pub static PICS: spin::Mutex = + spin::Mutex::new(unsafe { ChainedPics::new(PIC_1_OFFSET, PIC_2_OFFSET) }); +``` + +We're setting the offsets for the pics to the range 32–47 as we noted above. By wrapping the `ChainedPics` struct in a `Mutex` we are able to get safe mutable access (through the [`lock` method][spin mutex lock]), which we need in the next step. The `ChainedPics::new` function is unsafe because wrong offsets could cause undefined behavior. + +[spin mutex lock]: https://docs.rs/spin/0.4.8/spin/struct.Mutex.html#method.lock + +We can now initialize the 8259 PIC from our `_start` function: + +```rust +// in src/main.rs + +#[cfg(not(test))] +#[no_mangle] +pub extern "C" fn _start() -> ! { + println!("Hello World{}", "!"); + + blog_os::gdt::init(); + init_idt(); + unsafe { PICS.lock().initialize() }; // new + + println!("It did not crash!"); + loop {} +} +``` + +We use the [`initialize`] function to perform the PIC initialization. Like the `ChainedPics::new` function, this function is also unsafe because it can cause undefined behavior if the PIC is misconfigured. + +[`initialize`]: https://docs.rs/pic8259_simple/0.1.1/pic8259_simple/struct.ChainedPics.html#method.initialize + +If all goes well we should continue to see the "It did not crash" message when executing `bootimage run`. + +## Enabling Interrupts + +Until now nothing happened because we did not enable interrupts in the CPU. Let's change that: + +```rust +// in src/main.rs + +#[cfg(not(test))] +#[no_mangle] +pub extern "C" fn _start() -> ! { + println!("Hello World{}", "!"); + + blog_os::gdt::init(); + init_idt(); + unsafe { PICS.lock().initialize() }; + x86_64::instructions::interrupts::enable(); // new + + println!("It did not crash!"); + loop {} +} +``` + +This function of the `x86_64` crate executes the special `sti` instruction (“set interrupts”) to enable external interrupts. When we try `bootimage run` now, we see that a double fault occurs: + +TODO screenshot + +The reason for this double fault is that the hardware timer (the [Intel 8253] to be exact) is enabled by default, so we start receiving timer interrupts as soon as we enable interrupts. Since we didn't define a handler function for it yet, our double fault handler is invoked. + +[Intel 8253]: https://en.wikipedia.org/wiki/Intel_8253 + +## Handling Timer Interrupts + +As we see from the graphic [above](#the-8259-pic), the timer uses line 0 of the master PIC. This means that it arrives at the CPU as interrupt 32 (0 + offset 32). Therefore we need to add a handler for interrupt 32 if we want to handle the timer interrupt: + +```rust +// in src/interrupts.rs + +pub const TIMER_INTERRUPT_ID: u8 = PIC_1_OFFSET; + +// in src/main.rs + +lazy_static! { + static ref IDT: InterruptDescriptorTable = { + let mut idt = InterruptDescriptorTable::new(); + idt.breakpoint.set_handler_fn(breakpoint_handler); + […] + let timer_interrupt_id = usize::from(interrupts::TIMER_INTERRUPT_ID); // new + idt[timer_interrupt_id].set_handler_fn(timer_interrupt_handler); // new + + idt + }; +} + +extern "x86-interrupt" fn timer_interrupt_handler( + _stack_frame: &mut ExceptionStackFrame) +{ + print!("."); +} +``` + +We introduce a `TIMER_INTERRUPT_ID` constant to keep things organized. Our `timer_interrupt_handler` has the same signature as our exception handlers, because the CPU reacts identically to exceptions and external interrupts (the only difference is that some exceptions push an error code). The [`InterruptDescriptorTable`] struct implements the [`IndexMut`] trait, so we can access individual entries through array indexing syntax. + +[`InterruptDescriptorTable`]: https://docs.rs/x86_64/0.2.11/x86_64/structures/idt/struct.InterruptDescriptorTable.html +[`IndexMut`]: https://doc.rust-lang.org/core/ops/trait.IndexMut.html + +In our timer interrupt handler, we print a dot to the screen. As the timer interrupt happens periodically, we expect to see a dot appearing on each timer tick. However, when we run it we see that only a single dot is printed: + +TODO screenshot + +### End of Interrupt + +The reason is that the PIC expects an explicit “end of interrupt” (EOI) signal from our interrupt handler. This signal tells the controller that the interrupt was processed and that the system is ready to receive the next interrupt. So the PIC thinks we're still busy processing the first timer interrupt and waits patiently for the EOI signal before sending the next one. + +To send the EOI, we use our static `PICS` struct again: + +```rust +// in src/main.rs + +extern "x86-interrupt" fn timer_interrupt_handler( + _stack_frame: &mut ExceptionStackFrame) +{ + print!("."); + unsafe { PICS.lock().notify_end_of_interrupt(interrupts::TIMER_INTERRUPT_ID) } +} +``` + +The `notify_end_of_interrupt` figures out wether the master or slave PIC sent the interrupt and then uses the `command` and `data` registers to send an EOI signal to respective controllers. If the slave PIC sent the interrupt both PICs need to be notified because the slave PIC is connected to an input line of the master PIC. + +We need to be careful to use the correct interrupt vector number, otherwise we could accidentally delete an important unsent interrupt or cause our system to hang. This is the reason that the function is unsafe. + +When we now execute `bootimage run` we see dots periodically appearing on the screen: + +TODO screenshot gif From fa426fc633dd8b7484d985e7706ba20cf1a024ac Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Wed, 10 Oct 2018 14:48:07 +0200 Subject: [PATCH 03/18] Minor improvements --- .../posts/08-hardware-interrupts/index.md | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/blog/content/second-edition/posts/08-hardware-interrupts/index.md b/blog/content/second-edition/posts/08-hardware-interrupts/index.md index e794310a..d68baa30 100644 --- a/blog/content/second-edition/posts/08-hardware-interrupts/index.md +++ b/blog/content/second-edition/posts/08-hardware-interrupts/index.md @@ -6,7 +6,7 @@ date = 2018-07-26 template = "second-edition/page.html" +++ -In this post we set up the programmable interrupt controller to correctly forward hardware interrupts to the CPU. This allows us to create handler functions for these, which work in almost the same way as our exception handlers. We will then learn how to configure a hardware timer so that we get periodic interrupts and also how to add keyboard support. +In this post we set up the programmable interrupt controller to correctly forward hardware interrupts to the CPU. To handle these interrups we add new entries to our interrupt descriptor table, just like we did for our exception handlers. We will learn how to get periodic timer interrupts and how to get input from the keyboard. @@ -21,6 +21,7 @@ Interrupts provide a way to notify the CPU from attached hardware devices. So in [_polling_]: https://en.wikipedia.org/wiki/Polling_(computer_science) +Connecting all hardware devices directly to the CPU is not possible. Instead, a separate _interrupt controller_ aggregates the interrupts from all devices and then notifies the CPU: ``` ____________ _____ @@ -31,9 +32,9 @@ Interrupts provide a way to notify the CPU from attached hardware devices. So in ``` -Connecting all hardware devices directly to the CPU is not possible. Instead, a separate _interrupt controller_ aggregates the interrupts from all devices and then notifies the CPU. Most interrupt controllers are programmable, which means that they support different priority levels for interrupts. For example, we could give timer interrupts a higher priority than keyboard interrupts to ensure accurate timekeeping. +Most interrupt controllers are programmable, which means that they support different priority levels for interrupts. For example, this allows to give timer interrupts a higher priority than keyboard interrupts to ensure accurate timekeeping. -Unlike exceptions, hardware interrupts occur asynchronously. This means that they are completely independent from the executed code and can occur at any time. Thus we suddenly have a form of concurrency in our kernel with all the potential concurrency-related bugs. Rust's strict ownership model helps us here because it forbids mutable global state. However, deadlocks are still possible, as we will see later in this post. +Unlike exceptions, hardware interrupts occur _asynchronously_. This means that they are completely independent from the executed code and can occur at any time. Thus we suddenly have a form of concurrency in our kernel with all the potential concurrency-related bugs. Rust's strict ownership model helps us here because it forbids mutable global state. However, deadlocks are still possible, as we will see later in this post. ## The 8259 PIC @@ -60,8 +61,9 @@ Secondary ATA ----> |____________| Parallel Port 1----> |____________| This graphic shows the typical assignment of interrupt lines. We see that most of the 15 lines have a fixed mapping, e.g. line 4 of the slave PIC is assigned to the mouse. -Each controller can be configured through two I/O ports, one “command” port and one “data” port. For the master controller these ports are `0x20` (command) and `0x21` (data). For the slave they are `0xa0` (command) and `0xa1` (data). For more information on how the PICs can be configured see the [article on osdev.org]. +Each controller can be configured through two [I/O ports], one “command” port and one “data” port. For the master controller these ports are `0x20` (command) and `0x21` (data). For the slave they are `0xa0` (command) and `0xa1` (data). For more information on how the PICs can be configured see the [article on osdev.org]. +[I/O ports]: ./second-edition/posts/05-integration-tests/index.md#port-i-o [article on osdev.org]: https://wiki.osdev.org/8259_PIC ### Implementation @@ -141,7 +143,7 @@ If all goes well we should continue to see the "It did not crash" message when e ## Enabling Interrupts -Until now nothing happened because we did not enable interrupts in the CPU. Let's change that: +Until now nothing happened because interrupts are still disabled in the CPU configuration. This means that the CPU does not listen to the interrupt controller at all, so no interrupts can reach the CPU. Let's change that: ```rust // in src/main.rs @@ -161,7 +163,7 @@ pub extern "C" fn _start() -> ! { } ``` -This function of the `x86_64` crate executes the special `sti` instruction (“set interrupts”) to enable external interrupts. When we try `bootimage run` now, we see that a double fault occurs: +The `interrupts::enable` function of the `x86_64` crate executes the special `sti` instruction (“set interrupts”) to enable external interrupts. When we try `bootimage run` now, we see that a double fault occurs: TODO screenshot @@ -176,7 +178,7 @@ As we see from the graphic [above](#the-8259-pic), the timer uses line 0 of the ```rust // in src/interrupts.rs -pub const TIMER_INTERRUPT_ID: u8 = PIC_1_OFFSET; +pub const TIMER_INTERRUPT_ID: u8 = PIC_1_OFFSET; // new // in src/main.rs @@ -204,7 +206,7 @@ We introduce a `TIMER_INTERRUPT_ID` constant to keep things organized. Our `time [`InterruptDescriptorTable`]: https://docs.rs/x86_64/0.2.11/x86_64/structures/idt/struct.InterruptDescriptorTable.html [`IndexMut`]: https://doc.rust-lang.org/core/ops/trait.IndexMut.html -In our timer interrupt handler, we print a dot to the screen. As the timer interrupt happens periodically, we expect to see a dot appearing on each timer tick. However, when we run it we see that only a single dot is printed: +In our timer interrupt handler, we print a dot to the screen. As the timer interrupt happens periodically, we would expect to see a dot appearing on each timer tick. However, when we run it we see that only a single dot is printed: TODO screenshot @@ -225,7 +227,7 @@ extern "x86-interrupt" fn timer_interrupt_handler( } ``` -The `notify_end_of_interrupt` figures out wether the master or slave PIC sent the interrupt and then uses the `command` and `data` registers to send an EOI signal to respective controllers. If the slave PIC sent the interrupt both PICs need to be notified because the slave PIC is connected to an input line of the master PIC. +The `notify_end_of_interrupt` figures out wether the master or slave PIC sent the interrupt and then uses the `command` and `data` ports to send an EOI signal to respective controllers. If the slave PIC sent the interrupt both PICs need to be notified because the slave PIC is connected to an input line of the master PIC. We need to be careful to use the correct interrupt vector number, otherwise we could accidentally delete an important unsent interrupt or cause our system to hang. This is the reason that the function is unsafe. From 9effe2b27b88002ca7c6c6fd296329cc04f2c9f8 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Wed, 10 Oct 2018 18:56:16 +0200 Subject: [PATCH 04/18] Master/slave -> primary/seondary --- .../posts/08-hardware-interrupts/index.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/blog/content/second-edition/posts/08-hardware-interrupts/index.md b/blog/content/second-edition/posts/08-hardware-interrupts/index.md index d68baa30..f8ad9aae 100644 --- a/blog/content/second-edition/posts/08-hardware-interrupts/index.md +++ b/blog/content/second-edition/posts/08-hardware-interrupts/index.md @@ -42,7 +42,7 @@ The [Intel 8259] is a programmable interrupt controller (PIC) introduced in 1976 [APIC]: https://en.wikipedia.org/wiki/Intel_APIC_Architecture -The 8259 has 8 interrupt lines and several lines for communicating with the CPU. The typical systems back then where equipped with two instances of the 8259 PIC, one acting as master and the other as slave connected to one of the masters interrupt lines: +The 8259 has 8 interrupt lines and several lines for communicating with the CPU. The typical systems back then where equipped with two instances of the 8259 PIC, one primary and one secondary PIC connected to one of the interrupt lines of the primary: [Intel 8259]: https://en.wikipedia.org/wiki/Intel_8259 @@ -50,7 +50,7 @@ The 8259 has 8 interrupt lines and several lines for communicating with the CPU. ____________ ____________ Real Time Clock --> | | Timer -------------> | | ACPI -------------> | | Keyboard-----------> | | _____ -Available --------> | Slave |----------------------> | Master | | | +Available --------> | Secondary |----------------------> | Primary | | | Available --------> | Interrupt | Serial Port 2 -----> | Interrupt |---> | CPU | Mouse ------------> | Controller | Serial Port 1 -----> | Controller | |_____| Co-Processor -----> | | Parallel Port 2/3 -> | | @@ -59,9 +59,9 @@ Secondary ATA ----> |____________| Parallel Port 1----> |____________| ``` -This graphic shows the typical assignment of interrupt lines. We see that most of the 15 lines have a fixed mapping, e.g. line 4 of the slave PIC is assigned to the mouse. +This graphic shows the typical assignment of interrupt lines. We see that most of the 15 lines have a fixed mapping, e.g. line 4 of the secondary PIC is assigned to the mouse. -Each controller can be configured through two [I/O ports], one “command” port and one “data” port. For the master controller these ports are `0x20` (command) and `0x21` (data). For the slave they are `0xa0` (command) and `0xa1` (data). For more information on how the PICs can be configured see the [article on osdev.org]. +Each controller can be configured through two [I/O ports], one “command” port and one “data” port. For the primary controller these ports are `0x20` (command) and `0x21` (data). For the secondary controller they are `0xa0` (command) and `0xa1` (data). For more information on how the PICs can be configured see the [article on osdev.org]. [I/O ports]: ./second-edition/posts/05-integration-tests/index.md#port-i-o [article on osdev.org]: https://wiki.osdev.org/8259_PIC @@ -91,7 +91,7 @@ pic8259_simple = "0.1.1" extern crate pic8259_simple; ``` -The main abstraction provided by the crate is the [`ChainedPics`] struct that represents the master/slave PIC layout we saw above. It is designed to be used in the following way: +The main abstraction provided by the crate is the [`ChainedPics`] struct that represents the primary/secondary PIC layout we saw above. It is designed to be used in the following way: [`ChainedPics`]: https://docs.rs/pic8259_simple/0.1.1/pic8259_simple/struct.ChainedPics.html @@ -173,7 +173,7 @@ The reason for this double fault is that the hardware timer (the [Intel 8253] to ## Handling Timer Interrupts -As we see from the graphic [above](#the-8259-pic), the timer uses line 0 of the master PIC. This means that it arrives at the CPU as interrupt 32 (0 + offset 32). Therefore we need to add a handler for interrupt 32 if we want to handle the timer interrupt: +As we see from the graphic [above](#the-8259-pic), the timer uses line 0 of the primary PIC. This means that it arrives at the CPU as interrupt 32 (0 + offset 32). Therefore we need to add a handler for interrupt 32 if we want to handle the timer interrupt: ```rust // in src/interrupts.rs From a619b8908d523a512736f277c5e1722871b8be50 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Wed, 10 Oct 2018 18:56:40 +0200 Subject: [PATCH 05/18] Add aside styling --- blog/static/css/main.css | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/blog/static/css/main.css b/blog/static/css/main.css index 8a3852fc..792fe109 100644 --- a/blog/static/css/main.css +++ b/blog/static/css/main.css @@ -295,3 +295,12 @@ form.subscribe input[type=submit] { padding: .25rem .5rem; cursor: pointer; } + +/* Asides */ +aside.post_aside { + font-style: italic; + padding: 0rem 1rem 0rem; + margin: .8rem 0; + border-left: .1rem solid #e5e5e5; + border-right: .1rem solid #e5e5e5; +} From 87f6e734a9391a25162d9f39874f7e7d47a1692d Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Wed, 10 Oct 2018 18:57:21 +0200 Subject: [PATCH 06/18] Finish first draft --- .../posts/08-hardware-interrupts/index.md | 196 +++++++++++++++++- 1 file changed, 195 insertions(+), 1 deletion(-) diff --git a/blog/content/second-edition/posts/08-hardware-interrupts/index.md b/blog/content/second-edition/posts/08-hardware-interrupts/index.md index f8ad9aae..69007908 100644 --- a/blog/content/second-edition/posts/08-hardware-interrupts/index.md +++ b/blog/content/second-edition/posts/08-hardware-interrupts/index.md @@ -227,10 +227,204 @@ extern "x86-interrupt" fn timer_interrupt_handler( } ``` -The `notify_end_of_interrupt` figures out wether the master or slave PIC sent the interrupt and then uses the `command` and `data` ports to send an EOI signal to respective controllers. If the slave PIC sent the interrupt both PICs need to be notified because the slave PIC is connected to an input line of the master PIC. +The `notify_end_of_interrupt` figures out wether the primary or secondary PIC sent the interrupt and then uses the `command` and `data` ports to send an EOI signal to respective controllers. If the secondary PIC sent the interrupt both PICs need to be notified because the secondary PIC is connected to an input line of the primary PIC. We need to be careful to use the correct interrupt vector number, otherwise we could accidentally delete an important unsent interrupt or cause our system to hang. This is the reason that the function is unsafe. When we now execute `bootimage run` we see dots periodically appearing on the screen: TODO screenshot gif + +### Configuring The Timer + +The hardware timer that we use is called the _Progammable Interval Timer_ or PIT for short. Like the name says, it is possible to configure the interval between two interrupts. We won't go into details here because we will switch to the [APIC timer] soon, but the OSDev wiki has an extensive article about the [configuring the PIT]. + +[APIC timer]: https://wiki.osdev.org/APIC_timer +[configuring the PIT]: https://wiki.osdev.org/Programmable_Interval_Timer + +## The `hlt` Instruction + +Until now we used a simple empty loop statement at the end of our `_start` and `panic` functions. This causes the CPU to spin endlessly and thus works as expected. But it is also very inefficient, because the CPU continues to run at full speed even though there's no work to do. You can see this problem in your task manager when you run your kernel: The QEMU process needs close to 100% CPU the whole time. + +What we really want to do is to halt the CPU until the next interrupt arrives. This allows the CPU to enter a sleep state in which it consumes much less energy. The [`hlt` instruction] does exactly that. Let's use this instruction to create an energy efficient endless loop: + +[`hlt` instruction]: https://en.wikipedia.org/wiki/HLT_(x86_instruction) + +```rust +// in src/lib.rs + +pub fn hlt_loop() -> ! { + loop { + x86_64::instructions::hlt(); + } +} +``` + +The `instructions::hlt` function is just a [thin wrapper] around the assembly instruction. It is safe because there's no way it can compromise memory safety. + +[thin wrapper]: https://github.com/rust-osdev/x86_64/blob/5e8e218381c5205f5777cb50da3ecac5d7e3b1ab/src/instructions/mod.rs#L16-L22 + +We can now use this `hlt_loop` instead of the endless loops in our `_start` and `panic` functions: + +```rust +// in src/main.rs + +#[cfg(not(test))] +#[no_mangle] +pub extern "C" fn _start() -> ! { + […] + + println!("It did not crash!"); + blog_os::hlt_loop(); // new +} + + +#[cfg(not(test))] +#[panic_handler] +fn panic(info: &PanicInfo) -> ! { + println!("{}", info); + blog_os::hlt_loop(); // new +} + +``` + +When we run our kernel now in QEMU, we see a much lower CPU usage. + +## Keyboard Input + +Now that we are able to handle interrupts from external devices we are finally able to add support for keyboard input. This will allow us to interact with our kernel for the first time. + + + +[PS/2]: https://en.wikipedia.org/wiki/PS/2_port + +Like the hardware timer, the keyboard controller is already enabled by default. So when you press a key the keyboard controller sends an interrupt to the PIC, which forwards it to the CPU. The CPU looks for a handler function in the IDT, but the corresponding entry is empty. Therefore a double fault occurs. + +So let's add a handler function for the keyboard interrupt. It's quite similar to how we defined the handler for the timer interrupt, it just uses a different interrupt number: + +```rust +// in src/interrupts.rs + +pub const KEYBOARD_INTERRUPT_ID: u8 = PIC_1_OFFSET + 1; // new + +// in src/main.rs + +lazy_static! { + static ref IDT: InterruptDescriptorTable = { + let mut idt = InterruptDescriptorTable::new(); + idt.breakpoint.set_handler_fn(breakpoint_handler); + […] + // new + let keyboard_interrupt_id = usize::from(interrupts::KEYBOARD_INTERRUPT_ID); + idt[keyboard_interrupt_id].set_handler_fn(keyboard_interrupt_handler); + + idt + }; +} + +extern "x86-interrupt" fn keyboard_interrupt_handler( + _stack_frame: &mut ExceptionStackFrame) +{ + print!("k"); + unsafe { PICS.lock().notify_end_of_interrupt(KEYBOARD_INTERRUPT_ID) } +} +``` + +As we see from the graphic [above](#the-8259-pic), the keyboard uses line 1 of the primary PIC. This means that it arrives at the CPU as interrupt 33 (1 + offset 32). We again create a `KEYBOARD_INTERRUPT_ID` constant to keep things organized. In the interrupt handler, we print a `k` and send the end of interrupt signal to the interrupt controller. + +We now see that a `k` appears whenever we press or release a key. The keyboard controller generates continuous interrupts if the key is hold down, so we see a series of `k`s on the screen. + +### Reading the Scancodes + +To find out _which_ key was pressed, we need to query the keyboard controller. We do this by reading from the from the data port of the PS/2 controller, which is the [I/O port] with number `0x60`: + +[I/O port]: ./second-edition/posts/05-integration-tests/index.md#port-i-o + +```rust +// in src/main.rs + +extern "x86-interrupt" fn keyboard_interrupt_handler( + _stack_frame: &mut ExceptionStackFrame) +{ + use x86_64::instructions::port::Port; + + let port = Port::new(0x60); + let scancode: u8 = unsafe { port.read() }; + print!("{}", scancode); + unsafe { PICS.lock().notify_end_of_interrupt(KEYBOARD_INTERRUPT_ID) } +} +``` + +We use the [`Port`] type of the `x86_64` crate to read a byte from the keyboard's data port. This byte is called the [_scancode_] and is a number that represents the key press/release. We don't do anything with the scancode yet, we just print it to the screen: + +[`Port`]: https://docs.rs/x86_64/0.2.11/x86_64/instructions/port/struct.Port.html +[_scancode_]: https://en.wikipedia.org/wiki/Scancode + +TODO image/gif + +The above image shows me slowly typing "123". We see that adjacent keys have adjacent scancodes and that pressing a key causes a different scancode than releasing it. But how do we translate the scancodes to the actual key actions exactly? + +### Interpreting the Scancodes +There are three different standards for the mapping between scancodes and keys, the so-called _scancode sets_. All three sets go back to the keyboards of early IBM computers: the [IBM XT], the [IBM 3270 PC], and the [IBM AT]. Later computers fortunatly did not continue the trend of defining new scancodes, but rather emulated the existing scancode sets and extending them. Today most keyboards can be configured to emulate any of the three sets. + +[IBM XT]: https://en.wikipedia.org/wiki/IBM_Personal_Computer_XT +[IBM 3270 PC]: https://en.wikipedia.org/wiki/IBM_3270_PC +[IBM AT]: https://en.wikipedia.org/wiki/IBM_Personal_Computer/AT + +By default, PS/2 keyboards emulate scancode set 1 ("XT"). In this set, the lower 7 bits of a scancode byte define the key, and the most significant bit defines whether it's a press ("0") or an release ("1"). Keys that were not present on the original [IBM XT] keyboard, such as the enter key on the keypad, generate two scancodes in succession: a `0xe0` escape byte and then a byte representing the key. For a list of all the set 1 scancodes and their corresponding keys, check out the [OSDev Wiki][scancode set 1]. + +[scancode set 1]: https://wiki.osdev.org/Keyboard#Scan_Code_Set_1 + +To translate the scancodes to keys, we can use a match statement: + +```rust +// in src/main.rs + +extern "x86-interrupt" fn keyboard_interrupt_handler( + _stack_frame: &mut ExceptionStackFrame) +{ + use x86_64::instructions::port::Port; + + let port = Port::new(0x60); + let scancode: u8 = unsafe { port.read() }; + + // new + let key = match scancode { + 0x02 => Some('1'), + 0x03 => Some('2'), + 0x04 => Some('3'), + 0x05 => Some('4'), + 0x06 => Some('5'), + 0x07 => Some('6'), + 0x08 => Some('7'), + 0x09 => Some('8'), + 0x0a => Some('9'), + 0x0b => Some('0'), + _ => None, + }; + if let Some(key) = key { + print!("{}", key); + } + unsafe { PICS.lock().notify_end_of_interrupt(KEYBOARD_INTERRUPT_ID) } +} +``` + +The above code just translates the numbers 0-9 and ignores all other keys. Now we can write numbers: + +TODO image + +Translating the other keys could work in the same way, probably with an enum for control keys such as escape or backspace. Such a translation function would be a good candidate for a small external crate, but I couldn't find one that works with scancode set 1. In case you'd like to write such a crate and need mentoring, just let us know, we're happy to help! + +## Summary + +In this post we learned how to enable and handle external interrupts. We learned about the 8259 PIC and its primary/secondary layout, the remapping of the interrupt numbers, and the "end of interrupt" signal. We saw that the hardware timer and the keyboard controller are active by default and start to send interrupts as soon as we enable them in the CPU. We learned about the `hlt` instruction, which halts the CPU until the next interrupt, and about the scancode sets of PS/2 keyboards. + +Now we are able to interact with our kernel and have some fundamential building blocks for creating a small shell or simple games. + +## What's next? + +As already mentioned, the 8259 APIC has been superseded by the [APIC], a controller with more capabilities and multicore support. In the next post we will explore this controller and learn how to use its integrated timer and interrupt priorities. From 7ad0ed92543c1e9f3761b205a254fe06db0aaf12 Mon Sep 17 00:00:00 2001 From: acheronfail Date: Mon, 15 Oct 2018 23:32:22 +1000 Subject: [PATCH 07/18] update source to match draft --- Cargo.lock | 53 +++++++++++++++++++++++++++++++++-------------- Cargo.toml | 1 + src/interrupts.rs | 10 +++++++++ src/lib.rs | 2 ++ src/main.rs | 20 ++++++++++++------ 5 files changed, 65 insertions(+), 21 deletions(-) create mode 100644 src/interrupts.rs diff --git a/Cargo.lock b/Cargo.lock index 860a2f14..6f513372 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -13,7 +13,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "bitflags" -version = "1.0.3" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -22,11 +22,12 @@ version = "0.2.0" dependencies = [ "array-init 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)", "bootloader_precompiled 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "lazy_static 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", - "spin 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "pic8259_simple 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "spin 0.4.9 (registry+https://github.com/rust-lang/crates.io-index)", "uart_16550 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "volatile 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", - "x86_64 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "x86_64 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -37,12 +38,18 @@ dependencies = [ "os_bootinfo 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "cpuio" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "lazy_static" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "spin 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", + "spin 0.4.9 (registry+https://github.com/rust-lang/crates.io-index)", + "version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -55,9 +62,17 @@ name = "os_bootinfo" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "pic8259_simple" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cpuio 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "spin" -version = "0.4.8" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -65,8 +80,8 @@ name = "uart_16550" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "bitflags 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", - "x86_64 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "x86_64 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -79,6 +94,11 @@ name = "ux" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "version_check" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "volatile" version = "0.2.4" @@ -86,11 +106,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "x86_64" -version = "0.2.8" +version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "bit_field 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", - "bitflags 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", + "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", "os_bootinfo 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "usize_conversions 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "ux 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", @@ -99,14 +119,17 @@ dependencies = [ [metadata] "checksum array-init 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c3cc8456d0ae81a8c76f59e384683a601548c38949a4bfcb65dd31ded5c75ff3" "checksum bit_field 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ed8765909f9009617974ab6b7d332625b320b33c326b1e9321382ef1999b5d56" -"checksum bitflags 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "d0c54bb8f454c567f21197eefcdbf5679d0bd99f2ddbe52e84c77061952e6789" +"checksum bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "228047a76f468627ca71776ecdebd732a3423081fcf5125585bcd7c49886ce12" "checksum bootloader_precompiled 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "245362094f3e7e5c801e71646a672c1fa895c033ca47473278e3d99af6300be6" -"checksum lazy_static 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e6412c5e2ad9584b0b8e979393122026cdd6d2a80b933f890dcd694ddbe73739" +"checksum cpuio 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "22b8e308ccfc5acf3b82f79c0eac444cf6114cb2ac67a230ca6c177210068daa" +"checksum lazy_static 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ca488b89a5657b0a2ecd45b95609b3e848cf1755da332a0da46e2b2b1cb371a7" "checksum nodrop 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "9a2228dca57108069a5262f2ed8bd2e82496d2e074a06d1ccc7ce1687b6ae0a2" "checksum os_bootinfo 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "66481dbeb5e773e7bd85b63cd6042c30786f834338288c5ec4f3742673db360a" -"checksum spin 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "14db77c5b914df6d6173dda9a3b3f5937bd802934fa5edaf934df06a3491e56f" +"checksum pic8259_simple 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "dc64b2fd10828da8521b6cdabe0679385d7d2a3a6d4c336b819d1fa31ba35c72" +"checksum spin 0.4.9 (registry+https://github.com/rust-lang/crates.io-index)" = "37b5646825922b96b5d7d676b5bb3458a54498e96ed7b0ce09dc43a07038fea4" "checksum uart_16550 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "269f953d8de3226f7c065c589c7b4a3e83d10a419c7c3b5e2e0f197e6acc966e" "checksum usize_conversions 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f70329e2cbe45d6c97a5112daad40c34cd9a4e18edb5a2a18fefeb584d8d25e5" "checksum ux 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "53d8df5dd8d07fedccd202de1887d94481fadaea3db70479f459e8163a1fab41" +"checksum version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd" "checksum volatile 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "54d4343a2df2d65144a874f95950754ee7b7e8594f6027aae8c7d0f4858a3fe8" -"checksum x86_64 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "51b347fd81faca2e19366605a2bb52aa2f0e1572835678e3355b66aab18650e6" +"checksum x86_64 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)" = "a7e95a2813e20d24546c2b29ecc6df55cfde30c983df69eeece0b179ca9d68ac" diff --git a/Cargo.toml b/Cargo.toml index 76e47e7b..d99803cd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,6 +9,7 @@ spin = "0.4.6" volatile = "0.2.3" uart_16550 = "0.1.0" x86_64 = "0.2.8" +pic8259_simple = "0.1.1" [dependencies.lazy_static] version = "1.0" diff --git a/src/interrupts.rs b/src/interrupts.rs new file mode 100644 index 00000000..810219df --- /dev/null +++ b/src/interrupts.rs @@ -0,0 +1,10 @@ +use pic8259_simple::ChainedPics; +use spin; + +pub const PIC_1_OFFSET: u8 = 32; +pub const PIC_2_OFFSET: u8 = PIC_1_OFFSET + 8; + +pub static PICS: spin::Mutex = + spin::Mutex::new(unsafe { ChainedPics::new(PIC_1_OFFSET, PIC_2_OFFSET) }); + +pub const TIMER_INTERRUPT_ID: u8 = PIC_1_OFFSET; diff --git a/src/lib.rs b/src/lib.rs index 86ef1bc1..351a79ca 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,6 +5,7 @@ extern crate spin; extern crate volatile; #[macro_use] extern crate lazy_static; +extern crate pic8259_simple; extern crate uart_16550; extern crate x86_64; @@ -14,6 +15,7 @@ extern crate array_init; extern crate std; pub mod gdt; +pub mod interrupts; pub mod serial; pub mod vga_buffer; diff --git a/src/main.rs b/src/main.rs index 9b56c433..444b918e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -11,6 +11,7 @@ extern crate x86_64; extern crate lazy_static; use core::panic::PanicInfo; +use blog_os::interrupts::{self, PICS}; /// This function is the entry point, since the linker looks for a function /// named `_start` by default. @@ -22,12 +23,8 @@ pub extern "C" fn _start() -> ! { blog_os::gdt::init(); init_idt(); - fn stack_overflow() { - stack_overflow(); // for each recursion, the return address is pushed - } - - // trigger a stack overflow - stack_overflow(); + unsafe { PICS.lock().initialize() }; + x86_64::instructions::interrupts::enable(); println!("It did not crash!"); loop {} @@ -54,6 +51,9 @@ lazy_static! { .set_stack_index(blog_os::gdt::DOUBLE_FAULT_IST_INDEX); } + let timer_interrupt_id = usize::from(interrupts::TIMER_INTERRUPT_ID); + idt[timer_interrupt_id].set_handler_fn(timer_interrupt_handler); + idt }; } @@ -73,3 +73,11 @@ extern "x86-interrupt" fn double_fault_handler( println!("EXCEPTION: DOUBLE FAULT\n{:#?}", stack_frame); loop {} } + +extern "x86-interrupt" fn timer_interrupt_handler(_stack_frame: &mut ExceptionStackFrame) { + print!("."); + unsafe { + PICS.lock() + .notify_end_of_interrupt(interrupts::TIMER_INTERRUPT_ID) + } +} From 5b308be891304cb9ed5a1f67836ed2c7adeb08e7 Mon Sep 17 00:00:00 2001 From: acheronfail Date: Tue, 16 Oct 2018 00:06:43 +1000 Subject: [PATCH 08/18] add screenshots and gif of hardware timer --- .../posts/08-hardware-interrupts/index.md | 8 ++++---- .../qemu-hardware-timer-dots.gif | Bin 0 -> 10563 bytes .../qemu-hardware-timer-double-fault.png | Bin 0 -> 5906 bytes .../qemu-single-dot-printed.png | Bin 0 -> 3014 bytes src/main.rs | 1 - 5 files changed, 4 insertions(+), 5 deletions(-) create mode 100644 blog/content/second-edition/posts/08-hardware-interrupts/qemu-hardware-timer-dots.gif create mode 100644 blog/content/second-edition/posts/08-hardware-interrupts/qemu-hardware-timer-double-fault.png create mode 100644 blog/content/second-edition/posts/08-hardware-interrupts/qemu-single-dot-printed.png diff --git a/blog/content/second-edition/posts/08-hardware-interrupts/index.md b/blog/content/second-edition/posts/08-hardware-interrupts/index.md index 69007908..08d4f1a7 100644 --- a/blog/content/second-edition/posts/08-hardware-interrupts/index.md +++ b/blog/content/second-edition/posts/08-hardware-interrupts/index.md @@ -102,7 +102,7 @@ pub mod interrupts; // in src/interrupts.rs -use pic8259::ChainedPics; +use pic8259_simple::ChainedPics; use spin; pub const PIC_1_OFFSET: u8 = 32; @@ -165,7 +165,7 @@ pub extern "C" fn _start() -> ! { The `interrupts::enable` function of the `x86_64` crate executes the special `sti` instruction (“set interrupts”) to enable external interrupts. When we try `bootimage run` now, we see that a double fault occurs: -TODO screenshot +![QEMU printing `EXCEPTION: DOUBLE FAULT` because of hardware timer](qemu-hardware-timer-double-fault.png) The reason for this double fault is that the hardware timer (the [Intel 8253] to be exact) is enabled by default, so we start receiving timer interrupts as soon as we enable interrupts. Since we didn't define a handler function for it yet, our double fault handler is invoked. @@ -208,7 +208,7 @@ We introduce a `TIMER_INTERRUPT_ID` constant to keep things organized. Our `time In our timer interrupt handler, we print a dot to the screen. As the timer interrupt happens periodically, we would expect to see a dot appearing on each timer tick. However, when we run it we see that only a single dot is printed: -TODO screenshot +![QEMU printing only a single dot for hardware timer](qemu-single-dot-printed.png) ### End of Interrupt @@ -233,7 +233,7 @@ We need to be careful to use the correct interrupt vector number, otherwise we c When we now execute `bootimage run` we see dots periodically appearing on the screen: -TODO screenshot gif +![QEMU printing consequtive dots showing the hardware timer](qemu-hardware-timer-dots.gif) ### Configuring The Timer diff --git a/blog/content/second-edition/posts/08-hardware-interrupts/qemu-hardware-timer-dots.gif b/blog/content/second-edition/posts/08-hardware-interrupts/qemu-hardware-timer-dots.gif new file mode 100644 index 0000000000000000000000000000000000000000..8ea34202ac9bc0b1408fe6c147ce527610529e3e GIT binary patch literal 10563 zcmZ?wbhEHbyuh@T@jC+uD5$6^sHiKe>#Az$scGqH=^JV58)@j8Y3iEko0u6{SQ%Sd zn^;(xSz4M{*_c|{m|EMKS=*Y~*qPhfS=re#FoaszIat~|TG=@;gfcLMhFUo|Svfjc zJ2+W8I@>xp+BiDfI62!oJKH-u+c>${I=R?7yV^Os+Pb*fySjsPFocGNGB~)pySTeM zxOq5xcshG}yL$P!d;7Zi_`3P{x%>Kg`1(8h#Jc#zdie+X1P1v81^Wht_yvdfhlGZP zh6aU41cgTiM?{81MukR2hsVT5#KuL&#Ye}+xcVi;BqYWpB*i8s#Uv%iC8xwErzE7N zC8uX3XJn>iWTs?hrDkQPWo4&l=VavOX6EJR7360Z6y_EdhTlRoB%sFsx!=_|Fi!iXrrWD8s5y zhX0|VtE%f8LPP(9WIzH8U@?Z!RUk32s#OfD{xkev#qj@s=&Dt<4UKh;jdhJp^-az7 z&CT^KEupLahyGs``v3nbkm;eTK<0-22V3(WVgbXdRSf@Eg{}fA0-N<8qGuJus#T$@ z{xg6L0IB%@A7sh@|KSbI%?-^h4J|E=t!+)Mtxauh%^=j?(%#=kEXHmGYF=gf?aa9ACiUSLlpP%p0%q8o!WyQtC9+S0VPiu7k$+{kEoWskr?<2ARp7d$>S*L(ZAySpnszq+91s7fo(6A3nzN+dVT-= z{`zqKRUE;mr8aUL=H|C!z1%IyGwUNu^&ytUP5wDY%;RhA?oOXyFH`p7VY_psB9Ewz zZGnryCZ7wz25VSEd-M)wTny$t^hBwr=F81?`3{?7s%NC-z8qQ7cW-&Np{KU_AGj(6_=g%|KVjG7aqkkqW-&XEE0UNQ(db%;2TS; zfybV#HGzsO8fUcCWL~d{QR6W7X0g31*rRxeV`BCcUAKa612cmUuXXkYY-=^JvssGc|=k6jG< zT72O>p`7ck%s8g_g4Nk&PRXQ$EC(F^dL6Fp-tw^9AT}arjlqf&ryDp@cjbHz5lLt~ zp}(o2uvu}#lWMtTuQy6uS5e{cgqcwcqd6eE`-6#<57?KyC07yxa<9VG9^6k=hGSK>wZ3)Q~vJf^9AjCzg{evp7-nJiskEeJ!RPP z?$_%L$Mt@{*>XMa_uC!M*ZqFC=li?g?+>u+|M_r4Jpa$f6YA^#d^%(P{?F$N?)rbf zTnW$r`}Icp`oG`ql)wM`{Xx6_zaLMg=l}cpV)^=izus(r|L^yQ3I z>;M1z^Zot*{|r0}7}z}yFiCxA;P6?%B!1!mo7IO#fu02{>K+HVqCPZ9oLRtTe&Qfs z)rV#Uo`oFl9*2ZxeQ43}S;!TB;*i*`53L403whE#4olto&}MOFAz%54!*ahqv^(%D z68PWlaYRY#V~2;&BBALgj;L9E>>#I~O}s#o=~JAr4h#Bq;f zMzcQlWcVzWx_;uA*{+Yh1wD&po_id(y7jTI;>=>X?q$EY)!LJQXzS)07oHOSQsJ zo(kLbY3hcar8?=Jr=xCtnzrN2QoZt%r{jKontp(1nL)efnIx&tGfwy{Gn#(#Oq$i_ znHPGNnJo7_n-%qW){QgE%(kCAn^*OD_5+^f7RNo$70voQ=Y`L5tLrDvmF@aG_e0Ne zo9CYAt8RUs_v6fRyYDCepRfD%c|HU03I}$t3r$jA7I65ka1uXtq0Q>cLV?~DF6v$v zyQ01^Zhcv1 zadxF&`KinEetlW)z`H7--RsICsjn+Md{+fcKXql9)z_5)y{kf&dtF@>^>tOm*;QfN zPhDMC^>uXu@9K!-Ue`9w`no2=cXibDQ`ffb`ntBDcXiBjuj{*ReO*^^c6HqMQ`h(X z`ntY>cTEDj_l-kR-!^pku1OL-#iudZPSdiYtqb5-#l0KZSw-& zwHfZ-w=T{4wq=Fy+N|)?x32B_wsk}A+MIOn+qeGT`nGMy*|mA)r*Gf;^=-&BNz6}lRJ`b3re;nZO z+t4I_<^h}akAnhz8(P$T9&$zhI3#gyL!0@ThkVsP4lD3&>~QyaBsBZS5e>hMUEyaQ ziS7Pz)Sz!;PrA=zsoOt}S)AM0SAOQP-0vU99r!j)X!m)dB>nS*hu@}2)6YCnv;KK9 zpl{QZIJarq_A^iQs(+qN;M+XoxX&}A*+0)@_-&qb{me77-9P`IE$G`k z=ebXM{k%sa>vkKzd)JeG>ZTk1kHTf^c?^Ox5w zXPuu|_F<8+?{2T~sPdkFCGYHNUpm`YTy8Sky3|tlrBCv#t95-P3*!G~gl@leVb!rM zE%COm9j8Z?E&OJ>Mm{z>arv)n9c8BT)OE917w@{d#m{U*YVMm`FMr-Tz0Pb?y>Ct$ z|LvPs^R_Kt>HEen{r9!=Zr`>nUHh)c`Nz#iX{8(P+UCZsw!XU~t$eenY+mmAs=H6` zec$$bPF~>o-FN@r+q!M>+^kgg+t(QGe_2@C`?_5@=0WMX9mmYieeA2A@u*|jmhG?4 z?#cJ(f5iFS{{0EpdHD_M{Esgxo1Qwj`_tWnHcwCN{`om&UGW>cnr9pJOqV<6 zKjPVLc4DJ%?!xo8U%puT^X-PapOW6sd=OD?eumrcYXp1Djpu3GPqpWL%9|W}KUKcs z6u(_*#P?kfpX6C?KOXnhE&cB6wEb0wKJG2uKL78dJM(r|E?rkL=kcGnx%YSP|82K> zYQOZ`53)b^3F?<^RloP??YpXr*Z-BTd%pM66V+d-YQC?SJ2PoZtJd{QU2GzyEzd@c#AV*M9kjrRBe#khi-x-9GVY^S>YG^y@x7 z^{t)G{{PpF^ZQDenZRu!Hc(qApJ6>C69W?i0|y&}Kc|hT09Xp#Osc>t#RAgRh+C=) zs}$J4$ylYp2F}4M1=h6`x70e^Qrodgf$chgTk0fksY_U;I6^@%UH`F4ft|s@h%G9?x&#^5GjfAtnV-RbZjsxD1;&j`yka5}km%+D`9c}D zb{*VOrnsf-a7(#kl>+-c0IL+(7m-+{z`ByKN`ZqV8>zaXEY9UrBu;W)@l>*1tW+LMBD1J3yhh4{N1z73ILx}32}fpz&{l>+Mu#VQ5% zMI2Tsu;0`1dl0Oq62BU-!@99r0e09FtWscIbFoT+buGgx1=h75s}xw*4y;mOUmV0L z1vZd@p`7sylNAG~!wxFG0u&B5a|m+D%qU1a)Gi=uq+?;Y$Tgi$K5a?`W`}*#($muo zl26U?+??j!EaLbmCt~6gzaCrne@`?DSDZ!du-__Nez7BbecPXvq3IXqxb6><_1?DT z=H`sct73Owdv&DMZS%Jl?P(ejyV!a2&Pa>sonM#3%q`~w?yyH*I{I>&y34x!*G*?{ z&UkcS;SBxnQ|Ij1{QUd^=We;)O`jg^j92`;c9rCc*C%@dg?mrW-17GJj^fw%_FmbN zva^hF_qE$nWwBS+zx@;k>9E(vv+S!fx_0@bw*5K@>0RI7KRDd|Uti~LRngO1x@G6( zB5ljtwj4h_-+%wUzmso$Z2SAWZhiG*>7Pq4#_^?=-EZVjTk)WY$Lz&}W&t;ihbGX7&H@K5lAX-+w%v6xP`a|% z)A(M;<9<7?my_yG?OgF>lG-;7fr-yWBbD8ly5c}LUqP<&hTgRYK$tL_0Os0oZlb*sa$oAF<(jgB4)l< zODC?(ye6N;B6fWGyxO!?FKf(%z9{#wrOtdhIX7>n#w;hH@)c8hF6~&h$a&WPT~aG1 zMn=h2Pj~Blxopq3Uw7u4*J)`j*|3jOWWfi?+mAObeP+7c?ffaNwb_f+sx^!HUPjGc zf2VA=^oDuIG{R;bUH2=c?^>I-_U78NCquU!I(I6y=GLiYYq$S+c3OJJvQ?fsvyaWI zzCOEb6SwXzu7lD!s}f&HEG>QgQv2hP9Ur~3U7j%I=&fQd|Cza&b?523(=3k8zI^oA z-f8jG--F(KI^BAaYxS;@WiK;VmPbvSbMo1}oVasyg-+hs+Pg_S;fUtpHD50In7{pU zIRMmQ4++oxN@|DQ1Kwd5&-+12hyB;9HQV3)dj04v*Y7ubxI`fJJ~&G`<(ZNCMwolRdQx37)a z9&By5MtZx}{Z(B1SII&$m+*4guIcIz4%mJ__x0zGXXlRf8=I%pTr5gIIYG1=G8(qT zGXKwtTZPZM)(BQDs}aq4vwMr?;kiA#w!goBpqYF9+X>rWUkjcry#9}+dCu*S{QbOt zcm4VR9u3PA+Zk~4;?1W2*EMRly?%Q1a&*1!+}*#we|UV_-0OqLh2Z5i&&>Z7{CD~@ z;bHmgh8)Y}cz*^fHwAxAwdeDj*^ML?y78)oEOZnXo3gO+=iloKouu1-ENoRyidfX9 zUc@o6LwnhYMeVxBI3D*X?8;c&WBSNrQJ>nUjz#@imv$_kU@P=<@kGaG9Z$Nw%q*8o zc6F*;GR4y_Gj&S1pXZ_+F~OVnQ)OP3$9FR6u9%Y>S2S~a<3isE$-uOE?=seR^CgmNW%or@hWxZaz-Kzm^?LmQHtjbX zj)-Ny*?2;2^_xv+%wE6Qe8Elo?UpNH*>AVrNL&4O+nuu4Z?`{a(|)(($+YZu#d49M zS9ZPHHv8S~SApv5i%*@cmfQP!Blr5fUmvZ`-}7%5_lLbK-=homF@5g-u#eHW=EHt2 z=Q|$`u!Zk2I4bTw=fe>x>zs{8`155p9e>^1V|YTodydgbjr21gPpg&JY&xZPoagf? zrmjPBOaP4l1RSK-j39A&iI`n41+p+X71BcHktZoMD zVqi#TJj3J#ZhDUP>qh%^!?j;$)?H>d<+JPDV_9W$wCA1;xmD7q`~6D%rrf~0>9>0) z-Say7Kl0n12J>EX&zf@1;D;VRdCMR4?CwcDsPMP;#iH(HnJ146#NT{d?4;LSId{@} zZmUPV)91Z>Ffo2#*_zhbK2IL6TraimRhIwnUC+19UblPuis~bq-mf{M=Pvk2&kx&A%kiU-$7!bf4{qhpW%; zJ?@Zw|KE=<<+C%k2A#{<_iuZf_J%e8cS|0ha(I1R=n>EJS_*3g+~?+48hCnUE3nAB zIdXn`-z3;`rOYg1DW9D{v*?ut?CKc@d5S(XtH~&GcuO1-o)plck+YC1Si)KCmOz{S zlY|Wh%be5`Po}?;dcgW$e1@&UqZRGeb7W7{L>yrex~Q!eqsX^Z!d2>7L7UN-a1k|!&U8Lj%*o7AJs@!Fm46S2k9vc1y&+)Lk)w(0%M z=h^d(W^(#w>P@cPx%!#O#+PR@@{DKS+oNW&*7Iz^w8?YsTv=wZyYpOO-Q>CNL!Mhc z&OBFg?eg5$QRHwR6w8Jm&IJXnNIG%E_9cDS<=qM0iLx17q{3uJ>Yr(d#49n#)CRNgvxmA zT^w*hPI4Cq>{L(}2eYCB8yJGSV+88-VCn*UFb#iy2y7|IogT0*?42I46!uvyurElS z)dCwx@~jrv@7TLRU;{zjAj}X0yAb=V7FZYdjuAMufI3EmQVaHO5ZGkw-5{`guy=#N ay0CYHz`C$^gTT75cY~Nf6JD6zAZq|nBMj33 literal 0 HcmV?d00001 diff --git a/blog/content/second-edition/posts/08-hardware-interrupts/qemu-hardware-timer-double-fault.png b/blog/content/second-edition/posts/08-hardware-interrupts/qemu-hardware-timer-double-fault.png new file mode 100644 index 0000000000000000000000000000000000000000..7d02b13ad15e0be4bac18f680a4d26f57819c290 GIT binary patch literal 5906 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 literal 0 HcmV?d00001 diff --git a/blog/content/second-edition/posts/08-hardware-interrupts/qemu-single-dot-printed.png b/blog/content/second-edition/posts/08-hardware-interrupts/qemu-single-dot-printed.png new file mode 100644 index 0000000000000000000000000000000000000000..cfdc06cc9812fc6138920a04487bc163111392c0 GIT binary patch literal 3014 zcmeAS@N?(olHy`uVBq!ia0y~yV7kD-z_^uziGhJ3{z=Gw1_lP^VkgfK4i1jn5B(o7 zFfa&|xJHx&=ckpFCl;kL1ZR|%l%y6VH(lUlVBk*jba4!+xb^mKWzUqSvg{wy)A*+y zzT0|W(FUiVh(m45vi%f(eSC+NoB>hF8>SU*;C?_YHspHL+91y?MpT)2Hw9Q;2J36`ihkyMNzE#Xwt?#ckWd9`ITPXws&{= z^XE^WmS}2NEM2jq-1GjeTg<$@y^Giw3T7sxA*t&uMU6D%+B{@&b9p}V&?-z7#1k22zAb>(3y9C>48)ch6T+ofm7z+ zSC#8NeuatQg~i4S8@Z+fj0`0OPYP^V8D0bgic}sDXSnc?g^$7E0Rw~8{utGU$zgSI z_e{gMZeLVZ$$8*x__3nSUczkmHoM};sk6QBY7G1vL+AUxG?x(nj zJPY50B>^!pTYfxbeVUqj_3BmmHGem3so(E?Jk}`f%nHlm=X-y@|Gy;c_V)b$`|s77 zo9--o`|HZ{Zas<5NAl<0+Etoe^y$@VwN+XQ1q|&oe(gQ^G&R+`?*H-S(`@E?s3dK? zu{2qK=jU&d<^K|nnNH4Mv3j-lx@~JWeVSMOKKtKS+v;y$UR+Fm)S@l_`=`D$M>?OJ z$)hFEVUTYTK}@ztmA_v-7<-Kn%Lf5)XLA}V%ncJ=uWiO$DsN^{zDB@Q#H zFOIXV?vpn!JABO7|KGRrP8X#X0oRM&`ug=hr;0A0TlQ+kMdjN$3wNdco}IsTXU?A; zhYkfhafHX47QVl?S37*~vTX%Uzn)w^>wMm>`r~2luI}!x?k>5=9SJ`+ER5FDGRi+F z$!8%sbt-Sq(WLzSpU>4U2u#zf zE<3YgVt7p5%~e;|_eJqlfmj8C23o_DwE z_!~%IT??N5BX zj59p$;{N*o0*5xG&;NbwX?%FJg<@!E=>4+aYf_$ZDCVqQGpF!4f3Wio7QO|kC!+)X z-bPQFWn6JV^6&4dZh=ZI0kN@@XZ=69eD?a;MakX0E`eK0ZpOcxSNnWvvVPpotkTrb zs|US>J6+=LDgHnFRy?>0Vp{%hB-UAumj)!vohvyjZaw@0%nR7dDXXU#&H^Y5@m2o8AZR?fXwmIvo^zHID9EyKlo^_`?so5& z`FZR0`g%DLK3S7r2XB>nPq(cuux7W{c$45g@898{&ugDfo4u@0y87e0yVd(lQVOoG zi=AKp@TgjNT!UZYhXuxFdA};Iz1clU-E{f+Ia@y*WM`}081sPPR>0=k-&5Y-ulc+6 zzN}%*jSUN(x98vgcpwn6#yfRZaVIKkw$x33blbtv=-Y`D-Qf=}})$qK+ zzb4(cR{vDnnytL~#t)yew;#T9Gx*7UJnW^twtUy|`uaI_+|kjqxRh4%GdLV)<6~&> zd9p12&J&A&9wE6^9-Zg;Pqxm##zfktVpHt? zsdXK0S^ErS7$)qzBj(Q|Gr#@!&E9J{*MG6KFgdWZ^(kLXl;57h!C7S+KFgrSqkFK1TK}m4gbYve6z>od@^t5dc)2PhLWs}X4czutoEqM7PRGV z`?>bZeo>X6^{+HE^l=I-0F^yI0N z-w&wnpIX_>aN*^VsSA`hd3^pXmiejW`gtaWf`TcRJgbe}vlZDGtjZr)1o9u*e0%-j z^~VnCrcAV)BhI+MST ! { blog_os::gdt::init(); init_idt(); - unsafe { PICS.lock().initialize() }; x86_64::instructions::interrupts::enable(); From 5243ef874d184564fe28e6bf8fdcc732fe56126c Mon Sep 17 00:00:00 2001 From: acheronfail Date: Tue, 16 Oct 2018 07:57:36 +1000 Subject: [PATCH 09/18] add gifs for keyboard and fix source to compile --- .../posts/08-hardware-interrupts/index.md | 16 ++++---- .../qemu-printing-numbers.gif | Bin 0 -> 5033 bytes .../qemu-printing-scancodes.gif | Bin 0 -> 4686 bytes src/interrupts.rs | 1 + src/lib.rs | 6 +++ src/main.rs | 36 ++++++++++++++++-- 6 files changed, 48 insertions(+), 11 deletions(-) create mode 100644 blog/content/second-edition/posts/08-hardware-interrupts/qemu-printing-numbers.gif create mode 100644 blog/content/second-edition/posts/08-hardware-interrupts/qemu-printing-scancodes.gif diff --git a/blog/content/second-edition/posts/08-hardware-interrupts/index.md b/blog/content/second-edition/posts/08-hardware-interrupts/index.md index 08d4f1a7..8387de66 100644 --- a/blog/content/second-edition/posts/08-hardware-interrupts/index.md +++ b/blog/content/second-edition/posts/08-hardware-interrupts/index.md @@ -330,13 +330,13 @@ extern "x86-interrupt" fn keyboard_interrupt_handler( _stack_frame: &mut ExceptionStackFrame) { print!("k"); - unsafe { PICS.lock().notify_end_of_interrupt(KEYBOARD_INTERRUPT_ID) } + unsafe { PICS.lock().notify_end_of_interrupt(interrupts::KEYBOARD_INTERRUPT_ID) } } ``` As we see from the graphic [above](#the-8259-pic), the keyboard uses line 1 of the primary PIC. This means that it arrives at the CPU as interrupt 33 (1 + offset 32). We again create a `KEYBOARD_INTERRUPT_ID` constant to keep things organized. In the interrupt handler, we print a `k` and send the end of interrupt signal to the interrupt controller. -We now see that a `k` appears whenever we press or release a key. The keyboard controller generates continuous interrupts if the key is hold down, so we see a series of `k`s on the screen. +We now see that a `k` appears on the screen when we press a key. However, this only works for the first key we press, even if we continue to press keys no more `k`s appear on the screen. This is because the keyboard won't send another interrupt if we haven't read the pressed from its data port. ### Reading the Scancodes @@ -355,7 +355,7 @@ extern "x86-interrupt" fn keyboard_interrupt_handler( let port = Port::new(0x60); let scancode: u8 = unsafe { port.read() }; print!("{}", scancode); - unsafe { PICS.lock().notify_end_of_interrupt(KEYBOARD_INTERRUPT_ID) } + unsafe { PICS.lock().notify_end_of_interrupt(interrupts::KEYBOARD_INTERRUPT_ID) } } ``` @@ -364,12 +364,12 @@ We use the [`Port`] type of the `x86_64` crate to read a byte from the keyboard' [`Port`]: https://docs.rs/x86_64/0.2.11/x86_64/instructions/port/struct.Port.html [_scancode_]: https://en.wikipedia.org/wiki/Scancode -TODO image/gif +![QEMU printing scancodes to the screen when keys are pressed](qemu-printing-scancodes.gif) The above image shows me slowly typing "123". We see that adjacent keys have adjacent scancodes and that pressing a key causes a different scancode than releasing it. But how do we translate the scancodes to the actual key actions exactly? ### Interpreting the Scancodes -There are three different standards for the mapping between scancodes and keys, the so-called _scancode sets_. All three sets go back to the keyboards of early IBM computers: the [IBM XT], the [IBM 3270 PC], and the [IBM AT]. Later computers fortunatly did not continue the trend of defining new scancodes, but rather emulated the existing scancode sets and extending them. Today most keyboards can be configured to emulate any of the three sets. +There are three different standards for the mapping between scancodes and keys, the so-called _scancode sets_. All three sets go back to the keyboards of early IBM computers: the [IBM XT], the [IBM 3270 PC], and the [IBM AT]. Later computers fortunately did not continue the trend of defining new scancodes, but rather emulated the existing scancode sets and extending them. Today most keyboards can be configured to emulate any of the three sets. [IBM XT]: https://en.wikipedia.org/wiki/IBM_Personal_Computer_XT [IBM 3270 PC]: https://en.wikipedia.org/wiki/IBM_3270_PC @@ -409,13 +409,13 @@ extern "x86-interrupt" fn keyboard_interrupt_handler( if let Some(key) = key { print!("{}", key); } - unsafe { PICS.lock().notify_end_of_interrupt(KEYBOARD_INTERRUPT_ID) } + unsafe { PICS.lock().notify_end_of_interrupt(interrupts::KEYBOARD_INTERRUPT_ID) } } ``` The above code just translates the numbers 0-9 and ignores all other keys. Now we can write numbers: -TODO image +![QEMU printing numbers to the screen](qemu-printing-numbers.gif) Translating the other keys could work in the same way, probably with an enum for control keys such as escape or backspace. Such a translation function would be a good candidate for a small external crate, but I couldn't find one that works with scancode set 1. In case you'd like to write such a crate and need mentoring, just let us know, we're happy to help! @@ -423,7 +423,7 @@ Translating the other keys could work in the same way, probably with an enum for In this post we learned how to enable and handle external interrupts. We learned about the 8259 PIC and its primary/secondary layout, the remapping of the interrupt numbers, and the "end of interrupt" signal. We saw that the hardware timer and the keyboard controller are active by default and start to send interrupts as soon as we enable them in the CPU. We learned about the `hlt` instruction, which halts the CPU until the next interrupt, and about the scancode sets of PS/2 keyboards. -Now we are able to interact with our kernel and have some fundamential building blocks for creating a small shell or simple games. +Now we are able to interact with our kernel and have some fundamental building blocks for creating a small shell or simple games. ## What's next? diff --git a/blog/content/second-edition/posts/08-hardware-interrupts/qemu-printing-numbers.gif b/blog/content/second-edition/posts/08-hardware-interrupts/qemu-printing-numbers.gif new file mode 100644 index 0000000000000000000000000000000000000000..3985bd14fb27b73121b0b99ee52605750f610c2b GIT binary patch literal 5033 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 Q;PjV-Aq-ZXjUsFf0B_~zd;kCd literal 0 HcmV?d00001 diff --git a/src/interrupts.rs b/src/interrupts.rs index 810219df..3c3332d3 100644 --- a/src/interrupts.rs +++ b/src/interrupts.rs @@ -8,3 +8,4 @@ pub static PICS: spin::Mutex = spin::Mutex::new(unsafe { ChainedPics::new(PIC_1_OFFSET, PIC_2_OFFSET) }); pub const TIMER_INTERRUPT_ID: u8 = PIC_1_OFFSET; +pub const KEYBOARD_INTERRUPT_ID: u8 = PIC_1_OFFSET + 1; diff --git a/src/lib.rs b/src/lib.rs index 351a79ca..505bc4d4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -25,3 +25,9 @@ pub unsafe fn exit_qemu() { let mut port = Port::::new(0xf4); port.write(0); } + +pub fn hlt_loop() -> ! { + loop { + x86_64::instructions::hlt(); + } +} diff --git a/src/main.rs b/src/main.rs index 501b14bf..f4f22d86 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,8 +10,8 @@ extern crate x86_64; #[macro_use] extern crate lazy_static; -use core::panic::PanicInfo; use blog_os::interrupts::{self, PICS}; +use core::panic::PanicInfo; /// This function is the entry point, since the linker looks for a function /// named `_start` by default. @@ -26,7 +26,7 @@ pub extern "C" fn _start() -> ! { x86_64::instructions::interrupts::enable(); println!("It did not crash!"); - loop {} + blog_os::hlt_loop(); } /// This function is called on panic. @@ -35,7 +35,7 @@ pub extern "C" fn _start() -> ! { #[no_mangle] pub fn panic(info: &PanicInfo) -> ! { println!("{}", info); - loop {} + blog_os::hlt_loop(); } use x86_64::structures::idt::{ExceptionStackFrame, InterruptDescriptorTable}; @@ -52,6 +52,8 @@ lazy_static! { let timer_interrupt_id = usize::from(interrupts::TIMER_INTERRUPT_ID); idt[timer_interrupt_id].set_handler_fn(timer_interrupt_handler); + let keyboard_interrupt_id = usize::from(interrupts::KEYBOARD_INTERRUPT_ID); + idt[keyboard_interrupt_id].set_handler_fn(keyboard_interrupt_handler); idt }; @@ -80,3 +82,31 @@ extern "x86-interrupt" fn timer_interrupt_handler(_stack_frame: &mut ExceptionSt .notify_end_of_interrupt(interrupts::TIMER_INTERRUPT_ID) } } + +extern "x86-interrupt" fn keyboard_interrupt_handler(_stack_frame: &mut ExceptionStackFrame) { + use x86_64::instructions::port::Port; + + let port = Port::new(0x60); + let scancode: u8 = unsafe { port.read() }; + + let key = match scancode { + 0x02 => Some('1'), + 0x03 => Some('2'), + 0x04 => Some('3'), + 0x05 => Some('4'), + 0x06 => Some('5'), + 0x07 => Some('6'), + 0x08 => Some('7'), + 0x09 => Some('8'), + 0x0a => Some('9'), + 0x0b => Some('0'), + _ => None, + }; + if let Some(key) = key { + print!("{}", key); + } + unsafe { + PICS.lock() + .notify_end_of_interrupt(interrupts::KEYBOARD_INTERRUPT_ID) + } +} From ac9582bd8b566b29c90328a03b35505ce1921a41 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Wed, 17 Oct 2018 14:45:13 +0200 Subject: [PATCH 10/18] Minor improvements --- .../posts/08-hardware-interrupts/index.md | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/blog/content/second-edition/posts/08-hardware-interrupts/index.md b/blog/content/second-edition/posts/08-hardware-interrupts/index.md index 8387de66..2c1feaf3 100644 --- a/blog/content/second-edition/posts/08-hardware-interrupts/index.md +++ b/blog/content/second-edition/posts/08-hardware-interrupts/index.md @@ -17,7 +17,7 @@ This blog is openly developed on [Github]. If you have any problems or questions ## Overview -Interrupts provide a way to notify the CPU from attached hardware devices. So instead of letting the kernel periodically check the keyboard for new characters (a process called [_polling_]), the keyboard can notify the kernel of each keypress. This is much more efficient because the kernel only needs to act when something happened. It also allows faster reaction times, because the kernel can react immediately and not only at the next poll. +Interrupts provide a way to notify the CPU from attached hardware devices. So instead of letting the kernel periodically check the keyboard for new characters (a process called [_polling_]), the keyboard can notify the kernel of each keypress. This is much more efficient because the kernel only needs to act when something happened. It also allows faster reaction times, since the kernel can react immediately and not only at the next poll. [_polling_]: https://en.wikipedia.org/wiki/Polling_(computer_science) @@ -336,7 +336,7 @@ extern "x86-interrupt" fn keyboard_interrupt_handler( As we see from the graphic [above](#the-8259-pic), the keyboard uses line 1 of the primary PIC. This means that it arrives at the CPU as interrupt 33 (1 + offset 32). We again create a `KEYBOARD_INTERRUPT_ID` constant to keep things organized. In the interrupt handler, we print a `k` and send the end of interrupt signal to the interrupt controller. -We now see that a `k` appears on the screen when we press a key. However, this only works for the first key we press, even if we continue to press keys no more `k`s appear on the screen. This is because the keyboard won't send another interrupt if we haven't read the pressed from its data port. +We now see that a `k` appears on the screen when we press a key. However, this only works for the first key we press, even if we continue to press keys no more `k`s appear on the screen. This is because the keyboard controller won't send another interrupt until we have read the so-called _scancode_ of the pressed key. ### Reading the Scancodes @@ -369,13 +369,13 @@ We use the [`Port`] type of the `x86_64` crate to read a byte from the keyboard' The above image shows me slowly typing "123". We see that adjacent keys have adjacent scancodes and that pressing a key causes a different scancode than releasing it. But how do we translate the scancodes to the actual key actions exactly? ### Interpreting the Scancodes -There are three different standards for the mapping between scancodes and keys, the so-called _scancode sets_. All three sets go back to the keyboards of early IBM computers: the [IBM XT], the [IBM 3270 PC], and the [IBM AT]. Later computers fortunately did not continue the trend of defining new scancodes, but rather emulated the existing scancode sets and extending them. Today most keyboards can be configured to emulate any of the three sets. +There are three different standards for the mapping between scancodes and keys, the so-called _scancode sets_. All three go back to the keyboards of early IBM computers: the [IBM XT], the [IBM 3270 PC], and the [IBM AT]. Later computers fortunately did not continue the trend of defining new scancode sets, but rather emulated the existing sets and extended them. Today most keyboards can be configured to emulate any of the three sets. [IBM XT]: https://en.wikipedia.org/wiki/IBM_Personal_Computer_XT [IBM 3270 PC]: https://en.wikipedia.org/wiki/IBM_3270_PC [IBM AT]: https://en.wikipedia.org/wiki/IBM_Personal_Computer/AT -By default, PS/2 keyboards emulate scancode set 1 ("XT"). In this set, the lower 7 bits of a scancode byte define the key, and the most significant bit defines whether it's a press ("0") or an release ("1"). Keys that were not present on the original [IBM XT] keyboard, such as the enter key on the keypad, generate two scancodes in succession: a `0xe0` escape byte and then a byte representing the key. For a list of all the set 1 scancodes and their corresponding keys, check out the [OSDev Wiki][scancode set 1]. +By default, PS/2 keyboards emulate scancode set 1 ("XT"). In this set, the lower 7 bits of a scancode byte define the key, and the most significant bit defines whether it's a press ("0") or a release ("1"). Keys that were not present on the original [IBM XT] keyboard, such as the enter key on the keypad, generate two scancodes in succession: a `0xe0` escape byte and then a byte representing the key. For a list of all set 1 scancodes and their corresponding keys, check out the [OSDev Wiki][scancode set 1]. [scancode set 1]: https://wiki.osdev.org/Keyboard#Scan_Code_Set_1 @@ -413,12 +413,18 @@ extern "x86-interrupt" fn keyboard_interrupt_handler( } ``` -The above code just translates the numbers 0-9 and ignores all other keys. Now we can write numbers: +The above code just translates keypresses of the number keys 0-9 and ignores all other keys. Now we can write numbers: ![QEMU printing numbers to the screen](qemu-printing-numbers.gif) Translating the other keys could work in the same way, probably with an enum for control keys such as escape or backspace. Such a translation function would be a good candidate for a small external crate, but I couldn't find one that works with scancode set 1. In case you'd like to write such a crate and need mentoring, just let us know, we're happy to help! +### Configuring The Keyboard + +It's possible to configure some aspects of a PS/2 keyboard, for example which scancode set it should use. We won't cover it here because this post is already long enough, but the OSDev Wiki has an overview of possible [configuration commands]. + +[configuration commands]: https://wiki.osdev.org/PS/2_Keyboard#Commands + ## Summary In this post we learned how to enable and handle external interrupts. We learned about the 8259 PIC and its primary/secondary layout, the remapping of the interrupt numbers, and the "end of interrupt" signal. We saw that the hardware timer and the keyboard controller are active by default and start to send interrupts as soon as we enable them in the CPU. We learned about the `hlt` instruction, which halts the CPU until the next interrupt, and about the scancode sets of PS/2 keyboards. @@ -427,4 +433,4 @@ Now we are able to interact with our kernel and have some fundamental building b ## What's next? -As already mentioned, the 8259 APIC has been superseded by the [APIC], a controller with more capabilities and multicore support. In the next post we will explore this controller and learn how to use its integrated timer and interrupt priorities. +As already mentioned, the 8259 APIC has been superseded by the [APIC], a controller with more capabilities and multicore support. In the next post we will explore this controller and learn how to use its integrated timer and how to set interrupt priorities. From 68ffc3cd5985cf6c737195e9443ed2c8a764bf32 Mon Sep 17 00:00:00 2001 From: acheronfail Date: Thu, 18 Oct 2018 18:08:17 +1100 Subject: [PATCH 11/18] refactor interrupts (#476) --- .../posts/08-hardware-interrupts/index.md | 46 ++++++----- src/interrupts.rs | 78 ++++++++++++++++++ src/lib.rs | 4 +- src/main.rs | 79 +------------------ 4 files changed, 111 insertions(+), 96 deletions(-) diff --git a/blog/content/second-edition/posts/08-hardware-interrupts/index.md b/blog/content/second-edition/posts/08-hardware-interrupts/index.md index 2c1feaf3..1b3442de 100644 --- a/blog/content/second-edition/posts/08-hardware-interrupts/index.md +++ b/blog/content/second-edition/posts/08-hardware-interrupts/index.md @@ -96,10 +96,6 @@ The main abstraction provided by the crate is the [`ChainedPics`] struct that re [`ChainedPics`]: https://docs.rs/pic8259_simple/0.1.1/pic8259_simple/struct.ChainedPics.html ```rust -// in src/lib.rs - -pub mod interrupts; - // in src/interrupts.rs use pic8259_simple::ChainedPics; @@ -127,7 +123,7 @@ pub extern "C" fn _start() -> ! { println!("Hello World{}", "!"); blog_os::gdt::init(); - init_idt(); + blog_os::interrupts::init_idt(); unsafe { PICS.lock().initialize() }; // new println!("It did not crash!"); @@ -154,7 +150,7 @@ pub extern "C" fn _start() -> ! { println!("Hello World{}", "!"); blog_os::gdt::init(); - init_idt(); + blog_os::interrupts::init_idt(); unsafe { PICS.lock().initialize() }; x86_64::instructions::interrupts::enable(); // new @@ -180,14 +176,14 @@ As we see from the graphic [above](#the-8259-pic), the timer uses line 0 of the pub const TIMER_INTERRUPT_ID: u8 = PIC_1_OFFSET; // new -// in src/main.rs +[…] lazy_static! { static ref IDT: InterruptDescriptorTable = { let mut idt = InterruptDescriptorTable::new(); idt.breakpoint.set_handler_fn(breakpoint_handler); […] - let timer_interrupt_id = usize::from(interrupts::TIMER_INTERRUPT_ID); // new + let timer_interrupt_id = usize::from(TIMER_INTERRUPT_ID); // new idt[timer_interrupt_id].set_handler_fn(timer_interrupt_handler); // new idt @@ -217,13 +213,13 @@ The reason is that the PIC expects an explicit “end of interrupt” (EOI) sign To send the EOI, we use our static `PICS` struct again: ```rust -// in src/main.rs +// in src/interrupts.rs extern "x86-interrupt" fn timer_interrupt_handler( _stack_frame: &mut ExceptionStackFrame) { print!("."); - unsafe { PICS.lock().notify_end_of_interrupt(interrupts::TIMER_INTERRUPT_ID) } + unsafe { PICS.lock().notify_end_of_interrupt(TIMER_INTERRUPT_ID) } } ``` @@ -288,6 +284,22 @@ fn panic(info: &PanicInfo) -> ! { ``` +We can also use `hlt_loop` in our double fault exception handler as well: + +```rust +// in src/interrupts.rs + +use hlt_loop; // new + +extern "x86-interrupt" fn double_fault_handler( + stack_frame: &mut ExceptionStackFrame, + _error_code: u64, +) { + println!("EXCEPTION: DOUBLE FAULT\n{:#?}", stack_frame); + hlt_loop(); // new +} +``` + When we run our kernel now in QEMU, we see a much lower CPU usage. ## Keyboard Input @@ -311,15 +323,13 @@ So let's add a handler function for the keyboard interrupt. It's quite similar t pub const KEYBOARD_INTERRUPT_ID: u8 = PIC_1_OFFSET + 1; // new -// in src/main.rs - lazy_static! { static ref IDT: InterruptDescriptorTable = { let mut idt = InterruptDescriptorTable::new(); idt.breakpoint.set_handler_fn(breakpoint_handler); […] // new - let keyboard_interrupt_id = usize::from(interrupts::KEYBOARD_INTERRUPT_ID); + let keyboard_interrupt_id = usize::from(KEYBOARD_INTERRUPT_ID); idt[keyboard_interrupt_id].set_handler_fn(keyboard_interrupt_handler); idt @@ -330,7 +340,7 @@ extern "x86-interrupt" fn keyboard_interrupt_handler( _stack_frame: &mut ExceptionStackFrame) { print!("k"); - unsafe { PICS.lock().notify_end_of_interrupt(interrupts::KEYBOARD_INTERRUPT_ID) } + unsafe { PICS.lock().notify_end_of_interrupt(KEYBOARD_INTERRUPT_ID) } } ``` @@ -345,7 +355,7 @@ To find out _which_ key was pressed, we need to query the keyboard controller. W [I/O port]: ./second-edition/posts/05-integration-tests/index.md#port-i-o ```rust -// in src/main.rs +// in src/interrupts.rs extern "x86-interrupt" fn keyboard_interrupt_handler( _stack_frame: &mut ExceptionStackFrame) @@ -355,7 +365,7 @@ extern "x86-interrupt" fn keyboard_interrupt_handler( let port = Port::new(0x60); let scancode: u8 = unsafe { port.read() }; print!("{}", scancode); - unsafe { PICS.lock().notify_end_of_interrupt(interrupts::KEYBOARD_INTERRUPT_ID) } + unsafe { PICS.lock().notify_end_of_interrupt(KEYBOARD_INTERRUPT_ID) } } ``` @@ -382,7 +392,7 @@ By default, PS/2 keyboards emulate scancode set 1 ("XT"). In this set, the lower To translate the scancodes to keys, we can use a match statement: ```rust -// in src/main.rs +// in src/interrupts.rs extern "x86-interrupt" fn keyboard_interrupt_handler( _stack_frame: &mut ExceptionStackFrame) @@ -409,7 +419,7 @@ extern "x86-interrupt" fn keyboard_interrupt_handler( if let Some(key) = key { print!("{}", key); } - unsafe { PICS.lock().notify_end_of_interrupt(interrupts::KEYBOARD_INTERRUPT_ID) } + unsafe { PICS.lock().notify_end_of_interrupt(KEYBOARD_INTERRUPT_ID) } } ``` diff --git a/src/interrupts.rs b/src/interrupts.rs index 3c3332d3..ce0898f3 100644 --- a/src/interrupts.rs +++ b/src/interrupts.rs @@ -9,3 +9,81 @@ pub static PICS: spin::Mutex = pub const TIMER_INTERRUPT_ID: u8 = PIC_1_OFFSET; pub const KEYBOARD_INTERRUPT_ID: u8 = PIC_1_OFFSET + 1; + +use x86_64::structures::idt::{ExceptionStackFrame, InterruptDescriptorTable}; +use gdt; + +lazy_static! { + static ref IDT: InterruptDescriptorTable = { + let mut idt = InterruptDescriptorTable::new(); + idt.breakpoint.set_handler_fn(breakpoint_handler); + unsafe { + idt.double_fault + .set_handler_fn(double_fault_handler) + .set_stack_index(gdt::DOUBLE_FAULT_IST_INDEX); + } + + let timer_interrupt_id = usize::from(TIMER_INTERRUPT_ID); + idt[timer_interrupt_id].set_handler_fn(timer_interrupt_handler); + let keyboard_interrupt_id = usize::from(KEYBOARD_INTERRUPT_ID); + idt[keyboard_interrupt_id].set_handler_fn(keyboard_interrupt_handler); + + idt + }; +} + +pub fn init_idt() { + IDT.load(); +} + +use hlt_loop; + +extern "x86-interrupt" fn breakpoint_handler(stack_frame: &mut ExceptionStackFrame) { + println!("EXCEPTION: BREAKPOINT\n{:#?}", stack_frame); +} + +extern "x86-interrupt" fn double_fault_handler( + stack_frame: &mut ExceptionStackFrame, + _error_code: u64, +) { + println!("EXCEPTION: DOUBLE FAULT\n{:#?}", stack_frame); + hlt_loop(); +} + +extern "x86-interrupt" fn timer_interrupt_handler(_stack_frame: &mut ExceptionStackFrame) { + print!("."); + unsafe { + PICS.lock() + .notify_end_of_interrupt(TIMER_INTERRUPT_ID) + } +} + +extern "x86-interrupt" fn keyboard_interrupt_handler(_stack_frame: &mut ExceptionStackFrame) { + use x86_64::instructions::port::Port; + + let port = Port::new(0x60); + + let scancode: u8 = unsafe { port.read() }; + let key = match scancode { + 0x02 => Some('1'), + 0x03 => Some('2'), + 0x04 => Some('3'), + 0x05 => Some('4'), + 0x06 => Some('5'), + 0x07 => Some('6'), + 0x08 => Some('7'), + 0x09 => Some('8'), + 0x0a => Some('9'), + 0x0b => Some('0'), + _ => None, + }; + + if let Some(key) = key { + print!("{}", key); + } + + unsafe { + PICS.lock() + .notify_end_of_interrupt(KEYBOARD_INTERRUPT_ID) + } +} diff --git a/src/lib.rs b/src/lib.rs index 505bc4d4..79c71ca5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,5 @@ #![no_std] // don't link the Rust standard library +#![feature(abi_x86_interrupt)] extern crate bootloader_precompiled; extern crate spin; @@ -14,10 +15,11 @@ extern crate array_init; #[cfg(test)] extern crate std; +#[macro_use] +pub mod vga_buffer; pub mod gdt; pub mod interrupts; pub mod serial; -pub mod vga_buffer; pub unsafe fn exit_qemu() { use x86_64::instructions::port::Port; diff --git a/src/main.rs b/src/main.rs index f4f22d86..5abc65af 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,10 +7,8 @@ #[macro_use] extern crate blog_os; extern crate x86_64; -#[macro_use] -extern crate lazy_static; -use blog_os::interrupts::{self, PICS}; +use blog_os::interrupts::PICS; use core::panic::PanicInfo; /// This function is the entry point, since the linker looks for a function @@ -21,7 +19,7 @@ pub extern "C" fn _start() -> ! { println!("Hello World{}", "!"); blog_os::gdt::init(); - init_idt(); + blog_os::interrupts::init_idt(); unsafe { PICS.lock().initialize() }; x86_64::instructions::interrupts::enable(); @@ -37,76 +35,3 @@ pub fn panic(info: &PanicInfo) -> ! { println!("{}", info); blog_os::hlt_loop(); } - -use x86_64::structures::idt::{ExceptionStackFrame, InterruptDescriptorTable}; - -lazy_static! { - static ref IDT: InterruptDescriptorTable = { - let mut idt = InterruptDescriptorTable::new(); - idt.breakpoint.set_handler_fn(breakpoint_handler); - unsafe { - idt.double_fault - .set_handler_fn(double_fault_handler) - .set_stack_index(blog_os::gdt::DOUBLE_FAULT_IST_INDEX); - } - - let timer_interrupt_id = usize::from(interrupts::TIMER_INTERRUPT_ID); - idt[timer_interrupt_id].set_handler_fn(timer_interrupt_handler); - let keyboard_interrupt_id = usize::from(interrupts::KEYBOARD_INTERRUPT_ID); - idt[keyboard_interrupt_id].set_handler_fn(keyboard_interrupt_handler); - - idt - }; -} - -pub fn init_idt() { - IDT.load(); -} - -extern "x86-interrupt" fn breakpoint_handler(stack_frame: &mut ExceptionStackFrame) { - println!("EXCEPTION: BREAKPOINT\n{:#?}", stack_frame); -} - -extern "x86-interrupt" fn double_fault_handler( - stack_frame: &mut ExceptionStackFrame, - _error_code: u64, -) { - println!("EXCEPTION: DOUBLE FAULT\n{:#?}", stack_frame); - loop {} -} - -extern "x86-interrupt" fn timer_interrupt_handler(_stack_frame: &mut ExceptionStackFrame) { - print!("."); - unsafe { - PICS.lock() - .notify_end_of_interrupt(interrupts::TIMER_INTERRUPT_ID) - } -} - -extern "x86-interrupt" fn keyboard_interrupt_handler(_stack_frame: &mut ExceptionStackFrame) { - use x86_64::instructions::port::Port; - - let port = Port::new(0x60); - let scancode: u8 = unsafe { port.read() }; - - let key = match scancode { - 0x02 => Some('1'), - 0x03 => Some('2'), - 0x04 => Some('3'), - 0x05 => Some('4'), - 0x06 => Some('5'), - 0x07 => Some('6'), - 0x08 => Some('7'), - 0x09 => Some('8'), - 0x0a => Some('9'), - 0x0b => Some('0'), - _ => None, - }; - if let Some(key) = key { - print!("{}", key); - } - unsafe { - PICS.lock() - .notify_end_of_interrupt(interrupts::KEYBOARD_INTERRUPT_ID) - } -} From 66d940559f9d98570b5829f08e9b48ba3ace8705 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sat, 20 Oct 2018 16:44:40 +0200 Subject: [PATCH 12/18] Avoid deadlocks in println/serial_println --- .../posts/08-hardware-interrupts/index.md | 107 +++++++++++++++++- .../08-hardware-interrupts/qemu-deadlock.png | Bin 0 -> 8685 bytes src/serial.rs | 12 +- src/vga_buffer.rs | 6 +- 4 files changed, 118 insertions(+), 7 deletions(-) create mode 100644 blog/content/second-edition/posts/08-hardware-interrupts/qemu-deadlock.png diff --git a/blog/content/second-edition/posts/08-hardware-interrupts/index.md b/blog/content/second-edition/posts/08-hardware-interrupts/index.md index 1b3442de..aae002dd 100644 --- a/blog/content/second-edition/posts/08-hardware-interrupts/index.md +++ b/blog/content/second-edition/posts/08-hardware-interrupts/index.md @@ -231,13 +231,116 @@ When we now execute `bootimage run` we see dots periodically appearing on the sc ![QEMU printing consequtive dots showing the hardware timer](qemu-hardware-timer-dots.gif) -### Configuring The Timer +### Configuring the Timer The hardware timer that we use is called the _Progammable Interval Timer_ or PIT for short. Like the name says, it is possible to configure the interval between two interrupts. We won't go into details here because we will switch to the [APIC timer] soon, but the OSDev wiki has an extensive article about the [configuring the PIT]. [APIC timer]: https://wiki.osdev.org/APIC_timer [configuring the PIT]: https://wiki.osdev.org/Programmable_Interval_Timer +## Deadlocks + +We now have a form of concurrency in our kernel: The timer interrupts occur asynchronously, so they can interrupt our `_start` function at any time. Fortunately Rust's ownership system prevents many types of concurrency related bugs at compile time. One notable exception are deadlocks. Deadlocks occur if a thread tries to aquire a lock that will never become free. Thus the thread hangs indefinitely. + +We can already provoke a deadlock in our kernel. Remember, our `println` macro calls the `vga_buffer::print` function, which [locks a global `WRITER`][vga spinlock] using a spinlock: + +[vga spinlock]: ./second-edition/posts/03-vga-text-buffer/index.md#spinlocks + +```rust +// in src/vga_buffer.rs + +[…] + +pub fn print(args: fmt::Arguments) { + use core::fmt::Write; + WRITER.lock().write_fmt(args).unwrap(); +} +``` + +It locks the `WRITER`, calls `write_fmt` on it, and implicitly unlocks it at the end of the function. Now imagine that an interrupt occurs while the `WRITER` is locked and the interrupt handler tries to print something too: + +Timestep | _start | interrupt_handler +---------|------|------------------ +0 | calls `println!` |   +1 | `print` locks `WRITER` |   +2 | | **interrupt occurs**, handler begins to run +3 | | calls `println!` | +4 | | `print` tries to lock `WRITER` (already locked) +5 | | `print` tries to lock `WRITER` (already locked) +… | | … +_never_ | _unlock `WRITER`_ | + +The `WRITER` is locked, so the interrupt handler waits until it becomes free. But this never happens, because the `_start` function only continues to run after the interrupt handler returns. Thus the complete system hangs. + +### Provoking a Deadlock + +We can easily provoke such a deadlock in our kernel by printing something in the loop at the end of our `_start` function: + +```rust +// in src/main.rs + +#[cfg(not(test))] +#[no_mangle] +pub extern "C" fn _start() -> ! { + […] + loop { + print!("-"); // new + } +} +``` + +When we run it in QEMU we get output of the form: + +![QEMU output with many rows of hyphens and no dots](./qemu-deadlock.png) + +We see that only a limited number of hyphens is printed, until the first timer interrupt occurs. Then the system hangs because the timer interrupt handler deadlocks when it tries to print a dot. This is the reason that we see no dots in the above output. + +The actual number of hyphens varies between runs because the timer interrupt occurs asynchronously. This non-determinism is what makes concurrency related bugs so difficult to debug. + +### Fixing the Deadlock + +To avoid this deadlock, we can disable interrupts as long as the `Mutex` is locked: + +```rust +// in src/vga_buffer.rs + +/// Prints the given formatted string to the VGA text buffer +/// through the global `WRITER` instance. +pub fn print(args: fmt::Arguments) { + use core::fmt::Write; + use x86_64::instructions::interrupts; // new + + interrupts::without_interrupts(|| { // new + WRITER.lock().write_fmt(args).unwrap(); + }); +} +``` + +The [`without_interrupts`] function takes a [closure] and executes it in an interrupt-free environment. We use it to ensure that no interrupt can occur as long as the `Mutex` is locked. When we run our kernel now we see that it keeps running without hanging. (We still don't notice any dots, but this is because they're scrolling by too fast. Try to slow down the printing, e.g. by putting a `for _ in 0..10000 {}` inside the loop.) + +[`without_interrupts`]: https://docs.rs/x86_64/0.2.10/x86_64/instructions/interrupts/fn.without_interrupts.html +[closure]: https://doc.rust-lang.org/book/second-edition/ch13-01-closures.html + +We can apply the same change to our serial printing function to ensure that no deadlocks occur with it either: + +```rust +// in src/serial.rs + +pub fn print(args: ::core::fmt::Arguments) { + use core::fmt::Write; + use x86_64::instructions::interrupts; // new + + interrupts::without_interrupts(|| { // new + SERIAL1 + .lock() + .write_fmt(args) + .expect("Printing to serial failed"); + }); +} +``` + +Note that disabling interrupts shouldn't be a general solution. The problem is that it increases the worst case interrupt latency, i.e. the time until the system reacts to an interrupt. Therefore interrupts should be only disabled for a very short time. + ## The `hlt` Instruction Until now we used a simple empty loop statement at the end of our `_start` and `panic` functions. This causes the CPU to spin endlessly and thus works as expected. But it is also very inefficient, because the CPU continues to run at full speed even though there's no work to do. You can see this problem in your task manager when you run your kernel: The QEMU process needs close to 100% CPU the whole time. @@ -429,7 +532,7 @@ The above code just translates keypresses of the number keys 0-9 and ignores all Translating the other keys could work in the same way, probably with an enum for control keys such as escape or backspace. Such a translation function would be a good candidate for a small external crate, but I couldn't find one that works with scancode set 1. In case you'd like to write such a crate and need mentoring, just let us know, we're happy to help! -### Configuring The Keyboard +### Configuring the Keyboard It's possible to configure some aspects of a PS/2 keyboard, for example which scancode set it should use. We won't cover it here because this post is already long enough, but the OSDev Wiki has an overview of possible [configuration commands]. diff --git a/blog/content/second-edition/posts/08-hardware-interrupts/qemu-deadlock.png b/blog/content/second-edition/posts/08-hardware-interrupts/qemu-deadlock.png new file mode 100644 index 0000000000000000000000000000000000000000..0f4590fe47fa400307eafb723abe47402fb678b2 GIT binary patch literal 8685 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 ZtlCl9ZF<#vDFXuogQu&X%Q~loCIE?B8qWX# literal 0 HcmV?d00001 diff --git a/src/serial.rs b/src/serial.rs index 3cd4817f..830bf2f5 100644 --- a/src/serial.rs +++ b/src/serial.rs @@ -11,10 +11,14 @@ lazy_static! { pub fn print(args: ::core::fmt::Arguments) { use core::fmt::Write; - SERIAL1 - .lock() - .write_fmt(args) - .expect("Printing to serial failed"); + use x86_64::instructions::interrupts; + + interrupts::without_interrupts(|| { + SERIAL1 + .lock() + .write_fmt(args) + .expect("Printing to serial failed"); + }); } /// Prints to the host through the serial interface. diff --git a/src/vga_buffer.rs b/src/vga_buffer.rs index 8c956365..3e937482 100644 --- a/src/vga_buffer.rs +++ b/src/vga_buffer.rs @@ -164,7 +164,11 @@ macro_rules! println { /// Prints the given formatted string to the VGA text buffer through the global `WRITER` instance. pub fn print(args: fmt::Arguments) { use core::fmt::Write; - WRITER.lock().write_fmt(args).unwrap(); + use x86_64::instructions::interrupts; + + interrupts::without_interrupts(|| { + WRITER.lock().write_fmt(args).unwrap(); + }); } #[cfg(test)] From 16a727b3ccba0d2d88afae5372d54355795f042d Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sat, 20 Oct 2018 16:45:48 +0200 Subject: [PATCH 13/18] Set release date --- .../second-edition/posts/08-hardware-interrupts/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/blog/content/second-edition/posts/08-hardware-interrupts/index.md b/blog/content/second-edition/posts/08-hardware-interrupts/index.md index aae002dd..61c8a285 100644 --- a/blog/content/second-edition/posts/08-hardware-interrupts/index.md +++ b/blog/content/second-edition/posts/08-hardware-interrupts/index.md @@ -2,7 +2,7 @@ title = "Hardware Interrupts" order = 8 path = "hardware-interrupts" -date = 2018-07-26 +date = 2018-10-20 template = "second-edition/page.html" +++ From 4583936f0b3b54aed93676d7a47e83245252cf48 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sat, 20 Oct 2018 17:02:52 +0200 Subject: [PATCH 14/18] Remove some temporary variables --- .../second-edition/posts/08-hardware-interrupts/index.md | 8 ++++---- src/interrupts.rs | 6 ++---- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/blog/content/second-edition/posts/08-hardware-interrupts/index.md b/blog/content/second-edition/posts/08-hardware-interrupts/index.md index 61c8a285..485bb6bc 100644 --- a/blog/content/second-edition/posts/08-hardware-interrupts/index.md +++ b/blog/content/second-edition/posts/08-hardware-interrupts/index.md @@ -183,8 +183,8 @@ lazy_static! { let mut idt = InterruptDescriptorTable::new(); idt.breakpoint.set_handler_fn(breakpoint_handler); […] - let timer_interrupt_id = usize::from(TIMER_INTERRUPT_ID); // new - idt[timer_interrupt_id].set_handler_fn(timer_interrupt_handler); // new + idt[usize::from(TIMER_INTERRUPT_ID)] + .set_handler_fn(timer_interrupt_handler); // new idt }; @@ -432,8 +432,8 @@ lazy_static! { idt.breakpoint.set_handler_fn(breakpoint_handler); […] // new - let keyboard_interrupt_id = usize::from(KEYBOARD_INTERRUPT_ID); - idt[keyboard_interrupt_id].set_handler_fn(keyboard_interrupt_handler); + idt[usize::from(KEYBOARD_INTERRUPT_ID)] + .set_handler_fn(keyboard_interrupt_handler); idt }; diff --git a/src/interrupts.rs b/src/interrupts.rs index ce0898f3..fac6fc52 100644 --- a/src/interrupts.rs +++ b/src/interrupts.rs @@ -23,10 +23,8 @@ lazy_static! { .set_stack_index(gdt::DOUBLE_FAULT_IST_INDEX); } - let timer_interrupt_id = usize::from(TIMER_INTERRUPT_ID); - idt[timer_interrupt_id].set_handler_fn(timer_interrupt_handler); - let keyboard_interrupt_id = usize::from(KEYBOARD_INTERRUPT_ID); - idt[keyboard_interrupt_id].set_handler_fn(keyboard_interrupt_handler); + idt[usize::from(TIMER_INTERRUPT_ID)].set_handler_fn(timer_interrupt_handler); + idt[usize::from(KEYBOARD_INTERRUPT_ID)].set_handler_fn(keyboard_interrupt_handler); idt }; From 5035872ab6c2552b7e1a918a31677c23941b65c9 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sat, 20 Oct 2018 17:03:09 +0200 Subject: [PATCH 15/18] Run rustfmt --- src/interrupts.rs | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/src/interrupts.rs b/src/interrupts.rs index fac6fc52..e931aede 100644 --- a/src/interrupts.rs +++ b/src/interrupts.rs @@ -10,8 +10,8 @@ pub static PICS: spin::Mutex = pub const TIMER_INTERRUPT_ID: u8 = PIC_1_OFFSET; pub const KEYBOARD_INTERRUPT_ID: u8 = PIC_1_OFFSET + 1; -use x86_64::structures::idt::{ExceptionStackFrame, InterruptDescriptorTable}; use gdt; +use x86_64::structures::idt::{ExceptionStackFrame, InterruptDescriptorTable}; lazy_static! { static ref IDT: InterruptDescriptorTable = { @@ -50,10 +50,7 @@ extern "x86-interrupt" fn double_fault_handler( extern "x86-interrupt" fn timer_interrupt_handler(_stack_frame: &mut ExceptionStackFrame) { print!("."); - unsafe { - PICS.lock() - .notify_end_of_interrupt(TIMER_INTERRUPT_ID) - } + unsafe { PICS.lock().notify_end_of_interrupt(TIMER_INTERRUPT_ID) } } extern "x86-interrupt" fn keyboard_interrupt_handler(_stack_frame: &mut ExceptionStackFrame) { @@ -80,8 +77,5 @@ extern "x86-interrupt" fn keyboard_interrupt_handler(_stack_frame: &mut Exceptio print!("{}", key); } - unsafe { - PICS.lock() - .notify_end_of_interrupt(KEYBOARD_INTERRUPT_ID) - } + unsafe { PICS.lock().notify_end_of_interrupt(KEYBOARD_INTERRUPT_ID) } } From b40e5bd7b23f42207f0f1abfab2f9f2a6530a1cc Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sat, 20 Oct 2018 17:09:01 +0200 Subject: [PATCH 16/18] Group imports --- src/interrupts.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/interrupts.rs b/src/interrupts.rs index e931aede..b880e831 100644 --- a/src/interrupts.rs +++ b/src/interrupts.rs @@ -1,5 +1,7 @@ +use gdt; use pic8259_simple::ChainedPics; use spin; +use x86_64::structures::idt::{ExceptionStackFrame, InterruptDescriptorTable}; pub const PIC_1_OFFSET: u8 = 32; pub const PIC_2_OFFSET: u8 = PIC_1_OFFSET + 8; @@ -10,9 +12,6 @@ pub static PICS: spin::Mutex = pub const TIMER_INTERRUPT_ID: u8 = PIC_1_OFFSET; pub const KEYBOARD_INTERRUPT_ID: u8 = PIC_1_OFFSET + 1; -use gdt; -use x86_64::structures::idt::{ExceptionStackFrame, InterruptDescriptorTable}; - lazy_static! { static ref IDT: InterruptDescriptorTable = { let mut idt = InterruptDescriptorTable::new(); From fb489a30eecc58c75691d93a797c758b67ab75ef Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sat, 20 Oct 2018 19:04:09 +0200 Subject: [PATCH 17/18] Move hlt_loop import into function --- src/interrupts.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/interrupts.rs b/src/interrupts.rs index b880e831..a43a5991 100644 --- a/src/interrupts.rs +++ b/src/interrupts.rs @@ -33,8 +33,6 @@ pub fn init_idt() { IDT.load(); } -use hlt_loop; - extern "x86-interrupt" fn breakpoint_handler(stack_frame: &mut ExceptionStackFrame) { println!("EXCEPTION: BREAKPOINT\n{:#?}", stack_frame); } @@ -43,6 +41,8 @@ extern "x86-interrupt" fn double_fault_handler( stack_frame: &mut ExceptionStackFrame, _error_code: u64, ) { + use hlt_loop; + println!("EXCEPTION: DOUBLE FAULT\n{:#?}", stack_frame); hlt_loop(); } From 75a7359ba54436f1ab2a592d8fd74b04c35359b8 Mon Sep 17 00:00:00 2001 From: Philipp Oppermann Date: Sat, 20 Oct 2018 19:29:44 +0200 Subject: [PATCH 18/18] Update to latest x86_64 crate --- Cargo.lock | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6f513372..ac178490 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -27,7 +27,7 @@ dependencies = [ "spin 0.4.9 (registry+https://github.com/rust-lang/crates.io-index)", "uart_16550 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "volatile 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", - "x86_64 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)", + "x86_64 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -81,7 +81,7 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", - "x86_64 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)", + "x86_64 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -106,7 +106,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "x86_64" -version = "0.2.13" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "bit_field 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -132,4 +132,4 @@ dependencies = [ "checksum ux 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "53d8df5dd8d07fedccd202de1887d94481fadaea3db70479f459e8163a1fab41" "checksum version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd" "checksum volatile 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "54d4343a2df2d65144a874f95950754ee7b7e8594f6027aae8c7d0f4858a3fe8" -"checksum x86_64 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)" = "a7e95a2813e20d24546c2b29ecc6df55cfde30c983df69eeece0b179ca9d68ac" +"checksum x86_64 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)" = "2bd647af1614659e1febec1d681231aea4ebda4818bf55a578aff02f3e4db4b4"