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
+![](images/PICs.png){ 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
+![](images/pic.png){ 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
+![](images/hw_int.png){ 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
+![](images/pic_cascade.png){ 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
 }