GCC Code Coverage Report
Directory: ./ Exec Total Coverage
File: usr.sbin/vmd/ns8250.c Lines: 0 181 0.0 %
Date: 2017-11-07 Branches: 0 77 0.0 %

Line Branch Exec Source
1
/* $OpenBSD: ns8250.c,v 1.12 2017/09/15 02:35:39 mlarkin Exp $ */
2
/*
3
 * Copyright (c) 2016 Mike Larkin <mlarkin@openbsd.org>
4
 *
5
 * Permission to use, copy, modify, and distribute this software for any
6
 * purpose with or without fee is hereby granted, provided that the above
7
 * copyright notice and this permission notice appear in all copies.
8
 *
9
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16
 */
17
18
#include <sys/types.h>
19
20
#include <dev/ic/comreg.h>
21
22
#include <machine/vmmvar.h>
23
24
#include <errno.h>
25
#include <event.h>
26
#include <pthread.h>
27
#include <string.h>
28
#include <unistd.h>
29
30
#include "ns8250.h"
31
#include "proc.h"
32
#include "vmd.h"
33
#include "vmm.h"
34
#include "atomicio.h"
35
36
extern char *__progname;
37
struct ns8250_dev com1_dev;
38
39
static void com_rcv_event(int, short, void *);
40
static void com_rcv(struct ns8250_dev *, uint32_t, uint32_t);
41
42
/*
43
 * ratelimit
44
 *
45
 * Timeout callback function used when we have to slow down the output rate
46
 * from the emulated serial port.
47
 *
48
 * Parameters:
49
 *  fd: unused
50
 *  type: unused
51
 *  arg: unused
52
 */
53
static void
54
ratelimit(int fd, short type, void *arg)
55
{
56
	/* Set TXRDY and clear "no pending interrupt" */
57
	com1_dev.regs.iir |= IIR_TXRDY;
58
	com1_dev.regs.iir &= ~IIR_NOPEND;
59
	vcpu_assert_pic_irq(com1_dev.vmid, 0, com1_dev.irq);
60
}
61
62
void
63
ns8250_init(int fd, uint32_t vmid)
64
{
65
	int ret;
66
67
	memset(&com1_dev, 0, sizeof(com1_dev));
68
	ret = pthread_mutex_init(&com1_dev.mutex, NULL);
69
	if (ret) {
70
		errno = ret;
71
		fatal("could not initialize com1 mutex");
72
	}
73
	com1_dev.fd = fd;
74
	com1_dev.irq = 4;
75
	com1_dev.rcv_pending = 0;
76
	com1_dev.vmid = vmid;
77
	com1_dev.byte_out = 0;
78
	com1_dev.regs.divlo = 1;
79
	com1_dev.baudrate = 115200;
80
81
	/*
82
	 * Our serial port is essentially instantaneous, with infinite
83
	 * baudrate capability. To adjust for the selected baudrate,
84
	 * we calculate how many characters could be transmitted in a 10ms
85
	 * period (pause_ct) and then delay 10ms after each pause_ct sized
86
	 * group of characters have been transmitted. Since it takes nearly
87
	 * zero time to send the actual characters, the total amount of time
88
	 * spent is roughly equal to what it would be on real hardware.
89
	 *
90
	 * To make things simple, we don't adjust for different sized bytes
91
	 * (and parity, stop bits, etc) and simply assume each character
92
	 * output is 8 bits.
93
	 */
94
	com1_dev.pause_ct = (com1_dev.baudrate / 8) / 1000 * 10;
95
96
	event_set(&com1_dev.event, com1_dev.fd, EV_READ | EV_PERSIST,
97
	    com_rcv_event, (void *)(intptr_t)vmid);
98
	event_add(&com1_dev.event, NULL);
99
100
	/* Rate limiter for simulating baud rate */
101
	timerclear(&com1_dev.rate_tv);
102
	com1_dev.rate_tv.tv_usec = 10000;
103
	evtimer_set(&com1_dev.rate, ratelimit, NULL);
104
}
105
106
static void
107
com_rcv_event(int fd, short kind, void *arg)
108
{
109
	mutex_lock(&com1_dev.mutex);
110
111
	/*
112
	 * We already have other data pending to be received. The data that
113
	 * has become available now will be moved to the com port later.
114
	 */
115
	if (com1_dev.rcv_pending) {
116
		mutex_unlock(&com1_dev.mutex);
117
		return;
118
	}
119
120
	if (com1_dev.regs.lsr & LSR_RXRDY)
121
		com1_dev.rcv_pending = 1;
122
	else {
123
		com_rcv(&com1_dev, (uintptr_t)arg, 0);
124
125
		/* If pending interrupt, inject */
126
		if ((com1_dev.regs.iir & IIR_NOPEND) == 0) {
127
			/* XXX: vcpu_id */
128
			vcpu_assert_pic_irq((uintptr_t)arg, 0, com1_dev.irq);
129
		}
130
	}
131
132
	mutex_unlock(&com1_dev.mutex);
133
}
134
135
/*
136
 * com_rcv
137
 *
138
 * Move received byte into com data register.
139
 * Must be called with the mutex of the com device acquired
140
 */
