x86_q35/pic.rs
1// Licensed under the Apache License, Version 2.0 or the MIT License.
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3// Copyright Tock Contributors 2024.
4
5//! Module for interacting with legacy 8259 PIC
6//!
7//! This implementation is based on guidance from the following sources:
8//!
9//! * <https://wiki.osdev.org/8259_PIC>
10//! * <https://github.com/rust-osdev/pic8259>
11
12use x86::registers::io;
13use x86::IDT_RESERVED_EXCEPTIONS;
14
15/// PIC initialization command
16const PIC_CMD_INIT: u8 = 0x10;
17
18/// Flag that can be added to `PIC_CMD_INIT` indicating that a fourth configuration word will _not_
19/// be provided.
20const PIC_CMD_INIT_NO_ICW4: u8 = 0x01;
21
22/// PIC end-of-interrupt command
23const PIC_CMD_EOI: u8 = 0x20;
24
25/// Tells PIC to operate in 8086 mode
26const PIC_MODE_8086: u8 = 0x01;
27
28/// I/O port address of PIC 1 command register
29const PIC1_CMD: u16 = 0x20;
30
31/// I/O port address of PIC 1 data register
32const PIC1_DATA: u16 = PIC1_CMD + 1;
33
34/// Offset to which PIC 1 interrupts are re-mapped
35pub(crate) const PIC1_OFFSET: u8 = IDT_RESERVED_EXCEPTIONS;
36
37/// Number of interrupts handled by PIC 1
38const PIC1_NUM_INTERRUPTS: u8 = 8; // IRQ 0-7
39
40/// I/O port address of PIC 2 command register
41const PIC2_CMD: u16 = 0xa0;
42
43/// I/O port address of PIC 2 data register
44const PIC2_DATA: u16 = PIC2_CMD + 1;
45
46/// Offset to which PIC 2 interrupts are re-mapped
47pub(crate) const PIC2_OFFSET: u8 = PIC1_OFFSET + PIC1_NUM_INTERRUPTS;
48
49/// I/O dummy port used for introducing a delay between PIC commands
50const POST_PORT: u16 = 0x80;
51
52/// Initializes the system's primary and secondary PICs in a chained configuration and unmasks all
53/// interrupts.
54///
55/// ## Safety
56///
57/// Calling this function will cause interrupts to start firing. This means the IDT must already be
58/// initialized with valid handlers for all possible interrupt numbers (i.e. by a call to
59/// [`handlers::init`][super::handlers::init]).
60pub(crate) unsafe fn init() {
61 unsafe {
62 let wait = || io::outb(POST_PORT, 0);
63
64 // Begin initialization
65 io::outb(PIC1_CMD, PIC_CMD_INIT | PIC_CMD_INIT_NO_ICW4);
66 wait();
67 io::outb(PIC2_CMD, PIC_CMD_INIT | PIC_CMD_INIT_NO_ICW4);
68 wait();
69
70 // Re-map interrupt offsets
71 io::outb(PIC1_DATA, PIC1_OFFSET);
72 wait();
73 io::outb(PIC2_DATA, PIC2_OFFSET);
74 wait();
75
76 // Configure chaining
77 io::outb(PIC1_DATA, 4);
78 wait();
79 io::outb(PIC2_DATA, 2);
80 wait();
81
82 // Configure mode
83 io::outb(PIC1_DATA, PIC_MODE_8086);
84 wait();
85 io::outb(PIC2_DATA, PIC_MODE_8086);
86 wait();
87
88 // Unmask all interrupts
89 io::outb(PIC1_DATA, 0x00);
90 io::outb(PIC2_DATA, 0x00);
91 }
92}
93
94/// Sends an end-of-interrupt signal to the PIC responsible for generating interrupt `num`.
95///
96/// If `num` does not correspond to either the primary or secondary PIC, then no action is taken.
97///
98/// ## Safety
99///
100/// This function must _only_ be called from an interrupt servicing routine. Calling this function
101/// from the normal kernel loop could interfere with this crate's interrupt handling logic.
102pub(crate) unsafe fn eoi(num: u32) {
103 let _ = u8::try_from(num).map(|num| unsafe {
104 if (PIC1_OFFSET..PIC1_OFFSET + 8).contains(&num) {
105 io::outb(PIC1_CMD, PIC_CMD_EOI);
106 } else if (PIC2_OFFSET..PIC2_OFFSET + 8).contains(&num) {
107 io::outb(PIC2_CMD, PIC_CMD_EOI);
108 }
109 });
110}