xref: /openbsd-src/usr.sbin/vmd/ns8250.c (revision f2da64fbbbf1b03f09f390ab01267c93dfd77c4c)
1 /*
2  * Copyright (c) 2016 Mike Larkin <mlarkin@openbsd.org>
3  *
4  * Permission to use, copy, modify, and distribute this software for any
5  * purpose with or without fee is hereby granted, provided that the above
6  * copyright notice and this permission notice appear in all copies.
7  *
8  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15  */
16 
17 #include <sys/types.h>
18 
19 #include <dev/ic/comreg.h>
20 
21 #include <machine/vmmvar.h>
22 
23 #include <errno.h>
24 #include <event.h>
25 #include <pthread.h>
26 #include <string.h>
27 #include <unistd.h>
28 
29 #include "ns8250.h"
30 #include "proc.h"
31 #include "vmd.h"
32 #include "vmm.h"
33 
34 extern char *__progname;
35 struct ns8250_dev com1_dev;
36 
37 static void com_rcv_event(int, short, void *);
38 static void com_rcv(struct ns8250_dev *, uint32_t, uint32_t);
39 
40 void
41 ns8250_init(int fd, uint32_t vmid)
42 {
43 	int ret;
44 
45 	memset(&com1_dev, 0, sizeof(com1_dev));
46 	ret = pthread_mutex_init(&com1_dev.mutex, NULL);
47 	if (ret) {
48 		errno = ret;
49 		fatal("could not initialize com1 mutex");
50 	}
51 	com1_dev.fd = fd;
52 	com1_dev.irq = 4;
53 	com1_dev.rcv_pending = 0;
54 
55 	event_set(&com1_dev.event, com1_dev.fd, EV_READ | EV_PERSIST,
56 	    com_rcv_event, (void *)(uint64_t)vmid);
57 	event_add(&com1_dev.event, NULL);
58 }
59 
60 static void
61 com_rcv_event(int fd, short kind, void *arg)
62 {
63 	mutex_lock(&com1_dev.mutex);
64 
65 	/*
66 	 * We already have other data pending to be received. The data that
67 	 * has become available now will be moved to the com port later.
68 	 */
69 	if (com1_dev.rcv_pending) {
70 		mutex_unlock(&com1_dev.mutex);
71 		return;
72 	}
73 
74 	if (com1_dev.regs.lsr & LSR_RXRDY)
75 		com1_dev.rcv_pending = 1;
76 	else {
77 		com_rcv(&com1_dev, (uintptr_t)arg, 0);
78 
79 		/* If pending interrupt, inject */
80 		if ((com1_dev.regs.iir & 0x1) == 0) {
81 			/* XXX: vcpu_id */
82 			vcpu_assert_pic_irq((uintptr_t)arg, 0, com1_dev.irq);
83 		}
84 	}
85 
86 	mutex_unlock(&com1_dev.mutex);
87 }
88 
89 /*
90  * com_rcv
91  *
92  * Move received byte into com data register.
93  * Must be called with the mutex of the com device acquired
94  */
95 static void
96 com_rcv(struct ns8250_dev *com, uint32_t vm_id, uint32_t vcpu_id)
97 {
98 	char ch;
99 	ssize_t sz;
100 
101 	/*
102 	 * Is there a new character available on com1?
103 	 * If so, consume the character, buffer it into the com1 data register
104 	 * assert IRQ4, and set the line status register RXRDY bit.
105 	 */
106 	sz = read(com->fd, &ch, sizeof(char));
107 	if (sz == -1) {
108 		/*
109 		 * If we get EAGAIN, we'll retry and get the character later.
110 		 * This error can happen when typing two characters at once
111 		 * at the keyboard, for example.
112 		 */
113 		if (errno != EAGAIN)
114 			log_warn("unexpected read error on com device");
115 	} else if (sz != 1)
116 		log_warnx("unexpected read return value on com device");
117 	else {
118 		com->regs.lsr |= LSR_RXRDY;
119 		com->regs.data = ch;
120 		/* XXX these ier and iir bits should be IER_x and IIR_x */
121 		if (com->regs.ier & 0x1) {
122 			com->regs.iir |= (2 << 1);
123 			com->regs.iir &= ~0x1;
124 		}
125 	}
126 
127 	com->rcv_pending = fd_hasdata(com->fd);
128 }
129 
130 
131 /*
132  * vcpu_process_com_data
133  *
134  * Emulate in/out instructions to the com1 (ns8250) UART data register
135  *
136  * Parameters:
137  *  vei: vm exit information from vmm(4) containing information on the in/out
138  *      instruction being performed
139  *
140  * Return value:
141  *  interrupt to inject, or 0xFF if nothing to inject
142  */
143 uint8_t
144 vcpu_process_com_data(union vm_exit *vei, uint32_t vm_id, uint32_t vcpu_id)
145 {
146 	/*
147 	 * vei_dir == VEI_DIR_OUT : out instruction
148 	 *
149 	 * The guest wrote to the data register. Since we are emulating a
150 	 * no-fifo chip, write the character immediately to the pty and
151 	 * assert TXRDY in IIR (if the guest has requested TXRDY interrupt
152 	 * reporting)
153 	 */
154 	if (vei->vei.vei_dir == VEI_DIR_OUT) {
155 		write(com1_dev.fd, &vei->vei.vei_data, 1);
156 		if (com1_dev.regs.ier & 0x2) {
157 			/* Set TXRDY */
158 			com1_dev.regs.iir |= IIR_TXRDY;
159 			/* Set "interrupt pending" (IIR low bit cleared) */
160 			com1_dev.regs.iir &= ~0x1;
161 		}
162 	} else {
163 		/*
164 		 * vei_dir == VEI_DIR_IN : in instruction
165 		 *
166 		 * The guest read from the data register. Check to see if
167 		 * there is data available (RXRDY) and if so, consume the
168 		 * input data and return to the guest. Also clear the
169 		 * interrupt info register regardless.
170 		 */
171 		if (com1_dev.regs.lsr & LSR_RXRDY) {
172 			vei->vei.vei_data = com1_dev.regs.data;
173 			com1_dev.regs.data = 0x0;
174 			com1_dev.regs.lsr &= ~LSR_RXRDY;
175 		} else {
176 			/* XXX should this be com1_dev.data or 0xff? */
177 			vei->vei.vei_data = com1_dev.regs.data;
178 			log_warnx("guest reading com1 when not ready");
179 		}
180 
181 		/* Reading the data register always clears RXRDY from IIR */
182 		com1_dev.regs.iir &= ~IIR_RXRDY;
183 
184 		/*
185 		 * Clear "interrupt pending" by setting IIR low bit to 1
186 		 * if no interrupt are pending
187 		 */
188 		if (com1_dev.regs.iir == 0x0)
189 			com1_dev.regs.iir = 0x1;
190 
191 		if (com1_dev.rcv_pending)
192 			com_rcv(&com1_dev, vm_id, vcpu_id);
193 	}
194 
195 	/* If pending interrupt, make sure it gets injected */
196 	if ((com1_dev.regs.iir & 0x1) == 0)
197 		return (com1_dev.irq);
198 	return (0xFF);
199 }
200 
201 /*
202  * vcpu_process_com_lcr
203  *
204  * Emulate in/out instructions to the com1 (ns8250) UART line control register
205  *
206  * Paramters:
207  *  vei: vm exit information from vmm(4) containing information on the in/out
208  *      instruction being performed
209  */
210 void
211 vcpu_process_com_lcr(union vm_exit *vei)
212 {
213 	/*
214 	 * vei_dir == VEI_DIR_OUT : out instruction
215 	 *
216 	 * Write content to line control register
217 	 */
218 	if (vei->vei.vei_dir == VEI_DIR_OUT) {
219 		com1_dev.regs.lcr = (uint8_t)vei->vei.vei_data;
220 	} else {
221 		/*
222 		 * vei_dir == VEI_DIR_IN : in instruction
223 		 *
224 		 * Read line control register
225 		 */
226 		vei->vei.vei_data = com1_dev.regs.lcr;
227 	}
228 }
229 
230 /*
231  * vcpu_process_com_iir
232  *
233  * Emulate in/out instructions to the com1 (ns8250) UART interrupt information
234  * register. Note that writes to this register actually are to a different
235  * register, the FCR (FIFO control register) that we don't emulate but still
236  * consume the data provided.
237  *
238  * Parameters:
239  *  vei: vm exit information from vmm(4) containing information on the in/out
240  *      instruction being performed
241  */
242 void
243 vcpu_process_com_iir(union vm_exit *vei)
244 {
245 	/*
246 	 * vei_dir == VEI_DIR_OUT : out instruction
247 	 *
248 	 * Write to FCR
249 	 */
250 	if (vei->vei.vei_dir == VEI_DIR_OUT) {
251 		com1_dev.regs.fcr = vei->vei.vei_data;
252 	} else {
253 		/*
254 		 * vei_dir == VEI_DIR_IN : in instruction
255 		 *
256 		 * Read IIR. Reading the IIR resets the TXRDY bit in the IIR
257 		 * after the data is read.
258 		 */
259 		vei->vei.vei_data = com1_dev.regs.iir;
260 		com1_dev.regs.iir &= ~IIR_TXRDY;
261 
262 		/*
263 		 * Clear "interrupt pending" by setting IIR low bit to 1
264 		 * if no interrupts are pending
265 		 */
266 		if (com1_dev.regs.iir == 0x0)
267 			com1_dev.regs.iir = 0x1;
268 	}
269 }
270 
271 /*
272  * vcpu_process_com_mcr
273  *
274  * Emulate in/out instructions to the com1 (ns8250) UART modem control
275  * register.
276  *
277  * Parameters:
278  *  vei: vm exit information from vmm(4) containing information on the in/out
279  *      instruction being performed
280  */
281 void
282 vcpu_process_com_mcr(union vm_exit *vei)
283 {
284 	/*
285 	 * vei_dir == VEI_DIR_OUT : out instruction
286 	 *
287 	 * Write to MCR
288 	 */
289 	if (vei->vei.vei_dir == VEI_DIR_OUT) {
290 		com1_dev.regs.mcr = vei->vei.vei_data;
291 	} else {
292 		/*
293 		 * vei_dir == VEI_DIR_IN : in instruction
294 		 *
295 		 * Read from MCR
296 		 */
297 		vei->vei.vei_data = com1_dev.regs.mcr;
298 	}
299 }
300 
301 /*
302  * vcpu_process_com_lsr
303  *
304  * Emulate in/out instructions to the com1 (ns8250) UART line status register.
305  *
306  * Parameters:
307  *  vei: vm exit information from vmm(4) containing information on the in/out
308  *      instruction being performed
309  */
310 void
311 vcpu_process_com_lsr(union vm_exit *vei)
312 {
313 	/*
314 	 * vei_dir == VEI_DIR_OUT : out instruction
315 	 *
316 	 * Write to LSR. This is an illegal operation, so we just log it and
317 	 * continue.
318 	 */
319 	if (vei->vei.vei_dir == VEI_DIR_OUT) {
320 		log_warnx("%s: LSR UART write 0x%x unsupported",
321 		    __progname, vei->vei.vei_data);
322 	} else {
323 		/*
324 		 * vei_dir == VEI_DIR_IN : in instruction
325 		 *
326 		 * Read from LSR. We always report TXRDY and TSRE since we
327 		 * can process output characters immediately (at any time).
328 		 */
329 		vei->vei.vei_data = com1_dev.regs.lsr | LSR_TSRE | LSR_TXRDY;
330 	}
331 }
332 
333 /*
334  * vcpu_process_com_msr
335  *
336  * Emulate in/out instructions to the com1 (ns8250) UART modem status register.
337  *
338  * Parameters:
339  *  vei: vm exit information from vmm(4) containing information on the in/out
340  *      instruction being performed
341  */
342 void
343 vcpu_process_com_msr(union vm_exit *vei)
344 {
345 	/*
346 	 * vei_dir == VEI_DIR_OUT : out instruction
347 	 *
348 	 * Write to MSR. This is an illegal operation, so we just log it and
349 	 * continue.
350 	 */
351 	if (vei->vei.vei_dir == VEI_DIR_OUT) {
352 		log_warnx("%s: MSR UART write 0x%x unsupported",
353 		    __progname, vei->vei.vei_data);
354 	} else {
355 		/*
356 		 * vei_dir == VEI_DIR_IN : in instruction
357 		 *
358 		 * Read from MSR. We always report DCD, DSR, and CTS.
359 		 */
360 		vei->vei.vei_data =
361 		    com1_dev.regs.lsr | MSR_DCD | MSR_DSR | MSR_CTS;
362 	}
363 }
364 
365 /*
366  * vcpu_process_com_scr
367  *
368  * Emulate in/out instructions to the com1 (ns8250) UART scratch register. The
369  * scratch register is sometimes used to distinguish an 8250 from a 16450,
370  * and/or used to distinguish submodels of the 8250 (eg 8250A, 8250B). We
371  * simulate an "original" 8250 by forcing the scratch register to return data
372  * on read that is different from what was written.
373  *
374  * Parameters:
375  *  vei: vm exit information from vmm(4) containing information on the in/out
376  *      instruction being performed
377  */
378 void
379 vcpu_process_com_scr(union vm_exit *vei)
380 {
381 	/*
382 	 * vei_dir == VEI_DIR_OUT : out instruction
383 	 *
384 	 * Write to SCR
385 	 */
386 	if (vei->vei.vei_dir == VEI_DIR_OUT) {
387 		com1_dev.regs.scr = vei->vei.vei_data;
388 	} else {
389 		/*
390 		 * vei_dir == VEI_DIR_IN : in instruction
391 		 *
392 		 * Read from SCR. To make sure we don't accidentally simulate
393 		 * a real scratch register, we negate what was written on
394 		 * subsequent readback.
395 		 */
396 		vei->vei.vei_data = ~com1_dev.regs.scr;
397 	}
398 }
399 
400 /*
401  * vcpu_process_com_ier
402  *
403  * Emulate in/out instructions to the com1 (ns8250) UART interrupt enable
404  * register.
405  *
406  * Parameters:
407  *  vei: vm exit information from vmm(4) containing information on the in/out
408  *      instruction being performed
409  */
410 void
411 vcpu_process_com_ier(union vm_exit *vei)
412 {
413 	/*
414 	 * vei_dir == VEI_DIR_OUT : out instruction
415 	 *
416 	 * Write to IER
417 	 */
418 	if (vei->vei.vei_dir == VEI_DIR_OUT) {
419 		com1_dev.regs.ier = vei->vei.vei_data;
420 	} else {
421 		/*
422 		 * vei_dir == VEI_DIR_IN : in instruction
423 		 *
424 		 * Read from IER
425 		 */
426 		vei->vei.vei_data = com1_dev.regs.ier;
427 	}
428 }
429 
430 /*
431  * vcpu_exit_com
432  *
433  * Process com1 (ns8250) UART exits. vmd handles most basic 8250
434  * features with the exception of the divisor latch (eg, no baud
435  * rate support)
436  *
437  * Parameters:
438  *  vrp: vcpu run parameters containing guest state for this exit
439  *
440  * Return value:
441  *  Interrupt to inject to the guest VM, or 0xFF if no interrupt should
442  *      be injected.
443  */
444 uint8_t
445 vcpu_exit_com(struct vm_run_params *vrp)
446 {
447 	uint8_t intr = 0xFF;
448 	union vm_exit *vei = vrp->vrp_exit;
449 
450 	mutex_lock(&com1_dev.mutex);
451 
452 	switch (vei->vei.vei_port) {
453 	case COM1_LCR:
454 		vcpu_process_com_lcr(vei);
455 		break;
456 	case COM1_IER:
457 		vcpu_process_com_ier(vei);
458 		break;
459 	case COM1_IIR:
460 		vcpu_process_com_iir(vei);
461 		break;
462 	case COM1_MCR:
463 		vcpu_process_com_mcr(vei);
464 		break;
465 	case COM1_LSR:
466 		vcpu_process_com_lsr(vei);
467 		break;
468 	case COM1_MSR:
469 		vcpu_process_com_msr(vei);
470 		break;
471 	case COM1_SCR:
472 		vcpu_process_com_scr(vei);
473 		break;
474 	case COM1_DATA:
475 		intr = vcpu_process_com_data(vei, vrp->vrp_vm_id,
476 		    vrp->vrp_vcpu_id);
477 		break;
478 	}
479 
480 	mutex_unlock(&com1_dev.mutex);
481 	return (intr);
482 }
483