141
static void
142
com_rcv(struct ns8250_dev *com, uint32_t vm_id, uint32_t vcpu_id)
143
{
144
	char ch;
145
	ssize_t sz;
146
147
	/*
148
	 * Is there a new character available on com1?
149
	 * If so, consume the character, buffer it into the com1 data register
150
	 * assert IRQ4, and set the line status register RXRDY bit.
151
	 */
152
	sz = read(com->fd, &ch, sizeof(char));
153
	if (sz == -1) {
154
		/*
155
		 * If we get EAGAIN, we'll retry and get the character later.
156
		 * This error can happen when typing two characters at once
157
		 * at the keyboard, for example.
158
		 */
159
		if (errno != EAGAIN)
160
			log_warn("unexpected read error on com device");
161
	} else if (sz != 1)
162
		log_warnx("unexpected read return value on com device");
163
	else {
164
		com->regs.lsr |= LSR_RXRDY;
165
		com->regs.data = ch;
166
167
		if (com->regs.ier & IER_ERXRDY) {
168
			com->regs.iir |= IIR_RXRDY;
169
			com->regs.iir &= ~IIR_NOPEND;
170
		}
171
	}
172
173
	com->rcv_pending = fd_hasdata(com->fd);
174
}
175
176
/*
177
 * vcpu_process_com_data
178
 *
179
 * Emulate in/out instructions to the com1 (ns8250) UART data register
180
 *
181
 * Parameters:
182
 *  vei: vm exit information from vmm(4) containing information on the in/out
183
 *      instruction being performed
184
 *
185
 * Return value:
186
 *  interrupt to inject, or 0xFF if nothing to inject
187
 */
188
uint8_t
189
vcpu_process_com_data(union vm_exit *vei, uint32_t vm_id, uint32_t vcpu_id)
190
{
191
	/*
192
	 * vei_dir == VEI_DIR_OUT : out instruction
193
	 *
194
	 * The guest wrote to the data register. Since we are emulating a
195
	 * no-fifo chip, write the character immediately to the pty and
196
	 * assert TXRDY in IIR (if the guest has requested TXRDY interrupt
197
	 * reporting)
198
	 */
199
	if (vei->vei.vei_dir == VEI_DIR_OUT) {
200
		if (com1_dev.regs.lcr & LCR_DLAB) {
201
			com1_dev.regs.divlo = vei->vei.vei_data;
202
			return 0xFF;
203
		}
204
205
		write(com1_dev.fd, &vei->vei.vei_data, 1);
206
		com1_dev.byte_out++;
207
208
		if (com1_dev.regs.ier & IER_ETXRDY) {
209
			/* Limit output rate if needed */
210
			if (com1_dev.byte_out % com1_dev.pause_ct == 0) {
211
				evtimer_add(&com1_dev.rate, &com1_dev.rate_tv);
212
			} else {
213
				/* Set TXRDY and clear "no pending interrupt" */
214
				com1_dev.regs.iir |= IIR_TXRDY;
215
				com1_dev.regs.iir &= ~IIR_NOPEND;
216
			}
217
		}
218
	} else {
219
		if (com1_dev.regs.lcr & LCR_DLAB) {
220
			set_return_data(vei, com1_dev.regs.divlo);
221
			return 0xFF;
222
		}
223
		/*
224
		 * vei_dir == VEI_DIR_IN : in instruction
225
		 *
226
		 * The guest read from the data register. Check to see if
227
		 * there is data available (RXRDY) and if so, consume the
228
		 * input data and return to the guest. Also clear the
229
		 * interrupt info register regardless.
230
		 */
231
		if (com1_dev.regs.lsr & LSR_RXRDY) {
232
			set_return_data(vei, com1_dev.regs.data);
233
			com1_dev.regs.data = 0x0;
234
			com1_dev.regs.lsr &= ~LSR_RXRDY;
235
		} else {
236
			set_return_data(vei, com1_dev.regs.data);
237
			log_warnx("%s: guest reading com1 when not ready", __func__);
238
		}
239
240
		/* Reading the data register always clears RXRDY from IIR */
241
		com1_dev.regs.iir &= ~IIR_RXRDY;
242
243
		/*
244
		 * Clear "interrupt pending" by setting IIR low bit to 1
245
		 * if no interrupt are pending
246
		 */
247
		if (com1_dev.regs.iir == 0x0)
248
			com1_dev.regs.iir = 0x1;
249
250
		if (com1_dev.rcv_pending)
251
			com_rcv(&com1_dev, vm_id, vcpu_id);
252
	}
253
254
	/* If pending interrupt, make sure it gets injected */
255
	if ((com1_dev.regs.iir & IIR_NOPEND) == 0)
256
		return (com1_dev.irq);
257
258
	return (0xFF);
259
}
260
261
/*
262
 * vcpu_process_com_lcr
263
 *
264
 * Emulate in/out instructions to the com1 (ns8250) UART line control register
265
 *
266
 * Paramters:
267
 *  vei: vm exit information from vmm(4) containing information on the in/out
268
 *      instruction being performed
269
 */
