diff --git a/course/06-PIC_PIT_keyboard.md b/course/06-PIC_PIT_keyboard.md new file mode 100644 index 0000000000000000000000000000000000000000..fdbc997df26f22c76500fcbe39cece708c8a088c --- /dev/null +++ b/course/06-PIC_PIT_keyboard.md @@ -0,0 +1,364 @@ +--- +author: Florent Gluck - Florent.Gluck@hesge.ch + +title: PIC, PIT, keyboard + +date: \vspace{.5cm} \footnotesize \today + +pandoc-latex-fontsize: + - classes: [tiny] + size: tiny + - classes: [verysmall] + size: scriptsize + - classes: [small] + size: footnotesize + - classes: [huge, important] + size: huge +--- + +[//]: # ---------------------------------------------------------------- +# Programmable Interrupt Controller (PIC) + +[//]: # ---------------------------------------------------------------- +## PIC basics + +- A Programmable Interrupt Controller (PIC) handles interrupt requests (IRQs) from devices +- A PIC has input lines to which hardware devices are connected + - each line is associated to an IRQ +- When a device requires the attention of the CPU: + - it triggers its line which triggers the associated IRQ + - the PIC notifies the CPU by triggering the hardware interrupt corresponding to the IRQ + +[//]: # ---------------------------------------------------------------- +## IRQ to hardware interrupt mapping + +- How does the PIC **map** an IRQ number to a hardware interrupt number? +- For instance, what hardware interrupt corresponds to IRQ4? + +[//]: # ---------------------------------------------------------------- +## IRQ to hardware interrupt mapping + +- How does the PIC **map** an IRQ number to an interrupt number? +- The hardware interrupt number is computed by adding a programmable **offset** to the IRQ number: + \vspace{0.2cm} + ```{.small} + hardware_interrupt_number = irq_number + offset + ``` +- \textcolor{myred}{The same offset applies to all IRQs!} + +[//]: # ---------------------------------------------------------------- +## Reality is slightly different + +:::::: {.columns} +::: {.column width="55%"} + +\small + +\vspace{1cm} + +- In reality, PCs feature two "Intel 8259" PICs: one master, one slave +- The master handles IRQ0 to IRQ7 +- The slave handles IRQ8 to IRQ15 +- Both are **addressed in PMIO** + +::: +::: {.column width="45%"} + +\centering +{ width=100% } + +::: +:::::: + +[//]: # ---------------------------------------------------------------- +## Interrupt conflicts + +::: incremental + +- At boot time, BIOS/UEFI configures the following mapping: + - IRQ0 to IRQ7 $\rightarrow$ hardware interrupts `0x8` to `0xF` + - IRQ8 to IRQ15 $\rightarrow$ hardware interrupts `0x70` to `0x77` +- \textcolor{myred}{\textbf{Issue}} + - the first 8 IRQs conflict with processor exceptions 0 to 7 + - reminder: the first 32 entries in the IDT are reserved for processor exceptions! +- \textcolor{myblue}{\textbf{Solution?}} + - program the PIC to map IRQ0 to IRQ7 to trigger hardware interrupts above 31 + +::: + +[//]: # ---------------------------------------------------------------- +## Remapping of hardware interrupts + +The code below maps: + +- IRQ0..7 to hardware interrupts 32..39 +- IRQ8..15 to hardware interrupts 40..47 + +\vfill + +```{.c .tiny} +#define PIC1_CMD 0x20 +#define PIC1_DATA 0x21 +#define PIC2_CMD 0xA0 +#define PIC2_DATA 0xA1 + +// Restart both PICs +outb(PIC1_CMD, 0x11); +outb(PIC2_CMD, 0x11); + +outb(PIC1_DATA, 32); // Map IRQs 0-7 to interrupts 32-39 +outb(PIC2_DATA, 40); // Map IRQs 8-15 to interrupts 40-47 + +// Setup PICs cascading +outb(PIC1_DATA, 0x04); +outb(PIC2_DATA, 0x02); +outb(PIC1_DATA, 0x01); +outb(PIC2_DATA, 0x01); +``` + +[//]: # ---------------------------------------------------------------- +## Hardware interrupt acknowledgement + +- The CPU must notify the PIC that the interrupt request was handled, by sending an *End Of Interrupt* (EOI), otherwise the interrupt won't be triggered again! +- This is usually done at the end of the ISR +- The PIC to notify depends on the PIC the IRQ came from: + - IRQ comes from the master: CPU sends an EOI to the master + - IRQ comes from the slave: CPU sends an EOI to the slave, then the master + +[//]: # ---------------------------------------------------------------- +## End of interrupt (EOI): code + +\small + +Sends an EOI to the necessary PICs based on the IRQ that was handled: + +\vspace{0.2cm} + +```{.c .verysmall} +#define PIC1_CMD 0x20 +#define PIC2_CMD 0xA0 +#define PIC_EOI 0x20 + +void pic_eoi(int irq) { + // An EOI must also be sent to the slave for IRQs > 7 + if (irq > 7) { + outb(PIC2_CMD, PIC_EOI); + } + // Send an EOI to the master + outb(PIC1_CMD, PIC_EOI); +} +``` + +<!-- +[//]: # ---------------------------------------------------------------- +## PIC (2/2) + +- Every PC has 2 PICs, a master and a slave +- Each PIC has 8 input lines, called Interrupt Requests (I) +- Chaque PIC possède 8 lignes d’entrée, appelées Interrupt Requests (IRQ), numérotée de 0 à 7 + +\small + +- Lorsqu’un périphérique lève une IRQ (i.e. lorsqu’une de ces lignes est “levée”), le PIC alèrte le CPU en envoyant une interruption matérielle +- Le n° de l’interruption matérielle est calculé en ajoutant le numéro d’IRQ (0 à 7) à un offset interne au PIC : + +\vspace{.2cm} +\centering +{ width=60% } + +- Ce n° détermine la routine (Interrupt Service Routine - ISR) à exécuter par le CPU : la routine localisée à l’indexe du n° dans la table IDT +- Lorsque le slave lève une interruption, celle-ci est envoyée au master qui l’envoie à son tour au CPU $\rightarrow$ le CPU peut donc avoir 16 lignes d’IRQ + +[//]: # ---------------------------------------------------------------- +## Interruptions matérielles + +\centering +{ width=100% } + +\small + +- Les périphériques possèdent des lignes générant les IRQ +- Les IRQ sont mappées sur les vecteurs d’interruption par un chip dédié, le Programmable Interrupt Controller (PIC) +- Lorsqu'un périphérique lève une IRQ, une interruption matérielle est signalée au CPU qui appelle la routine d'interruption (ISR) correspondante + +[//]: # ---------------------------------------------------------------- +## Contrôleurs d'interruptions + +:::::: {.columns} +::: {.column width="60%"} + +\footnotesize + +- Les interruptions matérielles sont gérées par deux PICs adressables en PMIO +- Le CPU n'ayant qu'une ligne d'interruption, le 2ème PIC doit donc être connecté en cascade au 1er en une configuration maître/esclave (master/slave) +- Chaque PIC peut gérer jusqu’à huit IRQs PIC maître : IRQ0 $\rightarrow$ IRQ7 +- PIC esclave : IRQ8 $\rightarrow$ IRQ15 +- Registre commande PIC maître : 0x20 +- Registre commande PIC esclave : 0xA0 + +::: +::: {.column width="40%"} + +\centering +{ width=100% } + +::: +:::::: +--> + +[//]: # ---------------------------------------------------------------- +# Programmable Interval Timer (PIT) + +[//]: # ---------------------------------------------------------------- +## PIT description + +- Every PC has an Intel 8253 chip which features a Programmable Interval Timer (PIT) +- It allows to measure elapsed time independently of the CPU frequency +- The PIT triggers IRQ0 at a frequency that can be programmed between 18.2065 Hz and 1.19182 MHz +- At boot time, BIOS/UEFI configures the PIT at the lowest frequency of 18.2065 Hz + +[//]: # ---------------------------------------------------------------- +## Variable frequency + +- The oscillator used by the PIT runs at 1.193182 MHz +- The output frequency is changed via a programmable *divider*: + + \vspace{.2cm} +$frequency = \frac{1193182}{divider}$ \hspace{.5cm} \footnotesize (integer division) \normalsize + \vspace{.2cm} + +- The divider is a 16 bit value, but 0 is interpreted as 65536 + - divider = 0 $\rightarrow$ frequency = 18.206512 Hz + - divider = 1 $\rightarrow$ frequency = 1193182 Hz + - divider = 1234 $\rightarrow$ frequency = 1193182/1234 Hz = 966 Hz + +<!-- +Le PIT possède 3 canaux, chacun avec un diviseur propre, mais seul le canal 0 nous intéresse + - Canal 0 : le canal utilisé ici ; sa sortie est connectée à l'IRQ0 + - Canal 1 : absent des PC modernes, mais utilisé historiquement pour rafraîchir la DRAM + - Canal 2 : contrôle le haut parleur interne du PC (avant la démocratisation des cartes son) +--> + +[//]: # ---------------------------------------------------------------- +## PIT programming + +- The PIT features 3 channels, but only channel 0 is of interest here +- The PIT registers are **addressed in PMIO**: + - port `0x43`: command register + - ports `0x40` to `0x42`: channels 0 to 2 +- How to change the divider? + - write `0x36` in the command register to set the divider and enable "repeat" mode (reset the counter once it reaches 0) + - write the least significant byte of the divider to channel 0 + - write the most significant byte of the divider to channel 0 + +[//]: # ---------------------------------------------------------------- +# PS/2 keyboard + +[//]: # ---------------------------------------------------------------- +## Introduction + +- Programming the PS/2 keyboard can be fairly complex +- Many aspects of the keyboard can be programmed, e.g. key delay, repetition rate, LEDs, Scan Code Set to use, etc. +- Here, we only explain how to read which keys are pressed or released in "Scan Code Set 1" + +[//]: # ---------------------------------------------------------------- +## Scan codes + +- The keyboard returns **scan codes** for each key press and key release +- Scan codes are usually 1 to 2 bytes long, but can be much longer (> 6) depending on the keyboard +- The scan code for a **key press** is called a **make code** +- The scan code for a **key release** is called a **break code** +- \textcolor{myred}{No scan code, in part or in full, matches another scan code} +- Keyboard scan codes have **nothing** to do with ASCII codes! + +[//]: # ---------------------------------------------------------------- +## Scan code sets + +- There are 3 different sets of scan codes dating back to the early days of the IBM PC: + - oldest "Scan Code Set 1" + - newer "Scan Code Set 2" + - newest and most complex is "Scan Code Set 3" +- Most PCs default to "Set 2", but most keyboard controllers translate it into "Set 1" for backward compatibility reasons + +[//]: # ---------------------------------------------------------------- +## Keyboard controller buffer + +- The keyboard controller has two 1-byte buffers: + - a 1-byte buffer to hold the data coming from the keyboard + - a 1-byte buffer to hold the data or command sent to the keyboard (from the PC) + +[//]: # ---------------------------------------------------------------- +## Keyboard interrupt + +- At boot time, the keyboard is initialized to generate IRQ1 upon a key press or release + - can be disabled if needed +- Whenever a key is pressed or released, two things happen: + 1. the scan code specific to the key is stored in the controller buffer + 1. the keyboard controller triggers IRQ1 +- if the scan code for a key is longer than 1 byte, the 2 steps above are repeated for each byte of the scan code sequence + +[//]: # ---------------------------------------------------------------- +## Keyboard registers + +- The keyboard controller has the following 1-byte registers, **addressed in PMIO**: + + **Port** **Access** **Description** + -------- ----------- --------------- + `0x60` read/write data + `0x64` read-only status + `0x64` write-only command + +- To **write a byte** to port `0x60` or `0x64`: **must** wait for bit 1 in the status register to be clear +- To **read a byte** from port `0x60`: **must** wait for bit 0 in the status register to be set + +[//]: # ---------------------------------------------------------------- +## Reading keys + +- When a key is pressed or released, its scan codes can be read from the data register `0x60` +- The scan code must be read from the keyboard, otherwise no new IRQ will be fired + +[//]: # ---------------------------------------------------------------- +## Reading keys in "Scan Code Set 1" (1/2) + +- Codes in "Scan Code Set 1" vary in length, ranging from 1 to 6 bytes +- Keys returning 1-byte scan codes are easy to handle: + - bit 7 of the scan code indicates whether the key was pressed or released + - bit 7 == 0 $\rightarrow$ key pressed + - bit 7 == 1 $\rightarrow$ key released + +[//]: # ---------------------------------------------------------------- +## Reading keys in "Scan Code Set 1" (2/2) + +- Keys returning scan codes longer than 1 byte cannot rely on bit 7 to detect a press/release +- Such keys return scan codes starting with `0xE0`, whether pressed or released +- Examples: + - Left arrow, pressed: `0xE0, 0x4B` + - Left arrow, released: `0xE0, 0xCB` + - Print screen pressed: `0xE0, 0x2A, 0xE0, 0x37` + - Print screen released: `0xE0, 0xB7, 0xE0, 0xAA` + +[//]: # ---------------------------------------------------------------- +## Ressources + +\footnotesize + +- 8259 PIC\ +\scriptsize [\textcolor{myblue}{https://wiki.osdev.org/8259\_PIC}](https://wiki.osdev.org/8259_PIC) + +\footnotesize + +- Programmable Interval Timer\ +\scriptsize [\textcolor{myblue}{https://wiki.osdev.org/Programmable\_Interval\_Timer}](https://wiki.osdev.org/Programmable_Interval_Timer) + +\footnotesize + +- PS/2 Keyboard Controller\ +\scriptsize [\textcolor{myblue}{https://wiki.osdev.org/\%228042\%22\_PS/2\_Controller}](https://wiki.osdev.org/%228042%22_PS/2_Controller)\ +\scriptsize [\textcolor{myblue}{https://wiki.osdev.org/PS/2\_Keyboard}](https://wiki.osdev.org/PS/2_Keyboard)\ +\scriptsize [\textcolor{myblue}{http://stanislavs.org/helppc/8042.html}](http://stanislavs.org/helppc/8042.html) + + +\footnotesize + +- Keyboard Scan Set 1\ +\scriptsize [\textcolor{myblue}{https://wiki.osdev.org/Keyboard\#Scan\_Code\_Set\_1}](https://wiki.osdev.org/Keyboard#Scan_Code_Set_1) diff --git a/course/06-PIC_PIT_keyboard.pdf b/course/06-PIC_PIT_keyboard.pdf new file mode 100644 index 0000000000000000000000000000000000000000..f9c8b176d7b0fbe56d1b1d424e4f996ecbf7e662 Binary files /dev/null and b/course/06-PIC_PIT_keyboard.pdf differ diff --git a/labs/lab3-interrupts_timer_keyboard/skeleton/yoctos/kernel/interrupt/idt.c b/labs/lab3-interrupts_timer_keyboard/skeleton/yoctos/kernel/interrupt/idt.c index 0417420007543017c6e5e1adb149f2b2289448eb..86a110212061923b1916904de68e764b72f1a5ae 100644 --- a/labs/lab3-interrupts_timer_keyboard/skeleton/yoctos/kernel/interrupt/idt.c +++ b/labs/lab3-interrupts_timer_keyboard/skeleton/yoctos/kernel/interrupt/idt.c @@ -98,6 +98,10 @@ void irq_handler(regs_t *regs) { void idt_init() { irq_init(); + // TODO + // Example of IDT entry for exception 3 idt[3] = idt_build_entry(GDT_KERNEL_CODE_SELECTOR, _exception3, TYPE_INTERRUPT_GATE, DPL_KERNEL); + + // TODO }