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