270
void
271
vcpu_process_com_lcr(union vm_exit *vei)
272
{
273
	uint8_t data = (uint8_t)vei->vei.vei_data;
274
	uint16_t divisor;
275
276
	/*
277
	 * vei_dir == VEI_DIR_OUT : out instruction
278
	 *
279
	 * Write content to line control register
280
	 */
281
	if (vei->vei.vei_dir == VEI_DIR_OUT) {
282
		if (com1_dev.regs.lcr & LCR_DLAB) {
283
			if (!(data & LCR_DLAB)) {
284
				if (com1_dev.regs.divlo == 0 &&
285
				    com1_dev.regs.divhi == 0) {
286
					log_warnx("%s: ignoring invalid "
287
					    "baudrate", __func__);
288
				} else {
289
					divisor = com1_dev.regs.divlo |
290
					     com1_dev.regs.divhi << 8;
291
					com1_dev.baudrate = 115200 / divisor;
292
					com1_dev.pause_ct =
293
					    (com1_dev.baudrate / 8) / 1000 * 10;
294
				}
295
296
				log_debug("%s: set baudrate = %d", __func__,
297
				    com1_dev.baudrate);
298
			}
299
		}
300
		com1_dev.regs.lcr = (uint8_t)vei->vei.vei_data;
301
	} else {
302
		/*
303
		 * vei_dir == VEI_DIR_IN : in instruction
304
		 *
305
		 * Read line control register
306
		 */
307
		set_return_data(vei, com1_dev.regs.lcr);
308
	}
309
}
310
311
/*
312
 * vcpu_process_com_iir
313
 *
314
 * Emulate in/out instructions to the com1 (ns8250) UART interrupt information
315
 * register. Note that writes to this register actually are to a different
316
 * register, the FCR (FIFO control register) that we don't emulate but still
317
 * consume the data provided.
318
 *
319
 * Parameters:
320
 *  vei: vm exit information from vmm(4) containing information on the in/out
321
 *      instruction being performed
322
 */
323
void
324
vcpu_process_com_iir(union vm_exit *vei)
325
{
326
	/*
327
	 * vei_dir == VEI_DIR_OUT : out instruction
328
	 *
329
	 * Write to FCR
330
	 */
331
	if (vei->vei.vei_dir == VEI_DIR_OUT) {
332
		com1_dev.regs.fcr = vei->vei.vei_data;
333
	} else {
334
		/*
335
		 * vei_dir == VEI_DIR_IN : in instruction
336
		 *
337
		 * Read IIR. Reading the IIR resets the TXRDY bit in the IIR
338
		 * after the data is read.
339
		 */
340
		set_return_data(vei, com1_dev.regs.iir);
341
		com1_dev.regs.iir &= ~IIR_TXRDY;
342
343
		/*
344
		 * Clear "interrupt pending" by setting IIR low bit to 1
345
		 * if no interrupts are pending
346
		 */
347
		if (com1_dev.regs.iir == 0x0)
348
			com1_dev.regs.iir = 0x1;
349
	}
350
}
351
352
/*
353
 * vcpu_process_com_mcr
354
 *
355
 * Emulate in/out instructions to the com1 (ns8250) UART modem control
356
 * register.
357
 *
358
 * Parameters:
359
 *  vei: vm exit information from vmm(4) containing information on the in/out
360
 *      instruction being performed
361
 */
