xref: /openbsd-src/usr.sbin/vmd/ns8250.c (revision 24bb5fcea3ed904bc467217bdaadb5dfc618d5bf)
1 /* $OpenBSD: ns8250.c,v 1.32 2021/07/16 16:21:22 dv 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 #include <sys/ttycom.h>
20 
21 #include <dev/ic/comreg.h>
22 
23 #include <machine/vmmvar.h>
24 
25 #include <errno.h>
26 #include <event.h>
27 #include <pthread.h>
28 #include <string.h>
29 #include <unistd.h>
30 
31 #include "atomicio.h"
32 #include "ns8250.h"
33 #include "vmd.h"
34 #include "vmm.h"
35 
36 extern char *__progname;
37 struct ns8250_dev com1_dev;
38 
39 static struct vm_dev_pipe dev_pipe;
40 
41 static void com_rcv_event(int, short, void *);
42 static void com_rcv(struct ns8250_dev *, uint32_t, uint32_t);
43 
44 /*
45  * ns8250_pipe_dispatch
46  *
47  * Reads a message off the pipe, expecting a reguest to reset events after a
48  * zero-byte read from the com device.
49  */
50 static void
51 ns8250_pipe_dispatch(int fd, short event, void *arg)
52 {
53 	enum pipe_msg_type msg;
54 
55 	msg = vm_pipe_recv(&dev_pipe);
56 	switch(msg) {
57 	case NS8250_RATELIMIT:
58 		evtimer_add(&com1_dev.rate, &com1_dev.rate_tv);
59 		break;
60 	default:
61 		fatalx("%s: unexpected pipe message %d", __func__, msg);
62 	}
63 }
64 
65 /*
66  * ratelimit
67  *
68  * Timeout callback function used when we have to slow down the output rate
69  * from the emulated serial port.
70  *
71  * Parameters:
72  *  fd: unused
73  *  type: unused
74  *  arg: unused
75  */
76 static void
77 ratelimit(int fd, short type, void *arg)
78 {
79 	/* Set TXRDY and clear "no pending interrupt" */
80 	mutex_lock(&com1_dev.mutex);
81 	com1_dev.regs.iir |= IIR_TXRDY;
82 	com1_dev.regs.iir &= ~IIR_NOPEND;
83 
84 	vcpu_assert_pic_irq(com1_dev.vmid, 0, com1_dev.irq);
85 	vcpu_deassert_pic_irq(com1_dev.vmid, 0, com1_dev.irq);
86 	mutex_unlock(&com1_dev.mutex);
87 }
88 
89 void
90 ns8250_init(int fd, uint32_t vmid)
91 {
92 	int ret;
93 
94 	memset(&com1_dev, 0, sizeof(com1_dev));
95 	ret = pthread_mutex_init(&com1_dev.mutex, NULL);
96 	if (ret) {
97 		errno = ret;
98 		fatal("could not initialize com1 mutex");
99 	}
100 
101 	com1_dev.fd = fd;
102 	com1_dev.irq = 4;
103 	com1_dev.portid = NS8250_COM1;
104 	com1_dev.vmid = vmid;
105 	com1_dev.byte_out = 0;
106 	com1_dev.regs.divlo = 1;
107 	com1_dev.baudrate = 115200;
108 
109 	/*
110 	 * Our serial port is essentially instantaneous, with infinite
111 	 * baudrate capability. To adjust for the selected baudrate,
112 	 * we calculate how many characters could be transmitted in a 10ms
113 	 * period (pause_ct) and then delay 10ms after each pause_ct sized
114 	 * group of characters have been transmitted. Since it takes nearly
115 	 * zero time to send the actual characters, the total amount of time
116 	 * spent is roughly equal to what it would be on real hardware.
117 	 *
118 	 * To make things simple, we don't adjust for different sized bytes
119 	 * (and parity, stop bits, etc) and simply assume each character
120 	 * output is 8 bits.
121 	 */
122 	com1_dev.pause_ct = (com1_dev.baudrate / 8) / 1000 * 10;
123 
124 	event_set(&com1_dev.event, com1_dev.fd, EV_READ | EV_PERSIST,
125 	    com_rcv_event, (void *)(intptr_t)vmid);
126 
127 	/*
128 	 * Whenever fd is writable implies that the pty slave is connected.
129 	 * Until then, avoid waiting for read events since EOF would constantly
130 	 * be reached.
131 	 */
132 	event_set(&com1_dev.wake, com1_dev.fd, EV_WRITE,
133 	    com_rcv_event, (void *)(intptr_t)vmid);
134 	event_add(&com1_dev.wake, NULL);
135 
136 	/* Rate limiter for simulating baud rate */
137 	timerclear(&com1_dev.rate_tv);
138 	com1_dev.rate_tv.tv_usec = 10000;
139 	evtimer_set(&com1_dev.rate, ratelimit, NULL);
140 
141 	vm_pipe_init(&dev_pipe, ns8250_pipe_dispatch);
142 	event_add(&dev_pipe.read_ev, NULL);
143 }
144 
145 static void
146 com_rcv_event(int fd, short kind, void *arg)
147 {
148 	mutex_lock(&com1_dev.mutex);
149 
150 	if (kind == EV_WRITE) {
151 		event_add(&com1_dev.event, NULL);
152 		mutex_unlock(&com1_dev.mutex);
153 		return;
154 	}
155 
156 	if ((com1_dev.regs.lsr & LSR_RXRDY) == 0)
157 		com_rcv(&com1_dev, (uintptr_t)arg, 0);
158 
159 	/* If pending interrupt, inject */
160 	if ((com1_dev.regs.iir & IIR_NOPEND) == 0) {
161 		/* XXX: vcpu_id */
162 		vcpu_assert_pic_irq((uintptr_t)arg, 0, com1_dev.irq);
163 		vcpu_deassert_pic_irq((uintptr_t)arg, 0, com1_dev.irq);
164 	}
165 
166 	mutex_unlock(&com1_dev.mutex);
167 }
168 
169 /*
170  * com_rcv_handle_break
171  *
172  * Set/clear break detected condition based on received TIOCUCNTL_{S,C}BRK.
173  */
174 static int
175 com_rcv_handle_break(struct ns8250_dev *com, uint8_t cmd)
176 {
177 	switch (cmd) {
178 	case 0: /* DATA */
179 		return 0;
180 	case TIOCUCNTL_SBRK:
181 		com->regs.lsr |= LSR_BI;
182 		break;
183 	case TIOCUCNTL_CBRK:
184 		com->regs.lsr &= ~LSR_BI;
185 		break;
186 	default:
187 		log_warnx("unexpected UCNTL ioctl: %d", cmd);
188 	}
189 
190 	return 1;
191 }
192 
193 /*
194  * com_rcv
195  *
196  * Move received byte into com data register.
197  * Must be called with the mutex of the com device acquired
198  */
199 static void
200 com_rcv(struct ns8250_dev *com, uint32_t vm_id, uint32_t vcpu_id)
201 {
202 	char buf[2];
203 	ssize_t sz;
204 
205 	/*
206 	 * Is there a new character available on com1?
207 	 * If so, consume the character, buffer it into the com1 data register
208 	 * assert IRQ4, and set the line status register RXRDY bit.
209 	 */
210 	sz = read(com->fd, buf, sizeof(buf));
211 	if (sz == -1) {
212 		/*
213 		 * If we get EAGAIN, we'll retry and get the character later.
214 		 * This error can happen when typing two characters at once
215 		 * at the keyboard, for example.
216 		 */
217 		if (errno != EAGAIN)
218 			log_warn("unexpected read error on com device");
219 	} else if (sz == 0) {
220 		/* Zero read typically occurs on a disconnect */
221 		event_del(&com->event);
222 		event_add(&com->wake, NULL);
223 		return;
224 	} else if (sz != 1 && sz != 2)
225 		log_warnx("unexpected read return value %zd on com device", sz);
226 	else {
227 		if (com_rcv_handle_break(com, buf[0]))
228 			buf[1] = 0;
229 
230 		com->regs.lsr |= LSR_RXRDY;
231 		com->regs.data = buf[1];
232 
233 		if (com->regs.ier & IER_ERXRDY) {
234 			com->regs.iir |= IIR_RXRDY;
235 			com->regs.iir &= ~IIR_NOPEND;
236 		}
237 	}
238 }
239 
240 /*
241  * vcpu_process_com_data
242  *
243  * Emulate in/out instructions to the com1 (ns8250) UART data register
244  *
245  * Parameters:
246  *  vei: vm exit information from vmm(4) containing information on the in/out
247  *      instruction being performed
248  *
249  * Return value:
250  *  interrupt to inject, or 0xFF if nothing to inject
251  */
252 uint8_t
253 vcpu_process_com_data(struct vm_exit *vei, uint32_t vm_id, uint32_t vcpu_id)
254 {
255 	/*
256 	 * vei_dir == VEI_DIR_OUT : out instruction
257 	 *
258 	 * The guest wrote to the data register. Since we are emulating a
259 	 * no-fifo chip, write the character immediately to the pty and
260 	 * assert TXRDY in IIR (if the guest has requested TXRDY interrupt
261 	 * reporting)
262 	 */
263 	if (vei->vei.vei_dir == VEI_DIR_OUT) {
264 		if (com1_dev.regs.lcr & LCR_DLAB) {
265 			com1_dev.regs.divlo = vei->vei.vei_data;
266 			return 0xFF;
267 		}
268 
269 		write(com1_dev.fd, &vei->vei.vei_data, 1);
270 		com1_dev.byte_out++;
271 
272 		if (com1_dev.regs.ier & IER_ETXRDY) {
273 			/* Limit output rate if needed */
274 			if (com1_dev.pause_ct > 0 &&
275 			    com1_dev.byte_out % com1_dev.pause_ct == 0) {
276 				vm_pipe_send(&dev_pipe, NS8250_RATELIMIT);
277 			} else {
278 				/* Set TXRDY and clear "no pending interrupt" */
279 				com1_dev.regs.iir |= IIR_TXRDY;
280 				com1_dev.regs.iir &= ~IIR_NOPEND;
281 			}
282 		}
283 	} else {
284 		if (com1_dev.regs.lcr & LCR_DLAB) {
285 			set_return_data(vei, com1_dev.regs.divlo);
286 			return 0xFF;
287 		}
288 		/*
289 		 * vei_dir == VEI_DIR_IN : in instruction
290 		 *
291 		 * The guest read from the data register. Check to see if
292 		 * there is data available (RXRDY) and if so, consume the
293 		 * input data and return to the guest. Also clear the
294 		 * interrupt info register regardless.
295 		 */
296 		if (com1_dev.regs.lsr & LSR_RXRDY) {
297 			set_return_data(vei, com1_dev.regs.data);
298 			com1_dev.regs.data = 0x0;
299 			com1_dev.regs.lsr &= ~LSR_RXRDY;
300 		} else {
301 			set_return_data(vei, com1_dev.regs.data);
302 			log_warnx("%s: guest reading com1 when not ready",
303 			    __func__);
304 		}
305 
306 		/* Reading the data register always clears RXRDY from IIR */
307 		com1_dev.regs.iir &= ~IIR_RXRDY;
308 
309 		/*
310 		 * Clear "interrupt pending" by setting IIR low bit to 1
311 		 * if no interrupt are pending
312 		 */
313 		if (com1_dev.regs.iir == 0x0)
314 			com1_dev.regs.iir = 0x1;
315 	}
316 
317 	/* If pending interrupt, make sure it gets injected */
318 	if ((com1_dev.regs.iir & IIR_NOPEND) == 0)
319 		return (com1_dev.irq);
320 
321 	return (0xFF);
322 }
323 
324 /*
325  * vcpu_process_com_lcr
326  *
327  * Emulate in/out instructions to the com1 (ns8250) UART line control register
328  *
329  * Paramters:
330  *  vei: vm exit information from vmm(4) containing information on the in/out
331  *      instruction being performed
332  */
333 void
334 vcpu_process_com_lcr(struct vm_exit *vei)
335 {
336 	uint8_t data = (uint8_t)vei->vei.vei_data;
337 	uint16_t divisor;
338 
339 	/*
340 	 * vei_dir == VEI_DIR_OUT : out instruction
341 	 *
342 	 * Write content to line control register
343 	 */
344 	if (vei->vei.vei_dir == VEI_DIR_OUT) {
345 		if (com1_dev.regs.lcr & LCR_DLAB) {
346 			if (!(data & LCR_DLAB)) {
347 				if (com1_dev.regs.divlo == 0 &&
348 				    com1_dev.regs.divhi == 0) {
349 					log_warnx("%s: ignoring invalid "
350 					    "baudrate", __func__);
351 				} else {
352 					divisor = com1_dev.regs.divlo |
353 					     com1_dev.regs.divhi << 8;
354 					com1_dev.baudrate = 115200 / divisor;
355 					com1_dev.pause_ct =
356 					    (com1_dev.baudrate / 8) / 1000 * 10;
357 				}
358 
359 				log_debug("%s: set baudrate = %d", __func__,
360 				    com1_dev.baudrate);
361 			}
362 		}
363 		com1_dev.regs.lcr = (uint8_t)vei->vei.vei_data;
364 	} else {
365 		/*
366 		 * vei_dir == VEI_DIR_IN : in instruction
367 		 *
368 		 * Read line control register
369 		 */
370 		set_return_data(vei, com1_dev.regs.lcr);
371 	}
372 }
373 
374 /*
375  * vcpu_process_com_iir
376  *
377  * Emulate in/out instructions to the com1 (ns8250) UART interrupt information
378  * register. Note that writes to this register actually are to a different
379  * register, the FCR (FIFO control register) that we don't emulate but still
380  * consume the data provided.
381  *
382  * Parameters:
383  *  vei: vm exit information from vmm(4) containing information on the in/out
384  *      instruction being performed
385  */
386 void
387 vcpu_process_com_iir(struct vm_exit *vei)
388 {
389 	/*
390 	 * vei_dir == VEI_DIR_OUT : out instruction
391 	 *
392 	 * Write to FCR
393 	 */
394 	if (vei->vei.vei_dir == VEI_DIR_OUT) {
395 		com1_dev.regs.fcr = vei->vei.vei_data;
396 	} else {
397 		/*
398 		 * vei_dir == VEI_DIR_IN : in instruction
399 		 *
400 		 * Read IIR. Reading the IIR resets the TXRDY bit in the IIR
401 		 * after the data is read.
402 		 */
403 		set_return_data(vei, com1_dev.regs.iir);
404 		com1_dev.regs.iir &= ~IIR_TXRDY;
405 
406 		/*
407 		 * Clear "interrupt pending" by setting IIR low bit to 1
408 		 * if no interrupts are pending
409 		 */
410 		if (com1_dev.regs.iir == 0x0)
411 			com1_dev.regs.iir = 0x1;
412 	}
413 }
414 
415 /*
416  * vcpu_process_com_mcr
417  *
418  * Emulate in/out instructions to the com1 (ns8250) UART modem control
419  * register.
420  *
421  * Parameters:
422  *  vei: vm exit information from vmm(4) containing information on the in/out
423  *      instruction being performed
424  */
425 void
426 vcpu_process_com_mcr(struct vm_exit *vei)
427 {
428 	/*
429 	 * vei_dir == VEI_DIR_OUT : out instruction
430 	 *
431 	 * Write to MCR
432 	 */
433 	if (vei->vei.vei_dir == VEI_DIR_OUT) {
434 		com1_dev.regs.mcr = vei->vei.vei_data;
435 	} else {
436 		/*
437 		 * vei_dir == VEI_DIR_IN : in instruction
438 		 *
439 		 * Read from MCR
440 		 */
441 		set_return_data(vei, com1_dev.regs.mcr);
442 	}
443 }
444 
445 /*
446  * vcpu_process_com_lsr
447  *
448  * Emulate in/out instructions to the com1 (ns8250) UART line status register.
449  *
450  * Parameters:
451  *  vei: vm exit information from vmm(4) containing information on the in/out
452  *      instruction being performed
453  */
454 void
455 vcpu_process_com_lsr(struct vm_exit *vei)
456 {
457 	/*
458 	 * vei_dir == VEI_DIR_OUT : out instruction
459 	 *
460 	 * Write to LSR. This is an illegal operation, so we just log it and
461 	 * continue.
462 	 */
463 	if (vei->vei.vei_dir == VEI_DIR_OUT) {
464 		log_warnx("%s: LSR UART write 0x%x unsupported",
465 		    __progname, vei->vei.vei_data);
466 	} else {
467 		/*
468 		 * vei_dir == VEI_DIR_IN : in instruction
469 		 *
470 		 * Read from LSR. We always report TXRDY and TSRE since we
471 		 * can process output characters immediately (at any time).
472 		 */
473 		set_return_data(vei, com1_dev.regs.lsr | LSR_TSRE | LSR_TXRDY);
474 	}
475 }
476 
477 /*
478  * vcpu_process_com_msr
479  *
480  * Emulate in/out instructions to the com1 (ns8250) UART modem status register.
481  *
482  * Parameters:
483  *  vei: vm exit information from vmm(4) containing information on the in/out
484  *      instruction being performed
485  */
486 void
487 vcpu_process_com_msr(struct vm_exit *vei)
488 {
489 	/*
490 	 * vei_dir == VEI_DIR_OUT : out instruction
491 	 *
492 	 * Write to MSR. This is an illegal operation, so we just log it and
493 	 * continue.
494 	 */
495 	if (vei->vei.vei_dir == VEI_DIR_OUT) {
496 		log_warnx("%s: MSR UART write 0x%x unsupported",
497 		    __progname, vei->vei.vei_data);
498 	} else {
499 		/*
500 		 * vei_dir == VEI_DIR_IN : in instruction
501 		 *
502 		 * Read from MSR. We always report DCD, DSR, and CTS.
503 		 */
504 		set_return_data(vei, com1_dev.regs.lsr | MSR_DCD | MSR_DSR |
505 		    MSR_CTS);
506 	}
507 }
508 
509 /*
510  * vcpu_process_com_scr
511  *
512  * Emulate in/out instructions to the com1 (ns8250) UART scratch register.
513  *
514  * Parameters:
515  *  vei: vm exit information from vmm(4) containing information on the in/out
516  *      instruction being performed
517  */
518 void
519 vcpu_process_com_scr(struct vm_exit *vei)
520 {
521 	/*
522 	 * vei_dir == VEI_DIR_OUT : out instruction
523 	 *
524 	 * The 8250 does not have a scratch register.
525 	 */
526 	if (vei->vei.vei_dir == VEI_DIR_OUT) {
527 		com1_dev.regs.scr = 0xFF;
528 	} else {
529 		/*
530 		 * vei_dir == VEI_DIR_IN : in instruction
531 		 *
532 		 * Read from SCR
533 		 */
534 		set_return_data(vei, com1_dev.regs.scr);
535 	}
536 }
537 
538 /*
539  * vcpu_process_com_ier
540  *
541  * Emulate in/out instructions to the com1 (ns8250) UART interrupt enable
542  * register.
543  *
544  * Parameters:
545  *  vei: vm exit information from vmm(4) containing information on the in/out
546  *      instruction being performed
547  */
548 void
549 vcpu_process_com_ier(struct vm_exit *vei)
550 {
551 	/*
552 	 * vei_dir == VEI_DIR_OUT : out instruction
553 	 *
554 	 * Write to IER
555 	 */
556 	if (vei->vei.vei_dir == VEI_DIR_OUT) {
557 		if (com1_dev.regs.lcr & LCR_DLAB) {
558 			com1_dev.regs.divhi = vei->vei.vei_data;
559 			return;
560 		}
561 		com1_dev.regs.ier = vei->vei.vei_data;
562 		if (com1_dev.regs.ier & IER_ETXRDY)
563 			com1_dev.regs.iir |= IIR_TXRDY;
564 	} else {
565 		if (com1_dev.regs.lcr & LCR_DLAB) {
566 			set_return_data(vei, com1_dev.regs.divhi);
567 			return;
568 		}
569 		/*
570 		 * vei_dir == VEI_DIR_IN : in instruction
571 		 *
572 		 * Read from IER
573 		 */
574 		set_return_data(vei, com1_dev.regs.ier);
575 	}
576 }
577 
578 /*
579  * vcpu_exit_com
580  *
581  * Process com1 (ns8250) UART exits. vmd handles most basic 8250
582  * features
583  *
584  * Parameters:
585  *  vrp: vcpu run parameters containing guest state for this exit
586  *
587  * Return value:
588  *  Interrupt to inject to the guest VM, or 0xFF if no interrupt should
589  *      be injected.
590  */
591 uint8_t
592 vcpu_exit_com(struct vm_run_params *vrp)
593 {
594 	uint8_t intr = 0xFF;
595 	struct vm_exit *vei = vrp->vrp_exit;
596 
597 	mutex_lock(&com1_dev.mutex);
598 
599 	switch (vei->vei.vei_port) {
600 	case COM1_LCR:
601 		vcpu_process_com_lcr(vei);
602 		break;
603 	case COM1_IER:
604 		vcpu_process_com_ier(vei);
605 		break;
606 	case COM1_IIR:
607 		vcpu_process_com_iir(vei);
608 		break;
609 	case COM1_MCR:
610 		vcpu_process_com_mcr(vei);
611 		break;
612 	case COM1_LSR:
613 		vcpu_process_com_lsr(vei);
614 		break;
615 	case COM1_MSR:
616 		vcpu_process_com_msr(vei);
617 		break;
618 	case COM1_SCR:
619 		vcpu_process_com_scr(vei);
620 		break;
621 	case COM1_DATA:
622 		intr = vcpu_process_com_data(vei, vrp->vrp_vm_id,
623 		    vrp->vrp_vcpu_id);
624 		break;
625 	}
626 
627 	mutex_unlock(&com1_dev.mutex);
628 
629 	return (intr);
630 }
631 
632 int
633 ns8250_dump(int fd)
634 {
635 	log_debug("%s: sending UART", __func__);
636 	if (atomicio(vwrite, fd, &com1_dev.regs,
637 	    sizeof(com1_dev.regs)) != sizeof(com1_dev.regs)) {
638 		log_warnx("%s: error writing UART to fd", __func__);
639 		return (-1);
640 	}
641 	return (0);
642 }
643 
644 int
645 ns8250_restore(int fd, int con_fd, uint32_t vmid)
646 {
647 	int ret;
648 	log_debug("%s: receiving UART", __func__);
649 	if (atomicio(read, fd, &com1_dev.regs,
650 	    sizeof(com1_dev.regs)) != sizeof(com1_dev.regs)) {
651 		log_warnx("%s: error reading UART from fd", __func__);
652 		return (-1);
653 	}
654 
655 	ret = pthread_mutex_init(&com1_dev.mutex, NULL);
656 	if (ret) {
657 		errno = ret;
658 		fatal("could not initialize com1 mutex");
659 	}
660 	com1_dev.fd = con_fd;
661 	com1_dev.irq = 4;
662 	com1_dev.portid = NS8250_COM1;
663 	com1_dev.vmid = vmid;
664 	com1_dev.byte_out = 0;
665 	com1_dev.regs.divlo = 1;
666 	com1_dev.baudrate = 115200;
667 	com1_dev.pause_ct = (com1_dev.baudrate / 8) / 1000 * 10;
668 
669 	event_set(&com1_dev.event, com1_dev.fd, EV_READ | EV_PERSIST,
670 	    com_rcv_event, (void *)(intptr_t)vmid);
671 
672 	event_set(&com1_dev.wake, com1_dev.fd, EV_WRITE,
673 	    com_rcv_event, (void *)(intptr_t)vmid);
674 
675 	timerclear(&com1_dev.rate_tv);
676 	com1_dev.rate_tv.tv_usec = 10000;
677 	evtimer_set(&com1_dev.rate, ratelimit, NULL);
678 
679 	vm_pipe_init(&dev_pipe, ns8250_pipe_dispatch);
680 
681 	return (0);
682 }
683 
684 void
685 ns8250_stop()
686 {
687 	if(event_del(&com1_dev.event))
688 		log_warn("could not delete ns8250 event handler");
689 	event_del(&dev_pipe.read_ev);
690 	evtimer_del(&com1_dev.rate);
691 }
692 
693 void
694 ns8250_start()
695 {
696 	event_add(&com1_dev.event, NULL);
697 	event_add(&com1_dev.wake, NULL);
698 	event_add(&dev_pipe.read_ev, NULL);
699 	evtimer_add(&com1_dev.rate, &com1_dev.rate_tv);
700 }
701