1e342083cSJohn Floren #include "kvm/read-write.h" 2e342083cSJohn Floren #include "kvm/ioport.h" 3e342083cSJohn Floren #include "kvm/mutex.h" 4e342083cSJohn Floren #include "kvm/util.h" 5e342083cSJohn Floren #include "kvm/term.h" 6e342083cSJohn Floren #include "kvm/kvm.h" 7e342083cSJohn Floren #include "kvm/i8042.h" 8714e5b7fSSasha Levin #include "kvm/kvm-cpu.h" 9e342083cSJohn Floren 10e342083cSJohn Floren #include <stdint.h> 11e342083cSJohn Floren 1267307cddSPekka Enberg /* 1367307cddSPekka Enberg * IRQs 1467307cddSPekka Enberg */ 15e342083cSJohn Floren #define KBD_IRQ 1 16e342083cSJohn Floren #define AUX_IRQ 12 17e342083cSJohn Floren 1867307cddSPekka Enberg /* 1967307cddSPekka Enberg * Registers 2067307cddSPekka Enberg */ 2167307cddSPekka Enberg #define I8042_DATA_REG 0x60 2248bed025SPekka Enberg #define I8042_PORT_B_REG 0x61 2367307cddSPekka Enberg #define I8042_COMMAND_REG 0x64 2467307cddSPekka Enberg 25b4a721d9SPekka Enberg /* 26b4a721d9SPekka Enberg * Commands 27b4a721d9SPekka Enberg */ 28b4a721d9SPekka Enberg #define I8042_CMD_CTL_RCTR 0x20 29b4a721d9SPekka Enberg #define I8042_CMD_CTL_WCTR 0x60 30b4a721d9SPekka Enberg #define I8042_CMD_AUX_LOOP 0xD3 31b4a721d9SPekka Enberg #define I8042_CMD_AUX_SEND 0xD4 32b4a721d9SPekka Enberg #define I8042_CMD_AUX_TEST 0xA9 33b4a721d9SPekka Enberg #define I8042_CMD_AUX_DISABLE 0xA7 34b4a721d9SPekka Enberg #define I8042_CMD_AUX_ENABLE 0xA8 35714e5b7fSSasha Levin #define I8042_CMD_SYSTEM_RESET 0xFE 36e342083cSJohn Floren 37e342083cSJohn Floren #define RESPONSE_ACK 0xFA 38e342083cSJohn Floren 39e342083cSJohn Floren #define MODE_DISABLE_AUX 0x20 40e342083cSJohn Floren 41e342083cSJohn Floren #define AUX_ENABLE_REPORTING 0x20 42e342083cSJohn Floren #define AUX_SCALING_FLAG 0x10 43e342083cSJohn Floren #define AUX_DEFAULT_RESOLUTION 0x2 44e342083cSJohn Floren #define AUX_DEFAULT_SAMPLE 100 45e342083cSJohn Floren 4698cbe4d9SPekka Enberg /* 4798cbe4d9SPekka Enberg * Status register bits 4898cbe4d9SPekka Enberg */ 49e9f4d662SPekka Enberg #define I8042_STR_AUXDATA 0x20 50e9f4d662SPekka Enberg #define I8042_STR_KEYLOCK 0x10 51e9f4d662SPekka Enberg #define I8042_STR_CMDDAT 0x08 52e9f4d662SPekka Enberg #define I8042_STR_MUXERR 0x04 53e9f4d662SPekka Enberg #define I8042_STR_OBF 0x01 54e342083cSJohn Floren 55e342083cSJohn Floren #define KBD_MODE_KBD_INT 0x01 56e342083cSJohn Floren #define KBD_MODE_SYS 0x02 57e342083cSJohn Floren 58e342083cSJohn Floren #define QUEUE_SIZE 128 59e342083cSJohn Floren 60e342083cSJohn Floren /* 61e342083cSJohn Floren * This represents the current state of the PS/2 keyboard system, 62e342083cSJohn Floren * including the AUX device (the mouse) 63e342083cSJohn Floren */ 64e342083cSJohn Floren struct kbd_state { 65e342083cSJohn Floren struct kvm *kvm; 66e342083cSJohn Floren 67fc769627SAndre Przywara u8 kq[QUEUE_SIZE]; /* Keyboard queue */ 68e342083cSJohn Floren int kread, kwrite; /* Indexes into the queue */ 69e342083cSJohn Floren int kcount; /* number of elements in queue */ 70e342083cSJohn Floren 71fc769627SAndre Przywara u8 mq[QUEUE_SIZE]; 72e342083cSJohn Floren int mread, mwrite; 73e342083cSJohn Floren int mcount; 74e342083cSJohn Floren 75e342083cSJohn Floren u8 mstatus; /* Mouse status byte */ 76e342083cSJohn Floren u8 mres; /* Current mouse resolution */ 77e342083cSJohn Floren u8 msample; /* Current mouse samples/second */ 78e342083cSJohn Floren 79e342083cSJohn Floren u8 mode; /* i8042 mode register */ 80e342083cSJohn Floren u8 status; /* i8042 status register */ 81e342083cSJohn Floren /* 82e342083cSJohn Floren * Some commands (on port 0x64) have arguments; 83e342083cSJohn Floren * we store the command here while we wait for the argument 84e342083cSJohn Floren */ 85fc769627SAndre Przywara u8 write_cmd; 86e342083cSJohn Floren }; 87e342083cSJohn Floren 88e342083cSJohn Floren static struct kbd_state state; 89e342083cSJohn Floren 90e342083cSJohn Floren /* 91e342083cSJohn Floren * If there are packets to be read, set the appropriate IRQs high 92e342083cSJohn Floren */ 93e342083cSJohn Floren static void kbd_update_irq(void) 94e342083cSJohn Floren { 95e342083cSJohn Floren u8 klevel = 0; 96e342083cSJohn Floren u8 mlevel = 0; 97e342083cSJohn Floren 98e342083cSJohn Floren /* First, clear the kbd and aux output buffer full bits */ 99e9f4d662SPekka Enberg state.status &= ~(I8042_STR_OBF | I8042_STR_AUXDATA); 100e342083cSJohn Floren 101e342083cSJohn Floren if (state.kcount > 0) { 102e9f4d662SPekka Enberg state.status |= I8042_STR_OBF; 103e342083cSJohn Floren klevel = 1; 104e342083cSJohn Floren } 105e342083cSJohn Floren 106e342083cSJohn Floren /* Keyboard has higher priority than mouse */ 107e342083cSJohn Floren if (klevel == 0 && state.mcount != 0) { 108e9f4d662SPekka Enberg state.status |= I8042_STR_OBF | I8042_STR_AUXDATA; 109e342083cSJohn Floren mlevel = 1; 110e342083cSJohn Floren } 111e342083cSJohn Floren 112e342083cSJohn Floren kvm__irq_line(state.kvm, KBD_IRQ, klevel); 113e342083cSJohn Floren kvm__irq_line(state.kvm, AUX_IRQ, mlevel); 114e342083cSJohn Floren } 115e342083cSJohn Floren 116e342083cSJohn Floren /* 117e342083cSJohn Floren * Add a byte to the mouse queue, then set IRQs 118e342083cSJohn Floren */ 119b6f60a85SPekka Enberg void mouse_queue(u8 c) 120e342083cSJohn Floren { 121e342083cSJohn Floren if (state.mcount >= QUEUE_SIZE) 122e342083cSJohn Floren return; 123e342083cSJohn Floren 124e342083cSJohn Floren state.mq[state.mwrite++ % QUEUE_SIZE] = c; 125e342083cSJohn Floren 126e342083cSJohn Floren state.mcount++; 127e342083cSJohn Floren kbd_update_irq(); 128e342083cSJohn Floren } 129e342083cSJohn Floren 130e342083cSJohn Floren /* 131e342083cSJohn Floren * Add a byte to the keyboard queue, then set IRQs 132e342083cSJohn Floren */ 133b6f60a85SPekka Enberg void kbd_queue(u8 c) 134e342083cSJohn Floren { 135e342083cSJohn Floren if (state.kcount >= QUEUE_SIZE) 136e342083cSJohn Floren return; 137e342083cSJohn Floren 138e342083cSJohn Floren state.kq[state.kwrite++ % QUEUE_SIZE] = c; 139e342083cSJohn Floren 140e342083cSJohn Floren state.kcount++; 141e342083cSJohn Floren kbd_update_irq(); 142e342083cSJohn Floren } 143e342083cSJohn Floren 144714e5b7fSSasha Levin static void kbd_write_command(struct kvm *kvm, u8 val) 145e342083cSJohn Floren { 146e342083cSJohn Floren switch (val) { 147b4a721d9SPekka Enberg case I8042_CMD_CTL_RCTR: 148e342083cSJohn Floren kbd_queue(state.mode); 149e342083cSJohn Floren break; 150b4a721d9SPekka Enberg case I8042_CMD_CTL_WCTR: 151b4a721d9SPekka Enberg case I8042_CMD_AUX_SEND: 152b4a721d9SPekka Enberg case I8042_CMD_AUX_LOOP: 153e342083cSJohn Floren state.write_cmd = val; 154e342083cSJohn Floren break; 155b4a721d9SPekka Enberg case I8042_CMD_AUX_TEST: 156e342083cSJohn Floren /* 0 means we're a normal PS/2 mouse */ 157e342083cSJohn Floren mouse_queue(0); 158e342083cSJohn Floren break; 159b4a721d9SPekka Enberg case I8042_CMD_AUX_DISABLE: 160e342083cSJohn Floren state.mode |= MODE_DISABLE_AUX; 161e342083cSJohn Floren break; 162b4a721d9SPekka Enberg case I8042_CMD_AUX_ENABLE: 163e342083cSJohn Floren state.mode &= ~MODE_DISABLE_AUX; 164e342083cSJohn Floren break; 165714e5b7fSSasha Levin case I8042_CMD_SYSTEM_RESET: 1662aa76b26SWill Deacon kvm__reboot(kvm); 167714e5b7fSSasha Levin break; 168e342083cSJohn Floren default: 169e342083cSJohn Floren break; 170e342083cSJohn Floren } 171e342083cSJohn Floren } 172e342083cSJohn Floren 173e342083cSJohn Floren /* 174e342083cSJohn Floren * Called when the OS reads from port 0x60 (PS/2 data) 175e342083cSJohn Floren */ 176fc769627SAndre Przywara static u8 kbd_read_data(void) 177e342083cSJohn Floren { 178fc769627SAndre Przywara u8 ret; 179e342083cSJohn Floren int i; 180e342083cSJohn Floren 181e342083cSJohn Floren if (state.kcount != 0) { 182e342083cSJohn Floren /* Keyboard data gets read first */ 183e342083cSJohn Floren ret = state.kq[state.kread++ % QUEUE_SIZE]; 184e342083cSJohn Floren state.kcount--; 185e342083cSJohn Floren kvm__irq_line(state.kvm, KBD_IRQ, 0); 186e342083cSJohn Floren kbd_update_irq(); 187e342083cSJohn Floren } else if (state.mcount > 0) { 188e342083cSJohn Floren /* Followed by the mouse */ 189e342083cSJohn Floren ret = state.mq[state.mread++ % QUEUE_SIZE]; 190e342083cSJohn Floren state.mcount--; 191e342083cSJohn Floren kvm__irq_line(state.kvm, AUX_IRQ, 0); 192e342083cSJohn Floren kbd_update_irq(); 19376857a39SCong Ding } else { 194e342083cSJohn Floren i = state.kread - 1; 195e342083cSJohn Floren if (i < 0) 196e342083cSJohn Floren i = QUEUE_SIZE; 197e342083cSJohn Floren ret = state.kq[i]; 198e342083cSJohn Floren } 199e342083cSJohn Floren return ret; 200e342083cSJohn Floren } 201e342083cSJohn Floren 202e342083cSJohn Floren /* 203e342083cSJohn Floren * Called when the OS read from port 0x64, the command port 204e342083cSJohn Floren */ 205fc769627SAndre Przywara static u8 kbd_read_status(void) 206e342083cSJohn Floren { 207fc769627SAndre Przywara return state.status; 208e342083cSJohn Floren } 209e342083cSJohn Floren 210e342083cSJohn Floren /* 211e342083cSJohn Floren * Called when the OS writes to port 0x60 (data port) 212e342083cSJohn Floren * Things written here are generally arguments to commands previously 213e342083cSJohn Floren * written to port 0x64 and stored in state.write_cmd 214e342083cSJohn Floren */ 215fc769627SAndre Przywara static void kbd_write_data(u8 val) 216e342083cSJohn Floren { 217e342083cSJohn Floren switch (state.write_cmd) { 218b4a721d9SPekka Enberg case I8042_CMD_CTL_WCTR: 219e342083cSJohn Floren state.mode = val; 220e342083cSJohn Floren kbd_update_irq(); 221e342083cSJohn Floren break; 222b4a721d9SPekka Enberg case I8042_CMD_AUX_LOOP: 223e342083cSJohn Floren mouse_queue(val); 224e342083cSJohn Floren mouse_queue(RESPONSE_ACK); 225e342083cSJohn Floren break; 226b4a721d9SPekka Enberg case I8042_CMD_AUX_SEND: 227e342083cSJohn Floren /* The OS wants to send a command to the mouse */ 228e342083cSJohn Floren mouse_queue(RESPONSE_ACK); 229e342083cSJohn Floren switch (val) { 230e342083cSJohn Floren case 0xe6: 231e342083cSJohn Floren /* set scaling = 1:1 */ 232e342083cSJohn Floren state.mstatus &= ~AUX_SCALING_FLAG; 233e342083cSJohn Floren break; 234e342083cSJohn Floren case 0xe8: 235e342083cSJohn Floren /* set resolution */ 236e342083cSJohn Floren state.mres = val; 237e342083cSJohn Floren break; 238e342083cSJohn Floren case 0xe9: 239e342083cSJohn Floren /* Report mouse status/config */ 240e342083cSJohn Floren mouse_queue(state.mstatus); 241e342083cSJohn Floren mouse_queue(state.mres); 242e342083cSJohn Floren mouse_queue(state.msample); 243e342083cSJohn Floren break; 244e342083cSJohn Floren case 0xf2: 245e342083cSJohn Floren /* send ID */ 246e342083cSJohn Floren mouse_queue(0); /* normal mouse */ 247e342083cSJohn Floren break; 248e342083cSJohn Floren case 0xf3: 249e342083cSJohn Floren /* set sample rate */ 250e342083cSJohn Floren state.msample = val; 251e342083cSJohn Floren break; 252e342083cSJohn Floren case 0xf4: 253e342083cSJohn Floren /* enable reporting */ 254e342083cSJohn Floren state.mstatus |= AUX_ENABLE_REPORTING; 255e342083cSJohn Floren break; 256e342083cSJohn Floren case 0xf5: 257e342083cSJohn Floren state.mstatus &= ~AUX_ENABLE_REPORTING; 258e342083cSJohn Floren break; 259e342083cSJohn Floren case 0xf6: 260e342083cSJohn Floren /* set defaults, just fall through to reset */ 261e342083cSJohn Floren case 0xff: 262e342083cSJohn Floren /* reset */ 263e342083cSJohn Floren state.mstatus = 0x0; 264e342083cSJohn Floren state.mres = AUX_DEFAULT_RESOLUTION; 265e342083cSJohn Floren state.msample = AUX_DEFAULT_SAMPLE; 266e342083cSJohn Floren break; 267e342083cSJohn Floren default: 268e342083cSJohn Floren break; 269e342083cSJohn Floren } 270e342083cSJohn Floren break; 271e342083cSJohn Floren case 0: 272e342083cSJohn Floren /* Just send the ID */ 273e342083cSJohn Floren kbd_queue(RESPONSE_ACK); 274e342083cSJohn Floren kbd_queue(0xab); 275e342083cSJohn Floren kbd_queue(0x41); 276e342083cSJohn Floren kbd_update_irq(); 277e342083cSJohn Floren break; 278e342083cSJohn Floren default: 279e342083cSJohn Floren /* Yeah whatever */ 280e342083cSJohn Floren break; 281e342083cSJohn Floren } 282e342083cSJohn Floren state.write_cmd = 0; 283e342083cSJohn Floren } 284e342083cSJohn Floren 285e342083cSJohn Floren static void kbd_reset(void) 286e342083cSJohn Floren { 287e342083cSJohn Floren state = (struct kbd_state) { 288e9f4d662SPekka Enberg .status = I8042_STR_MUXERR | I8042_STR_CMDDAT | I8042_STR_KEYLOCK, /* 0x1c */ 289e342083cSJohn Floren .mode = KBD_MODE_KBD_INT | KBD_MODE_SYS, /* 0x3 */ 290e342083cSJohn Floren .mres = AUX_DEFAULT_RESOLUTION, 291e342083cSJohn Floren .msample = AUX_DEFAULT_SAMPLE, 292e342083cSJohn Floren }; 293e342083cSJohn Floren } 294e342083cSJohn Floren 295f7ef3dc0SAndre Przywara static void kbd_io(struct kvm_cpu *vcpu, u64 addr, u8 *data, u32 len, 296f7ef3dc0SAndre Przywara u8 is_write, void *ptr) 297f7ef3dc0SAndre Przywara { 298f7ef3dc0SAndre Przywara u8 value; 299f7ef3dc0SAndre Przywara 300f7ef3dc0SAndre Przywara if (is_write) 301f7ef3dc0SAndre Przywara value = ioport__read8(data); 302f7ef3dc0SAndre Przywara 303f7ef3dc0SAndre Przywara switch (addr) { 304f7ef3dc0SAndre Przywara case I8042_COMMAND_REG: 305f7ef3dc0SAndre Przywara if (is_write) 306f7ef3dc0SAndre Przywara kbd_write_command(vcpu->kvm, value); 307f7ef3dc0SAndre Przywara else 308f7ef3dc0SAndre Przywara value = kbd_read_status(); 309f7ef3dc0SAndre Przywara break; 310f7ef3dc0SAndre Przywara case I8042_DATA_REG: 311f7ef3dc0SAndre Przywara if (is_write) 312f7ef3dc0SAndre Przywara kbd_write_data(value); 313f7ef3dc0SAndre Przywara else 314f7ef3dc0SAndre Przywara value = kbd_read_data(); 315f7ef3dc0SAndre Przywara break; 316f7ef3dc0SAndre Przywara case I8042_PORT_B_REG: 317f7ef3dc0SAndre Przywara if (!is_write) 318f7ef3dc0SAndre Przywara value = 0x20; 319f7ef3dc0SAndre Przywara break; 320f7ef3dc0SAndre Przywara default: 321f7ef3dc0SAndre Przywara return; 322f7ef3dc0SAndre Przywara } 323f7ef3dc0SAndre Przywara 324f7ef3dc0SAndre Przywara if (!is_write) 325f7ef3dc0SAndre Przywara ioport__write8(data, value); 326f7ef3dc0SAndre Przywara } 327f7ef3dc0SAndre Przywara 328*d24bedb1SAndre Przywara static int kbd__init(struct kvm *kvm) 329e342083cSJohn Floren { 3308f160708SAlexandru Elisei int r; 3318f160708SAlexandru Elisei 332e342083cSJohn Floren kbd_reset(); 333e342083cSJohn Floren state.kvm = kvm; 334*d24bedb1SAndre Przywara r = kvm__register_pio(kvm, I8042_DATA_REG, 2, kbd_io, NULL); 3358f160708SAlexandru Elisei if (r < 0) 3368f160708SAlexandru Elisei return r; 337*d24bedb1SAndre Przywara r = kvm__register_pio(kvm, I8042_COMMAND_REG, 2, kbd_io, NULL); 3388f160708SAlexandru Elisei if (r < 0) { 339*d24bedb1SAndre Przywara kvm__deregister_pio(kvm, I8042_DATA_REG); 3408f160708SAlexandru Elisei return r; 3418f160708SAlexandru Elisei } 342be1eb89bSSasha Levin 343be1eb89bSSasha Levin return 0; 344e342083cSJohn Floren } 34549a8afd1SSasha Levin dev_init(kbd__init); 346