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