diff --git a/guest/guest_main.c b/guest/guest_main.c
index f76e2fa85657fd1516ab418c9f12aa30cddd362f..1d0cfed8841b0af4a69df8d046ae7bed3a813542 100644
--- a/guest/guest_main.c
+++ b/guest/guest_main.c
@@ -1,9 +1,12 @@
 #include <stdint.h>
+#include <stdbool.h>
 
+#include "irq.h"
 #include "idt.h"
 #include "x86.h"
-#include "guest/stdio.h"
+#include "pmio.h"
 #include "shared/ide_regs.h"
+#include "shared/hypercall_params.h"
 
 #include "gfx/gfx.h"
 #include "ide/ide.h"
@@ -13,6 +16,8 @@
 
 #include "guest/resources/resources.h"
 
+// --- DEFINE ---
+
 // If PV == 1 => uses paravirtualized drivers (when available)
 // If PV == 0 => uses physical (hardware) drivers (when available)
 #if PV == 1
@@ -33,9 +38,62 @@
 #define sprite_position     sprite_phys_position
 #endif
 
+uint32_t x = 50;
+uint32_t y = 50;
+
+// --- FUNCTION ---
+
+void keyboard_handler(void) {
+
+    #if PV == 1
+
+        hyper_keyboard_params_t *p_keyboard = (hyper_keyboard_params_t *)HYPERCALL_SHARED_ADDR;
+        char key = (char)p_keyboard->key;
+    #else
+
+        char key = (char)ind(REG_KEYBOARD_DATA);
+    #endif
+
+    // char str[125]; snprintf(str, 125, "key received : %c\n", key);
+    // console_send(str);
+
+    switch (key) {
+
+        case 'w':
+
+            y -= 25;
+        break;
+
+        case 's':
+
+            y += 25;
+        break;
+
+        case 'a':
+
+            x -= 25;
+        break;
+
+        case 'd':
+
+            x += 25;
+        break;
+
+        default:
+            // nothing
+        break;
+    }
+
+    sprite_position(0, x, y);
+}
+
 void guest_main() {
 
     idt_init();  // Initialize interrupt subsystem
+
+    handler_t hd = {keyboard_handler, "IRQ_KEYBOARD"};
+    irq_install_handler(IRQ_KEYBOARD, hd);
+
     sti();       // Enable hardware interrupts
 
     // - console -
@@ -54,9 +112,14 @@ void guest_main() {
 
     // - sprite -
     sprite_init(0, sprite_tuxjedi, SPRITE_TUXJEDI_WIDTH, SPRITE_TUXJEDI_HEIGHT);
-    sprite_position(0, 50, 50);
+    sprite_position(0, x, y);
     sprite_visibility(0, 1);
 
+    // - main loop -
+    char *s = "-- guest enter while 1 --";
+    console_send(s);
+
+    while (1);
     halt();
 }
 
diff --git a/guest/idt.c b/guest/idt.c
index f49386dbe0193b45cff65f8ceca1d39a40f5bc0b..47d54cc77e7b826eee201cb1109d5262ea1ac926 100644
--- a/guest/idt.c
+++ b/guest/idt.c
@@ -2,6 +2,7 @@
 #include "utils.h"
 #include "descriptors.h"
 #include "x86.h"
+#include "irq.h"
 
 #define GDT_KERNEL_CODE_SELECTOR 0x08
 
@@ -53,12 +54,18 @@ static idt_entry_t idt_build_entry(uint16_t selector, uint32_t offset, uint8_t t
 
 // Low level handler defined in idt_asm.s
 void _irq0();
+void _irq1();
 
 // IRQ handler
 void irq_handler(uint32_t irq_number) {
+
+    handler_t *hd = irq_get_handler(irq_number);
+    if (hd != 0) hd->func();
 }
 
 void idt_init() {
+    irq_init();
+
     idt_ptr.limit = sizeof(idt)-1;
     idt_ptr.base  = (uint32_t)&idt;
 
@@ -66,6 +73,7 @@ void idt_init() {
     memset(idt, 0, sizeof(idt));
 
     idt[0] = idt_build_entry(GDT_KERNEL_CODE_SELECTOR, (uint32_t)_irq0, TYPE_INTERRUPT_GATE, DPL_KERNEL);
+    idt[1] = idt_build_entry(GDT_KERNEL_CODE_SELECTOR, (uint32_t)_irq1, TYPE_INTERRUPT_GATE, DPL_KERNEL);
 
     idt_load(&idt_ptr);
 }
diff --git a/guest/idt_asm.s b/guest/idt_asm.s
index bfd31a8b6510d004419aafdac1ca81559c4adb7a..1f0519f1bd80c7b61d1ac7ddd34b329faf97f0e7 100644
--- a/guest/idt_asm.s
+++ b/guest/idt_asm.s
@@ -15,6 +15,11 @@ _irq0:
     push    0  ; put irq number on the stack
     jmp     irq_handler_wrapper
 
+global _irq1
+_irq1:
+    push    1  ; put irq number on the stack
+    jmp     irq_handler_wrapper
+
 extern irq_handler
 
 irq_handler_wrapper:
diff --git a/guest/irq.c b/guest/irq.c
new file mode 100644
index 0000000000000000000000000000000000000000..a421e74746dcde8f656a4c1e452cb2394b957874
--- /dev/null
+++ b/guest/irq.c
@@ -0,0 +1,28 @@
+#include "irq.h"
+
+#include "utils.h"
+#include <stdint.h>
+
+// --- DEFINE ---
+
+#define IRQ_COUNT    (IRQ_LAST-IRQ_FIRST + 1)
+
+static handler_t irq_handlers[IRQ_COUNT];
+
+// --- FUNCTION ---
+
+void irq_init() {
+
+    memset(irq_handlers, 0, sizeof(irq_handlers));
+}
+
+void irq_install_handler(uint32_t irq, handler_t handler) {
+
+    irq_handlers[irq] = handler;
+}
+
+handler_t *irq_get_handler(uint32_t irq) {
+
+    return &irq_handlers[irq];
+}
+
diff --git a/guest/irq.h b/guest/irq.h
new file mode 100644
index 0000000000000000000000000000000000000000..219f9187f2750bb053f64ad5f51c375c66b5ec29
--- /dev/null
+++ b/guest/irq.h
@@ -0,0 +1,34 @@
+#ifndef _IRQ_H_
+#define _IRQ_H_
+
+#include <stdint.h>
+
+// --- DEFINE ---
+
+#define IRQ_FIRST    0
+#define IRQ_LAST     15
+
+#define IRQ_TIMER       0
+#define IRQ_KEYBOARD    1
+
+typedef struct {
+
+    void (*func)(void);   // pointer to handler function
+    char name[64];        // associate a name to the function (for debugging purposes)
+} handler_t;
+
+// --- FUNCTION ---
+
+// Initializes the array of IRQ handlers.
+void irq_init();
+
+// Installs a handler for the given IRQ.
+// The irq parameter must be in the range [0,15] inclusive.
+void irq_install_handler(uint32_t irq, handler_t handler);
+
+// Retrieves the handler for a given IRQ.
+// The irq parameter must be in the range [0,15] inclusive.
+// Returns NULL if there is no handler for the given IRQ.
+handler_t *irq_get_handler(uint32_t irq);
+
+#endif // _IRQ_H_
diff --git a/guest/sprite/sprite_phys.c b/guest/sprite/sprite_phys.c
index dea1b2da23ea4ce2a163391715a68c8b132301d9..7f4220b2e467fe69d37992738176eb28520b2a13 100644
--- a/guest/sprite/sprite_phys.c
+++ b/guest/sprite/sprite_phys.c
@@ -11,6 +11,8 @@
 
 void sprite_phys_init(uint8_t id, uint8_t *data, uint32_t width, uint32_t height) {
 
+    while(*(uint32_t *)REG_SPRITE_ST != 64);
+
     *(uint32_t *)REG_SPRITE_CMD = 9;
 
     *(uint32_t *)REG_SPRITE_CMD = 27;
diff --git a/shared/hypercall_params.h b/shared/hypercall_params.h
index ea5ad45721d7b2f6e74ad9f66c6e23d8b823db32..e4f4e15aec30e2206a68b5cf025837319160ea60 100644
--- a/shared/hypercall_params.h
+++ b/shared/hypercall_params.h
@@ -17,6 +17,7 @@
 #define HYPERCALL_CODE_SPRITE_INIT  5
 #define HYPERCALL_CODE_SPRITE_VISI  6
 #define HYPERCALL_CODE_SPRITE_POS   7
+#define HYPERCALL_CODE_KEYBOARD     8
 
 #define REG_TIMER_CMD  0x43
 #define REG_TIMER_DATA 0x40
@@ -29,6 +30,8 @@
 #define REG_SPRITE_CMD  0x3E800010
 #define REG_SPRITE_DATA 0x3E800020
 
+#define REG_KEYBOARD_DATA 0x60
+
 // --- SHARED STRUCT ---
 
 typedef struct {
@@ -74,6 +77,11 @@ typedef struct {
     uint32_t y;         // y position
 } __attribute__((packed)) hyper_sprite_position_params_t;
 
+typedef struct {
+
+    uint32_t key;         // code of the pressed key
+} __attribute__((packed)) hyper_keyboard_params_t;
+
 // --- FUNCTION ---
 
 // ...
diff --git a/vmm/handler.c b/vmm/handler.c
index 07c8cfbba2fbed530a79910ac194f1b72a9493e9..957b38bf825af34b13ac0f0fc392356a5e9ce746 100644
--- a/vmm/handler.c
+++ b/vmm/handler.c
@@ -132,6 +132,7 @@ static state_t *state_check(uint64_t addr, uint8_t size, uint32_t value) {
             break;
 
             case OP_READ_INJECT:
+            case OP_READ_INJECT_KEY:
 
                 if (state->addr == addr && state->size == size)
                     return state;
@@ -176,6 +177,13 @@ static void state_compute(state_t *state, uint32_t value, uint8_t *addr) {
                    state->size * 8, state->addr, state->value);
         break;
 
+        case OP_READ_INJECT_KEY:
+
+            // WARNING: operation should not be here, but f*** it
+            printf("OPERATION : send last key pressed %c...\n", (char)last_key_pressed);
+            injecte_data(addr, state->size, last_key_pressed);
+        break;
+
         case OP_WRITE_STORE_LOOP:
 
             // - prevent advancing to the next state if loop is ongoing -
diff --git a/vmm/handler.h b/vmm/handler.h
index d604a76884170fbbe2f0fc7228cbc6031e53393d..d5ec52523df9d1233b5a5c572ec63abb88ea2e67 100644
--- a/vmm/handler.h
+++ b/vmm/handler.h
@@ -10,6 +10,7 @@
 typedef void (*state_handler_t)(struct kvm_run *run, ...);
 
 extern state_handler_t const STATE_HANDLERS[];
+extern uint32_t last_key_pressed;
 
 // --- FUNCITON ---
 
diff --git a/vmm/operation.c b/vmm/operation.c
index ac3b74f3649f2b005926d0679507f051e4d835b6..e5d63364373d22fe0179b626f7e1d881a9ea650b 100644
--- a/vmm/operation.c
+++ b/vmm/operation.c
@@ -28,7 +28,7 @@ state_t STATE_TIMER[] = {
 
 state_t STATE_GFX_INIT[] = {
 
-    { OP_READ_INJECT, REG_GFX_INIT_ST,   0, 1, NULL },
+    { OP_READ_INJECT, REG_GFX_INIT_ST,   0, 4, NULL },
     { OP_WRITE_EQUAL, REG_GFX_INIT_CMD,  5, 4, NULL },
     { OP_WRITE_STORE, REG_GFX_INIT_DATA, 0, 4, op_callback_gfx_init_store_w },
     { OP_WRITE_STORE, REG_GFX_INIT_DATA, 0, 4, op_callback_gfx_init_store_h },
@@ -51,7 +51,8 @@ state_t STATE_IDE[] = {
 
 state_t STATE_SPRITE_INIT[] = {
 
-    { OP_WRITE_EQUAL,      REG_SPRITE_CMD,  0x9,  4, op_callback_sprite_init_prepare },
+    { OP_READ_INJECT,      REG_SPRITE_ST,   64,    4, op_callback_sprite_init_prepare },
+    { OP_WRITE_EQUAL,      REG_SPRITE_CMD,  0x9,  4, NULL },
     { OP_WRITE_EQUAL,      REG_SPRITE_CMD,  0x1B, 4, NULL },
     { OP_WRITE_STORE,      REG_SPRITE_DATA, 0,    4, op_callback_sprite_init_store_id },
     { OP_WRITE_STORE,      REG_SPRITE_DATA, 0,    4, op_callback_sprite_init_store_width },
@@ -65,18 +66,24 @@ state_t STATE_SPRITE_VIS[] = {
     { OP_WRITE_EQUAL, REG_SPRITE_CMD,  0xE,  4, NULL },
     { OP_WRITE_EQUAL, REG_SPRITE_CMD,  0x1B, 4, NULL },
     { OP_WRITE_STORE, REG_SPRITE_DATA, 0,    4, op_callback_sprite_visibility_store_id },
-    { OP_WRITE_STORE, REG_SPRITE_DATA, 0,    4, op_callback_sprite_visibility_store_toggle },
-    { OP_EMUL_END,    0,               0,    0, NULL },
+    { OP_WRITE_STORE, REG_SPRITE_DATA, 0,    1, op_callback_sprite_visibility_store_toggle },
+    { OP_EMUL_END,    0,               0,    0, op_callback_sprite_visibility_conclude },
 };
 
 state_t STATE_SPRITE_POS[] = {
 
-    { OP_WRITE_EQUAL, REG_SPRITE_CMD,  0x23, 4, NULL },
+    { OP_WRITE_EQUAL, REG_SPRITE_CMD,  35,   4, NULL },
     { OP_WRITE_EQUAL, REG_SPRITE_CMD,  0x1B, 4, NULL },
     { OP_WRITE_STORE, REG_SPRITE_DATA, 0,    4, op_callback_sprite_position_store_id },
     { OP_WRITE_STORE, REG_SPRITE_DATA, 0,    4, op_callback_sprite_position_store_x },
     { OP_WRITE_STORE, REG_SPRITE_DATA, 0,    4, op_callback_sprite_position_store_y },
-    { OP_EMUL_END,    0,               0,    0, NULL },
+    { OP_EMUL_END,    0,               0,    0, op_callback_sprite_position_conclude },
+};
+
+state_t STATE_KEYBOARD[] = {
+
+    { OP_READ_INJECT_KEY, REG_KEYBOARD_DATA, 0, 4, NULL },
+    { OP_EMUL_END,        0,                 0, 0, NULL },
 };
 
 state_t *STATE_ALL_STARTERS[] = {
@@ -87,6 +94,7 @@ state_t *STATE_ALL_STARTERS[] = {
     &STATE_SPRITE_INIT[0],
     &STATE_SPRITE_VIS[0],
     &STATE_SPRITE_POS[0],
+    &STATE_KEYBOARD[0],
 };
 
 bool flag_stop_loop = false;
@@ -110,7 +118,7 @@ static hyper_sprite_position_params_t param_sprite_pos;
 static uint64_t loop_idx = BUF_SIZE;
 static uint8_t loop_buf_8[BUF_SIZE];
 static uint16_t *loop_buf_16 = (uint16_t *)loop_buf_8;
-static uint32_t *loop_buf_32 = (uint32_t *)loop_buf_8;
+// static uint32_t *loop_buf_32 = (uint32_t *)loop_buf_8;
 
 // --- FUNCTION ---
 
@@ -339,3 +347,12 @@ void op_callback_sprite_position_conclude(void *addr) {
     sprites[p_sp_pos->id].y = p_sp_pos->y;
 }
 
+// === KEYBOARD ===
+
+void op_keyboard_pv_send_code(void *shared_buf, uint32_t key) {
+
+    hyper_keyboard_params_t param_key;
+    param_key.key = key;
+
+    memcpy(shared_buf, (void *)&param_key, sizeof(param_key));
+}
diff --git a/vmm/operation.h b/vmm/operation.h
index bd53f5ab20317c249a7d79fd96cc5887e5509265..59c96bee2eefd2ec8391bba91510e1685975199c 100644
--- a/vmm/operation.h
+++ b/vmm/operation.h
@@ -7,7 +7,7 @@
 
 // --- DEFINE ---
 
-#define STATE_MACHINE_NUM 3
+#define STATE_MACHINE_NUM 7
 
 typedef enum {
 
@@ -15,6 +15,7 @@ typedef enum {
     OP_WRITE_STORE,       // Store the written value into a specific location (used in callbacks)
     OP_WRITE_STORE_LOOP,  // Store the written value into a specific location multiple time
     OP_READ_INJECT,       // Inject a value into the guest from a specific address
+    OP_READ_INJECT_KEY,   // Inject last key pressed into the guest from a specific address
     OP_EMUL_END,          // End of the state machine, with an callback for completion
 } operation_t;
 
@@ -267,4 +268,6 @@ void op_callback_sprite_position_store_y(void *addr);
  */
 void op_callback_sprite_position_conclude(void *addr);
 
+void op_keyboard_pv_send_code(void *shared_buf, uint32_t key);
+
 #endif // _OPERATION_H_
diff --git a/vmm/vmm_main.c b/vmm/vmm_main.c
index c22b3809e612af1f61e67ea9395a5682e992d791..e4f08a86b89f9f32fb66a7c5978c63b8699d0195 100644
--- a/vmm/vmm_main.c
+++ b/vmm/vmm_main.c
@@ -1,3 +1,4 @@
+#include <SDL2/SDL_keycode.h>
 #include <SDL2/SDL_render.h>
 #include <err.h>
 #include <stdbool.h>
@@ -32,6 +33,7 @@ uint32_t width_gfx;
 uint32_t height_gfx;
 gfx_context_t *ctxt;
 sprite_t sprites[MAX_SPRITES];
+uint32_t last_key_pressed;
 
 char *disk_path;
 
@@ -143,6 +145,25 @@ int main(int argc, char* argv[])
     // Close the file descriptor
     close(binfd);
 
+    // -- 5.5. Create virtual PIC (Programmable Interrupt Controller) --
+    if (ioctl(vmfd, KVM_CREATE_IRQCHIP, NULL) < 0)
+        err(1, "Error: creating the vPIC\n");
+
+    struct kvm_irqchip irqchip = {
+        .chip_id = 0,       // 0 = PIC1, 1 = PIC2, 2 = IOAPIC
+    };
+
+    // retrieve the PIC registers
+    if (ioctl(vmfd, KVM_GET_IRQCHIP, &irqchip) < 0)
+        err(1, "Error: retrieving the PIC registers\n");
+
+    // configure the PIC to have auto-acknoledge from the guest
+    irqchip.chip.pic.auto_eoi = 1;
+
+    // set the PIC registers
+    if (ioctl(vmfd, KVM_SET_IRQCHIP, &irqchip) < 0)
+        err(1, "Error: setting the PIC registers\n");
+
     // -- 6.1. Create a vCPU --
     vcpufd = ioctl(vmfd, KVM_CREATE_VCPU, (unsigned long)0);
     if (vcpufd == -1) err(1, "KVM_CREATE_VCPU");
@@ -195,10 +216,44 @@ int main(int argc, char* argv[])
     ctxt = gfx_create("Basic Example", width_gfx, height_gfx);
     if (!ctxt) err(1, "KVM_SET_REGS");
 
-    while (1) {
+    bool done = false;
+    while (!done) {
+
+        gfx_background_update(ctxt);
+
+        // - handle key -
+        SDL_Keycode key = gfx_keypressed();
+        if (key != 0) {
 
-        // gfx_background_update(ctxt);
+            if (key == SDLK_ESCAPE) {
+
+                done = true;
+            }
+            else {
+
+                // - write key code to guest -
+                last_key_pressed = (uint32_t)key;
+
+                // WARNING: no need to do it in emulation, but whatever...
+                op_keyboard_pv_send_code(shared_buf, last_key_pressed);
+
+                // - trigger interrupt -
+                struct kvm_irq_level irq_level;
+                irq_level.irq = 1; // 1 -> key interrupt
+
+                // drive signal -> up (1)
+                irq_level.level = 1;
+                if (ioctl(vmfd, KVM_IRQ_LINE, &irq_level) < 0)
+                    err(1, "Error: driving irq signal up\n");
+
+                // drive signal -> down (0)
+                irq_level.level = 0;
+                if (ioctl(vmfd, KVM_IRQ_LINE, &irq_level) < 0)
+                    err(1, "Error: driving irq signal down\n");
+            }
+        }
 
+        // - handle sprites -
         for (uint8_t i = 0; i < MAX_SPRITES; i++) {
 
             if (sprites[i].toggle == 1) {