362
void
363
vcpu_process_com_mcr(union vm_exit *vei)
364
{
365
	/*
366
	 * vei_dir == VEI_DIR_OUT : out instruction
367
	 *
368
	 * Write to MCR
369
	 */
370
	if (vei->vei.vei_dir == VEI_DIR_OUT) {
371
		com1_dev.regs.mcr = vei->vei.vei_data;
372
	} else {
373
		/*
374
		 * vei_dir == VEI_DIR_IN : in instruction
375
		 *
376
		 * Read from MCR
377
		 */
378
		set_return_data(vei, com1_dev.regs.mcr);
379
	}
380
}
381
382
/*
383
 * vcpu_process_com_lsr
384
 *
385
 * Emulate in/out instructions to the com1 (ns8250) UART line status register.
386
 *
387
 * Parameters:
388
 *  vei: vm exit information from vmm(4) containing information on the in/out
389
 *      instruction being performed
390
 */
391
void
392
vcpu_process_com_lsr(union vm_exit *vei)
393
{
394
	/*
395
	 * vei_dir == VEI_DIR_OUT : out instruction
396
	 *
397
	 * Write to LSR. This is an illegal operation, so we just log it and
398
	 * continue.
399
	 */
400
	if (vei->vei.vei_dir == VEI_DIR_OUT) {
401
		log_warnx("%s: LSR UART write 0x%x unsupported",
402
		    __progname, vei->vei.vei_data);
403
	} else {
404
		/*
405
		 * vei_dir == VEI_DIR_IN : in instruction
406
		 *
407
		 * Read from LSR. We always report TXRDY and TSRE since we
408
		 * can process output characters immediately (at any time).
409
		 */
410
		set_return_data(vei, com1_dev.regs.lsr | LSR_TSRE | LSR_TXRDY);
411
	}
412
}
413
414
/*
415
 * vcpu_process_com_msr
416
 *
417
 * Emulate in/out instructions to the com1 (ns8250) UART modem status register.
418
 *
419
 * Parameters:
420
 *  vei: vm exit information from vmm(4) containing information on the in/out
421
 *      instruction being performed
422
 */
423
void
424
vcpu_process_com_msr(union vm_exit *vei)
425
{
426
	/*
427
	 * vei_dir == VEI_DIR_OUT : out instruction
428
	 *
429
	 * Write to MSR. This is an illegal operation, so we just log it and
430
	 * continue.
431
	 */
432
	if (vei->vei.vei_dir == VEI_DIR_OUT) {
433
		log_warnx("%s: MSR UART write 0x%x unsupported",
434
		    __progname, vei->vei.vei_data);
435
	} else {
436
		/*
437
		 * vei_dir == VEI_DIR_IN : in instruction
438
		 *
439
		 * Read from MSR. We always report DCD, DSR, and CTS.
440
		 */
441
		set_return_data(vei, com1_dev.regs.lsr | MSR_DCD | MSR_DSR |
442
		    MSR_CTS);
443
	}
444
}
445
446
/*
447
 * vcpu_process_com_scr
448
 *
449
 * Emulate in/out instructions to the com1 (ns8250) UART scratch register.
450
 *
451
 * Parameters:
452
 *  vei: vm exit information from vmm(4) containing information on the in/out
453
 *      instruction being performed
454
 */
455
void
456
vcpu_process_com_scr(union vm_exit *vei)
457
{
458
	/*
459
	 * vei_dir == VEI_DIR_OUT : out instruction
460
	 *
461
	 * Write to SCR
462
	 */
463
	if (vei->vei.vei_dir == VEI_DIR_OUT) {
464
		com1_dev.regs.scr = vei->vei.vei_data;
465
	} else {
466
		/*
467
		 * vei_dir == VEI_DIR_IN : in instruction
468
		 *
469
		 * Read from SCR
470
		 */
471
		set_return_data(vei, com1_dev.regs.scr);
472
	}
473
}
474
475
/*
476
 * vcpu_process_com_ier
477
 *
478
 * Emulate in/out instructions to the com1 (ns8250) UART interrupt enable
479
 * register.
480
 *
481
 * Parameters:
482
 *  vei: vm exit information from vmm(4) containing information on the in/out
483
 *      instruction being performed
484
 */
