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 */
kbd_update_irq(void)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 */
mouse_queue(u8 c)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 */
kbd_queue(u8 c)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
kbd_write_command(struct kvm * kvm,u8 val)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 */
kbd_read_data(void)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 */
kbd_read_status(void)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 */
kbd_write_data(u8 val)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
kbd_reset(void)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
kbd_io(struct kvm_cpu * vcpu,u64 addr,u8 * data,u32 len,u8 is_write,void * ptr)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 {
298*5a9cde65Shbuxiaofei u8 value = 0;
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
kbd__init(struct kvm * kvm)328d24bedb1SAndre 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;
334d24bedb1SAndre Przywara r = kvm__register_pio(kvm, I8042_DATA_REG, 2, kbd_io, NULL);
3358f160708SAlexandru Elisei if (r < 0)
3368f160708SAlexandru Elisei return r;
337d24bedb1SAndre Przywara r = kvm__register_pio(kvm, I8042_COMMAND_REG, 2, kbd_io, NULL);
3388f160708SAlexandru Elisei if (r < 0) {
339d24bedb1SAndre 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