485
void
486
vcpu_process_com_ier(union vm_exit *vei)
487
{
488
	/*
489
	 * vei_dir == VEI_DIR_OUT : out instruction
490
	 *
491
	 * Write to IER
492
	 */
493
	if (vei->vei.vei_dir == VEI_DIR_OUT) {
494
		if (com1_dev.regs.lcr & LCR_DLAB) {
495
			com1_dev.regs.divhi = vei->vei.vei_data;
496
			return;
497
		}
498
		com1_dev.regs.ier = vei->vei.vei_data;
499
		if (com1_dev.regs.ier & IER_ETXRDY)
500
			com1_dev.regs.iir |= IIR_TXRDY;
501
	} else {
502
		if (com1_dev.regs.lcr & LCR_DLAB) {
503
			set_return_data(vei, com1_dev.regs.divhi);
504
			return;
505
		}
506
		/*
507
		 * vei_dir == VEI_DIR_IN : in instruction
508
		 *
509
		 * Read from IER
510
		 */
511
		set_return_data(vei, com1_dev.regs.ier);
512
	}
513
}
514
515
/*
516
 * vcpu_exit_com
517
 *
518
 * Process com1 (ns8250) UART exits. vmd handles most basic 8250
519
 * features
520
 *
521
 * Parameters:
522
 *  vrp: vcpu run parameters containing guest state for this exit
523
 *
524
 * Return value:
525
 *  Interrupt to inject to the guest VM, or 0xFF if no interrupt should
526
 *      be injected.
527
 */
528
uint8_t
529
vcpu_exit_com(struct vm_run_params *vrp)
530
{
531
	uint8_t intr = 0xFF;
532
	union vm_exit *vei = vrp->vrp_exit;
533
534
	mutex_lock(&com1_dev.mutex);
535
536
	switch (vei->vei.vei_port) {
537
	case COM1_LCR:
538
		vcpu_process_com_lcr(vei);
539
		break;
540
	case COM1_IER:
541
		vcpu_process_com_ier(vei);
542
		break;
543
	case COM1_IIR:
544
		vcpu_process_com_iir(vei);
545
		break;
546
	case COM1_MCR:
547
		vcpu_process_com_mcr(vei);
548
		break;
549
	case COM1_LSR:
550
		vcpu_process_com_lsr(vei);
551
		break;
552
	case COM1_MSR:
553
		vcpu_process_com_msr(vei);
554
		break;
555
	case COM1_SCR:
556
		vcpu_process_com_scr(vei);
557
		break;
558
	case COM1_DATA:
559
		intr = vcpu_process_com_data(vei, vrp->vrp_vm_id,
560
		    vrp->vrp_vcpu_id);
561
		break;
562
	}
563
564
	mutex_unlock(&com1_dev.mutex);
565
566
	if ((com1_dev.regs.iir & IIR_NOPEND)) {
567
		/* XXX: vcpu_id */
568
		vcpu_deassert_pic_irq(com1_dev.vmid, 0, com1_dev.irq);
569
	}
570
571
	return (intr);
572
}
573
574
int
575
ns8250_dump(int fd)
576
{
577
	log_debug("%s: sending UART", __func__);
578
	if (atomicio(vwrite, fd, &com1_dev.regs,
579
	    sizeof(com1_dev.regs)) != sizeof(com1_dev.regs)) {
580
		log_warnx("%s: error writing UART to fd", __func__);
581
		return (-1);
582
	}
583
	return (0);
584
}
585
586
int
587
ns8250_restore(int fd, int con_fd, uint32_t vmid)
588
{
589
	int ret;
590
	log_debug("%s: receiving UART", __func__);
591
	if (atomicio(read, fd, &com1_dev.regs,
592
	    sizeof(com1_dev.regs)) != sizeof(com1_dev.regs)) {
593
		log_warnx("%s: error reading UART from fd", __func__);
594
		return (-1);
595
	}
596
597
	ret = pthread_mutex_init(&com1_dev.mutex, NULL);
598
	if (ret) {
599
		errno = ret;
600
		fatal("could not initialize com1 mutex");
601
	}
602
	com1_dev.fd = con_fd;
603
	com1_dev.irq = 4;
604
	com1_dev.rcv_pending = 0;
605
	com1_dev.vmid = vmid;
606
	com1_dev.byte_out = 0;
607
	com1_dev.regs.divlo = 1;
608
	com1_dev.baudrate = 115200;
609
	com1_dev.rate_tv.tv_usec = 10000;
610
	com1_dev.pause_ct = (com1_dev.baudrate / 8) / 1000 * 10;
611
	evtimer_set(&com1_dev.rate, ratelimit, NULL);
612
613
	event_set(&com1_dev.event, com1_dev.fd, EV_READ | EV_PERSIST,
614
	    com_rcv_event, (void *)(intptr_t)vmid);
615
	event_add(&com1_dev.event, NULL);
616
	return (0);
617
}