xref: /openbsd-src/usr.sbin/vmd/ns8250.c (revision c4fd4c5b29fc2f24970f3ce1ba4877296028afcf)
1*c4fd4c5bSdv /* $OpenBSD: ns8250.c,v 1.40 2024/07/10 09:27:33 dv Exp $ */
2ecc93de1Smlarkin /*
3ecc93de1Smlarkin  * Copyright (c) 2016 Mike Larkin <mlarkin@openbsd.org>
4ecc93de1Smlarkin  *
5ecc93de1Smlarkin  * Permission to use, copy, modify, and distribute this software for any
6ecc93de1Smlarkin  * purpose with or without fee is hereby granted, provided that the above
7ecc93de1Smlarkin  * copyright notice and this permission notice appear in all copies.
8ecc93de1Smlarkin  *
9ecc93de1Smlarkin  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10ecc93de1Smlarkin  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11ecc93de1Smlarkin  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12ecc93de1Smlarkin  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13ecc93de1Smlarkin  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14ecc93de1Smlarkin  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15ecc93de1Smlarkin  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16ecc93de1Smlarkin  */
17ecc93de1Smlarkin 
18ecc93de1Smlarkin #include <sys/types.h>
190077bcdfSmpi #include <sys/ttycom.h>
20ecc93de1Smlarkin 
21ecc93de1Smlarkin #include <dev/ic/comreg.h>
22ba66f564Sdv #include <dev/vmm/vmm.h>
23ecc93de1Smlarkin 
241bce73e1Sstefan #include <errno.h>
251bce73e1Sstefan #include <event.h>
261bce73e1Sstefan #include <pthread.h>
27ecc93de1Smlarkin #include <string.h>
28ecc93de1Smlarkin #include <unistd.h>
29ecc93de1Smlarkin 
306eb4c859Sdv #include "atomicio.h"
31ecc93de1Smlarkin #include "ns8250.h"
321bce73e1Sstefan #include "vmd.h"
33ecc93de1Smlarkin 
34ecc93de1Smlarkin extern char *__progname;
351bce73e1Sstefan struct ns8250_dev com1_dev;
361bce73e1Sstefan 
3708fd0ce3Spd static struct vm_dev_pipe dev_pipe;
3808fd0ce3Spd 
391bce73e1Sstefan static void com_rcv_event(int, short, void *);
4039d68386Sdv static void com_rcv(struct ns8250_dev *, uint32_t, uint32_t);
4108fd0ce3Spd 
4208fd0ce3Spd /*
4308fd0ce3Spd  * ns8250_pipe_dispatch
4408fd0ce3Spd  *
4508fd0ce3Spd  * Reads a message off the pipe, expecting a reguest to reset events after a
4608fd0ce3Spd  * zero-byte read from the com device.
4708fd0ce3Spd  */
4808fd0ce3Spd static void
ns8250_pipe_dispatch(int fd,short event,void * arg)4908fd0ce3Spd ns8250_pipe_dispatch(int fd, short event, void *arg)
5008fd0ce3Spd {
5108fd0ce3Spd 	enum pipe_msg_type msg;
5208fd0ce3Spd 
5308fd0ce3Spd 	msg = vm_pipe_recv(&dev_pipe);
5408fd0ce3Spd 	switch(msg) {
5508fd0ce3Spd 	case NS8250_RATELIMIT:
5608fd0ce3Spd 		evtimer_add(&com1_dev.rate, &com1_dev.rate_tv);
5708fd0ce3Spd 		break;
5808fd0ce3Spd 	default:
5908fd0ce3Spd 		fatalx("%s: unexpected pipe message %d", __func__, msg);
6008fd0ce3Spd 	}
6108fd0ce3Spd }
62ecc93de1Smlarkin 
632dda72b8Smlarkin /*
642dda72b8Smlarkin  * ratelimit
652dda72b8Smlarkin  *
662dda72b8Smlarkin  * Timeout callback function used when we have to slow down the output rate
672dda72b8Smlarkin  * from the emulated serial port.
682dda72b8Smlarkin  *
692dda72b8Smlarkin  * Parameters:
702dda72b8Smlarkin  *  fd: unused
712dda72b8Smlarkin  *  type: unused
722dda72b8Smlarkin  *  arg: unused
732dda72b8Smlarkin  */
742dda72b8Smlarkin static void
ratelimit(int fd,short type,void * arg)752dda72b8Smlarkin ratelimit(int fd, short type, void *arg)
762dda72b8Smlarkin {
772dda72b8Smlarkin 	/* Set TXRDY and clear "no pending interrupt" */
783862707aSpd 	mutex_lock(&com1_dev.mutex);
792dda72b8Smlarkin 	com1_dev.regs.iir |= IIR_TXRDY;
802dda72b8Smlarkin 	com1_dev.regs.iir &= ~IIR_NOPEND;
813862707aSpd 
82*c4fd4c5bSdv 	vcpu_assert_irq(com1_dev.vmid, 0, com1_dev.irq);
833862707aSpd 	mutex_unlock(&com1_dev.mutex);
842dda72b8Smlarkin }
852dda72b8Smlarkin 
86ecc93de1Smlarkin void
ns8250_init(int fd,uint32_t vmid)871bce73e1Sstefan ns8250_init(int fd, uint32_t vmid)
88ecc93de1Smlarkin {
891bce73e1Sstefan 	int ret;
901bce73e1Sstefan 
911bce73e1Sstefan 	memset(&com1_dev, 0, sizeof(com1_dev));
921bce73e1Sstefan 	ret = pthread_mutex_init(&com1_dev.mutex, NULL);
931bce73e1Sstefan 	if (ret) {
941bce73e1Sstefan 		errno = ret;
951bce73e1Sstefan 		fatal("could not initialize com1 mutex");
96ecc93de1Smlarkin 	}
9708fd0ce3Spd 
981bce73e1Sstefan 	com1_dev.fd = fd;
991bce73e1Sstefan 	com1_dev.irq = 4;
1005837d48bSmlarkin 	com1_dev.portid = NS8250_COM1;
1012dda72b8Smlarkin 	com1_dev.vmid = vmid;
1022dda72b8Smlarkin 	com1_dev.byte_out = 0;
1032dda72b8Smlarkin 	com1_dev.regs.divlo = 1;
1042dda72b8Smlarkin 	com1_dev.baudrate = 115200;
1052dda72b8Smlarkin 
1062dda72b8Smlarkin 	/*
1072dda72b8Smlarkin 	 * Our serial port is essentially instantaneous, with infinite
1082dda72b8Smlarkin 	 * baudrate capability. To adjust for the selected baudrate,
1092dda72b8Smlarkin 	 * we calculate how many characters could be transmitted in a 10ms
1102dda72b8Smlarkin 	 * period (pause_ct) and then delay 10ms after each pause_ct sized
1112dda72b8Smlarkin 	 * group of characters have been transmitted. Since it takes nearly
1122dda72b8Smlarkin 	 * zero time to send the actual characters, the total amount of time
1132dda72b8Smlarkin 	 * spent is roughly equal to what it would be on real hardware.
1142dda72b8Smlarkin 	 *
1152dda72b8Smlarkin 	 * To make things simple, we don't adjust for different sized bytes
1162dda72b8Smlarkin 	 * (and parity, stop bits, etc) and simply assume each character
1172dda72b8Smlarkin 	 * output is 8 bits.
1182dda72b8Smlarkin 	 */
1192dda72b8Smlarkin 	com1_dev.pause_ct = (com1_dev.baudrate / 8) / 1000 * 10;
1201bce73e1Sstefan 
1211bce73e1Sstefan 	event_set(&com1_dev.event, com1_dev.fd, EV_READ | EV_PERSIST,
1229dae6e67Smlarkin 	    com_rcv_event, (void *)(intptr_t)vmid);
123783667e4Santon 
124783667e4Santon 	/*
125783667e4Santon 	 * Whenever fd is writable implies that the pty slave is connected.
126783667e4Santon 	 * Until then, avoid waiting for read events since EOF would constantly
127783667e4Santon 	 * be reached.
128783667e4Santon 	 */
129783667e4Santon 	event_set(&com1_dev.wake, com1_dev.fd, EV_WRITE,
130783667e4Santon 	    com_rcv_event, (void *)(intptr_t)vmid);
131783667e4Santon 	event_add(&com1_dev.wake, NULL);
1322dda72b8Smlarkin 
1332dda72b8Smlarkin 	/* Rate limiter for simulating baud rate */
1342dda72b8Smlarkin 	timerclear(&com1_dev.rate_tv);
1352dda72b8Smlarkin 	com1_dev.rate_tv.tv_usec = 10000;
1362dda72b8Smlarkin 	evtimer_set(&com1_dev.rate, ratelimit, NULL);
13708fd0ce3Spd 
13808fd0ce3Spd 	vm_pipe_init(&dev_pipe, ns8250_pipe_dispatch);
13908fd0ce3Spd 	event_add(&dev_pipe.read_ev, NULL);
1401bce73e1Sstefan }
1411bce73e1Sstefan 
1421bce73e1Sstefan static void
com_rcv_event(int fd,short kind,void * arg)1431bce73e1Sstefan com_rcv_event(int fd, short kind, void *arg)
1441bce73e1Sstefan {
1451bce73e1Sstefan 	mutex_lock(&com1_dev.mutex);
1461bce73e1Sstefan 
147783667e4Santon 	if (kind == EV_WRITE) {
148783667e4Santon 		event_add(&com1_dev.event, NULL);
149783667e4Santon 		mutex_unlock(&com1_dev.mutex);
150783667e4Santon 		return;
151783667e4Santon 	}
152783667e4Santon 
1536d77afb4Sdv 	if ((com1_dev.regs.lsr & LSR_RXRDY) == 0)
15439d68386Sdv 		com_rcv(&com1_dev, (uintptr_t)arg, 0);
1551bce73e1Sstefan 
1561bce73e1Sstefan 	/* If pending interrupt, inject */
1572dda72b8Smlarkin 	if ((com1_dev.regs.iir & IIR_NOPEND) == 0) {
1581bce73e1Sstefan 		/* XXX: vcpu_id */
159*c4fd4c5bSdv 		vcpu_assert_irq((uintptr_t)arg, 0, com1_dev.irq);
1601bce73e1Sstefan 	}
1611bce73e1Sstefan 
1621bce73e1Sstefan 	mutex_unlock(&com1_dev.mutex);
1631bce73e1Sstefan }
1641bce73e1Sstefan 
1651bce73e1Sstefan /*
1660077bcdfSmpi  * com_rcv_handle_break
1670077bcdfSmpi  *
1685b86616bSanton  * Set/clear break detected condition based on received TIOCUCNTL_{S,C}BRK.
1690077bcdfSmpi  */
1700077bcdfSmpi static int
com_rcv_handle_break(struct ns8250_dev * com,uint8_t cmd)1710077bcdfSmpi com_rcv_handle_break(struct ns8250_dev *com, uint8_t cmd)
1720077bcdfSmpi {
1730077bcdfSmpi 	switch (cmd) {
1740077bcdfSmpi 	case 0: /* DATA */
1750077bcdfSmpi 		return 0;
1760077bcdfSmpi 	case TIOCUCNTL_SBRK:
1770077bcdfSmpi 		com->regs.lsr |= LSR_BI;
1780077bcdfSmpi 		break;
1790077bcdfSmpi 	case TIOCUCNTL_CBRK:
1800077bcdfSmpi 		com->regs.lsr &= ~LSR_BI;
1810077bcdfSmpi 		break;
1820077bcdfSmpi 	default:
1830077bcdfSmpi 		log_warnx("unexpected UCNTL ioctl: %d", cmd);
1840077bcdfSmpi 	}
1850077bcdfSmpi 
1860077bcdfSmpi 	return 1;
1870077bcdfSmpi }
1880077bcdfSmpi 
1890077bcdfSmpi /*
1901bce73e1Sstefan  * com_rcv
1911bce73e1Sstefan  *
1921bce73e1Sstefan  * Move received byte into com data register.
1931bce73e1Sstefan  * Must be called with the mutex of the com device acquired
1941bce73e1Sstefan  */
1951bce73e1Sstefan static void
com_rcv(struct ns8250_dev * com,uint32_t vm_id,uint32_t vcpu_id)19639d68386Sdv com_rcv(struct ns8250_dev *com, uint32_t vm_id, uint32_t vcpu_id)
1971bce73e1Sstefan {
1980077bcdfSmpi 	char buf[2];
1991bce73e1Sstefan 	ssize_t sz;
2001bce73e1Sstefan 
2011bce73e1Sstefan 	/*
2021bce73e1Sstefan 	 * Is there a new character available on com1?
2031bce73e1Sstefan 	 * If so, consume the character, buffer it into the com1 data register
2041bce73e1Sstefan 	 * assert IRQ4, and set the line status register RXRDY bit.
2051bce73e1Sstefan 	 */
2060077bcdfSmpi 	sz = read(com->fd, buf, sizeof(buf));
2071bce73e1Sstefan 	if (sz == -1) {
2081bce73e1Sstefan 		/*
2091bce73e1Sstefan 		 * If we get EAGAIN, we'll retry and get the character later.
2101bce73e1Sstefan 		 * This error can happen when typing two characters at once
2111bce73e1Sstefan 		 * at the keyboard, for example.
2121bce73e1Sstefan 		 */
2131bce73e1Sstefan 		if (errno != EAGAIN)
2141bce73e1Sstefan 			log_warn("unexpected read error on com device");
215783667e4Santon 	} else if (sz == 0) {
21639d68386Sdv 		/* Zero read typically occurs on a disconnect */
217783667e4Santon 		event_del(&com->event);
218783667e4Santon 		event_add(&com->wake, NULL);
219783667e4Santon 		return;
2200077bcdfSmpi 	} else if (sz != 1 && sz != 2)
2210077bcdfSmpi 		log_warnx("unexpected read return value %zd on com device", sz);
2221bce73e1Sstefan 	else {
2230077bcdfSmpi 		if (com_rcv_handle_break(com, buf[0]))
2240077bcdfSmpi 			buf[1] = 0;
2250077bcdfSmpi 
2261bce73e1Sstefan 		com->regs.lsr |= LSR_RXRDY;
2270077bcdfSmpi 		com->regs.data = buf[1];
2282dda72b8Smlarkin 
2292dda72b8Smlarkin 		if (com->regs.ier & IER_ERXRDY) {
2302dda72b8Smlarkin 			com->regs.iir |= IIR_RXRDY;
2312dda72b8Smlarkin 			com->regs.iir &= ~IIR_NOPEND;
2321bce73e1Sstefan 		}
2331bce73e1Sstefan 	}
2341bce73e1Sstefan }
2351bce73e1Sstefan 
236ecc93de1Smlarkin /*
237ecc93de1Smlarkin  * vcpu_process_com_data
238ecc93de1Smlarkin  *
239ecc93de1Smlarkin  * Emulate in/out instructions to the com1 (ns8250) UART data register
240ecc93de1Smlarkin  *
241ecc93de1Smlarkin  * Parameters:
242ecc93de1Smlarkin  *  vei: vm exit information from vmm(4) containing information on the in/out
243ecc93de1Smlarkin  *      instruction being performed
2441bce73e1Sstefan  *
2451bce73e1Sstefan  * Return value:
2461bce73e1Sstefan  *  interrupt to inject, or 0xFF if nothing to inject
247ecc93de1Smlarkin  */
2481bce73e1Sstefan uint8_t
vcpu_process_com_data(struct vm_exit * vei,uint32_t vm_id,uint32_t vcpu_id)24902ee787fSmlarkin vcpu_process_com_data(struct vm_exit *vei, uint32_t vm_id, uint32_t vcpu_id)
250ecc93de1Smlarkin {
251ecc93de1Smlarkin 	/*
2526ee12970Smlarkin 	 * vei_dir == VEI_DIR_OUT : out instruction
253ecc93de1Smlarkin 	 *
254ecc93de1Smlarkin 	 * The guest wrote to the data register. Since we are emulating a
255ecc93de1Smlarkin 	 * no-fifo chip, write the character immediately to the pty and
256ecc93de1Smlarkin 	 * assert TXRDY in IIR (if the guest has requested TXRDY interrupt
257ecc93de1Smlarkin 	 * reporting)
258ecc93de1Smlarkin 	 */
2596ee12970Smlarkin 	if (vei->vei.vei_dir == VEI_DIR_OUT) {
2602dda72b8Smlarkin 		if (com1_dev.regs.lcr & LCR_DLAB) {
2612dda72b8Smlarkin 			com1_dev.regs.divlo = vei->vei.vei_data;
2622dda72b8Smlarkin 			return 0xFF;
2632dda72b8Smlarkin 		}
2642dda72b8Smlarkin 
2651bce73e1Sstefan 		write(com1_dev.fd, &vei->vei.vei_data, 1);
2662dda72b8Smlarkin 		com1_dev.byte_out++;
2672dda72b8Smlarkin 
268ffc3523bSmlarkin 		if (com1_dev.regs.ier & IER_ETXRDY) {
2692dda72b8Smlarkin 			/* Limit output rate if needed */
27032cbe138Smlarkin 			if (com1_dev.pause_ct > 0 &&
27132cbe138Smlarkin 			    com1_dev.byte_out % com1_dev.pause_ct == 0) {
27208fd0ce3Spd 				vm_pipe_send(&dev_pipe, NS8250_RATELIMIT);
2732dda72b8Smlarkin 			} else {
2742dda72b8Smlarkin 				/* Set TXRDY and clear "no pending interrupt" */
2751bce73e1Sstefan 				com1_dev.regs.iir |= IIR_TXRDY;
2762dda72b8Smlarkin 				com1_dev.regs.iir &= ~IIR_NOPEND;
2772dda72b8Smlarkin 			}
278ecc93de1Smlarkin 		}
279ecc93de1Smlarkin 	} else {
2802dda72b8Smlarkin 		if (com1_dev.regs.lcr & LCR_DLAB) {
2812dda72b8Smlarkin 			set_return_data(vei, com1_dev.regs.divlo);
2822dda72b8Smlarkin 			return 0xFF;
2832dda72b8Smlarkin 		}
284ecc93de1Smlarkin 		/*
2856ee12970Smlarkin 		 * vei_dir == VEI_DIR_IN : in instruction
286ecc93de1Smlarkin 		 *
287ecc93de1Smlarkin 		 * The guest read from the data register. Check to see if
288ecc93de1Smlarkin 		 * there is data available (RXRDY) and if so, consume the
289ecc93de1Smlarkin 		 * input data and return to the guest. Also clear the
290ecc93de1Smlarkin 		 * interrupt info register regardless.
291ecc93de1Smlarkin 		 */
2921bce73e1Sstefan 		if (com1_dev.regs.lsr & LSR_RXRDY) {
293ffc3523bSmlarkin 			set_return_data(vei, com1_dev.regs.data);
2941bce73e1Sstefan 			com1_dev.regs.data = 0x0;
2951bce73e1Sstefan 			com1_dev.regs.lsr &= ~LSR_RXRDY;
296ecc93de1Smlarkin 		} else {
297ffc3523bSmlarkin 			set_return_data(vei, com1_dev.regs.data);
2980a48110eSdv 			log_debug("%s: guest reading com1 when not ready",
299d2de69e7Sreyk 			    __func__);
300ecc93de1Smlarkin 		}
301ecc93de1Smlarkin 
302ecc93de1Smlarkin 		/* Reading the data register always clears RXRDY from IIR */
3031bce73e1Sstefan 		com1_dev.regs.iir &= ~IIR_RXRDY;
304ecc93de1Smlarkin 
305ecc93de1Smlarkin 		/*
306ecc93de1Smlarkin 		 * Clear "interrupt pending" by setting IIR low bit to 1
307ecc93de1Smlarkin 		 * if no interrupt are pending
308ecc93de1Smlarkin 		 */
3091bce73e1Sstefan 		if (com1_dev.regs.iir == 0x0)
3101bce73e1Sstefan 			com1_dev.regs.iir = 0x1;
311ecc93de1Smlarkin 	}
3121bce73e1Sstefan 
3131bce73e1Sstefan 	/* If pending interrupt, make sure it gets injected */
3142dda72b8Smlarkin 	if ((com1_dev.regs.iir & IIR_NOPEND) == 0)
3151bce73e1Sstefan 		return (com1_dev.irq);
316ffc3523bSmlarkin 
3171bce73e1Sstefan 	return (0xFF);
318ecc93de1Smlarkin }
319ecc93de1Smlarkin 
320ecc93de1Smlarkin /*
321ecc93de1Smlarkin  * vcpu_process_com_lcr
322ecc93de1Smlarkin  *
323ecc93de1Smlarkin  * Emulate in/out instructions to the com1 (ns8250) UART line control register
324ecc93de1Smlarkin  *
3253a50f0a9Sjmc  * Parameters:
326ecc93de1Smlarkin  *  vei: vm exit information from vmm(4) containing information on the in/out
327ecc93de1Smlarkin  *      instruction being performed
328ecc93de1Smlarkin  */
329ecc93de1Smlarkin void
vcpu_process_com_lcr(struct vm_exit * vei)33002ee787fSmlarkin vcpu_process_com_lcr(struct vm_exit *vei)
331ecc93de1Smlarkin {
3322dda72b8Smlarkin 	uint8_t data = (uint8_t)vei->vei.vei_data;
3332dda72b8Smlarkin 	uint16_t divisor;
3342dda72b8Smlarkin 
335ecc93de1Smlarkin 	/*
3366ee12970Smlarkin 	 * vei_dir == VEI_DIR_OUT : out instruction
337ecc93de1Smlarkin 	 *
338ecc93de1Smlarkin 	 * Write content to line control register
339ecc93de1Smlarkin 	 */
3406ee12970Smlarkin 	if (vei->vei.vei_dir == VEI_DIR_OUT) {
3412dda72b8Smlarkin 		if (com1_dev.regs.lcr & LCR_DLAB) {
3422dda72b8Smlarkin 			if (!(data & LCR_DLAB)) {
3432dda72b8Smlarkin 				if (com1_dev.regs.divlo == 0 &&
3442dda72b8Smlarkin 				    com1_dev.regs.divhi == 0) {
3452dda72b8Smlarkin 					log_warnx("%s: ignoring invalid "
3462dda72b8Smlarkin 					    "baudrate", __func__);
3472dda72b8Smlarkin 				} else {
3482dda72b8Smlarkin 					divisor = com1_dev.regs.divlo |
3492dda72b8Smlarkin 					     com1_dev.regs.divhi << 8;
3502dda72b8Smlarkin 					com1_dev.baudrate = 115200 / divisor;
3512dda72b8Smlarkin 					com1_dev.pause_ct =
3522dda72b8Smlarkin 					    (com1_dev.baudrate / 8) / 1000 * 10;
3532dda72b8Smlarkin 				}
3542dda72b8Smlarkin 
3552dda72b8Smlarkin 				log_debug("%s: set baudrate = %d", __func__,
3562dda72b8Smlarkin 				    com1_dev.baudrate);
3572dda72b8Smlarkin 			}
3582dda72b8Smlarkin 		}
3591bce73e1Sstefan 		com1_dev.regs.lcr = (uint8_t)vei->vei.vei_data;
360ecc93de1Smlarkin 	} else {
361ecc93de1Smlarkin 		/*
3626ee12970Smlarkin 		 * vei_dir == VEI_DIR_IN : in instruction
363ecc93de1Smlarkin 		 *
364ecc93de1Smlarkin 		 * Read line control register
365ecc93de1Smlarkin 		 */
366ffc3523bSmlarkin 		set_return_data(vei, com1_dev.regs.lcr);
367ecc93de1Smlarkin 	}
368ecc93de1Smlarkin }
369ecc93de1Smlarkin 
370ecc93de1Smlarkin /*
371ecc93de1Smlarkin  * vcpu_process_com_iir
372ecc93de1Smlarkin  *
373ecc93de1Smlarkin  * Emulate in/out instructions to the com1 (ns8250) UART interrupt information
374ecc93de1Smlarkin  * register. Note that writes to this register actually are to a different
375ecc93de1Smlarkin  * register, the FCR (FIFO control register) that we don't emulate but still
376ecc93de1Smlarkin  * consume the data provided.
377ecc93de1Smlarkin  *
378ecc93de1Smlarkin  * Parameters:
379ecc93de1Smlarkin  *  vei: vm exit information from vmm(4) containing information on the in/out
380ecc93de1Smlarkin  *      instruction being performed
381ecc93de1Smlarkin  */
382ecc93de1Smlarkin void
vcpu_process_com_iir(struct vm_exit * vei)38302ee787fSmlarkin vcpu_process_com_iir(struct vm_exit *vei)
384ecc93de1Smlarkin {
385ecc93de1Smlarkin 	/*
3866ee12970Smlarkin 	 * vei_dir == VEI_DIR_OUT : out instruction
387ecc93de1Smlarkin 	 *
388ecc93de1Smlarkin 	 * Write to FCR
389ecc93de1Smlarkin 	 */
3906ee12970Smlarkin 	if (vei->vei.vei_dir == VEI_DIR_OUT) {
3911bce73e1Sstefan 		com1_dev.regs.fcr = vei->vei.vei_data;
392ecc93de1Smlarkin 	} else {
393ecc93de1Smlarkin 		/*
3946ee12970Smlarkin 		 * vei_dir == VEI_DIR_IN : in instruction
395ecc93de1Smlarkin 		 *
396ecc93de1Smlarkin 		 * Read IIR. Reading the IIR resets the TXRDY bit in the IIR
397ecc93de1Smlarkin 		 * after the data is read.
398ecc93de1Smlarkin 		 */
399ffc3523bSmlarkin 		set_return_data(vei, com1_dev.regs.iir);
4001bce73e1Sstefan 		com1_dev.regs.iir &= ~IIR_TXRDY;
401ecc93de1Smlarkin 
402ecc93de1Smlarkin 		/*
403ecc93de1Smlarkin 		 * Clear "interrupt pending" by setting IIR low bit to 1
404ecc93de1Smlarkin 		 * if no interrupts are pending
405ecc93de1Smlarkin 		 */
4061bce73e1Sstefan 		if (com1_dev.regs.iir == 0x0)
4071bce73e1Sstefan 			com1_dev.regs.iir = 0x1;
408ecc93de1Smlarkin 	}
409ecc93de1Smlarkin }
410ecc93de1Smlarkin 
411ecc93de1Smlarkin /*
412ecc93de1Smlarkin  * vcpu_process_com_mcr
413ecc93de1Smlarkin  *
414ecc93de1Smlarkin  * Emulate in/out instructions to the com1 (ns8250) UART modem control
415ecc93de1Smlarkin  * register.
416ecc93de1Smlarkin  *
417ecc93de1Smlarkin  * Parameters:
418ecc93de1Smlarkin  *  vei: vm exit information from vmm(4) containing information on the in/out
419ecc93de1Smlarkin  *      instruction being performed
420ecc93de1Smlarkin  */
421ecc93de1Smlarkin void
vcpu_process_com_mcr(struct vm_exit * vei)42202ee787fSmlarkin vcpu_process_com_mcr(struct vm_exit *vei)
423ecc93de1Smlarkin {
424ecc93de1Smlarkin 	/*
4256ee12970Smlarkin 	 * vei_dir == VEI_DIR_OUT : out instruction
426ecc93de1Smlarkin 	 *
427ecc93de1Smlarkin 	 * Write to MCR
428ecc93de1Smlarkin 	 */
4296ee12970Smlarkin 	if (vei->vei.vei_dir == VEI_DIR_OUT) {
4301bce73e1Sstefan 		com1_dev.regs.mcr = vei->vei.vei_data;
431ecc93de1Smlarkin 	} else {
432ecc93de1Smlarkin 		/*
4336ee12970Smlarkin 		 * vei_dir == VEI_DIR_IN : in instruction
434ecc93de1Smlarkin 		 *
435ecc93de1Smlarkin 		 * Read from MCR
436ecc93de1Smlarkin 		 */
437ffc3523bSmlarkin 		set_return_data(vei, com1_dev.regs.mcr);
438ecc93de1Smlarkin 	}
439ecc93de1Smlarkin }
440ecc93de1Smlarkin 
441ecc93de1Smlarkin /*
442ecc93de1Smlarkin  * vcpu_process_com_lsr
443ecc93de1Smlarkin  *
444ecc93de1Smlarkin  * Emulate in/out instructions to the com1 (ns8250) UART line status register.
445ecc93de1Smlarkin  *
446ecc93de1Smlarkin  * Parameters:
447ecc93de1Smlarkin  *  vei: vm exit information from vmm(4) containing information on the in/out
448ecc93de1Smlarkin  *      instruction being performed
449ecc93de1Smlarkin  */
450ecc93de1Smlarkin void
vcpu_process_com_lsr(struct vm_exit * vei)45102ee787fSmlarkin vcpu_process_com_lsr(struct vm_exit *vei)
452ecc93de1Smlarkin {
453ecc93de1Smlarkin 	/*
4546ee12970Smlarkin 	 * vei_dir == VEI_DIR_OUT : out instruction
455ecc93de1Smlarkin 	 *
456ecc93de1Smlarkin 	 * Write to LSR. This is an illegal operation, so we just log it and
457ecc93de1Smlarkin 	 * continue.
458ecc93de1Smlarkin 	 */
4596ee12970Smlarkin 	if (vei->vei.vei_dir == VEI_DIR_OUT) {
460ecc93de1Smlarkin 		log_warnx("%s: LSR UART write 0x%x unsupported",
461ecc93de1Smlarkin 		    __progname, vei->vei.vei_data);
462ecc93de1Smlarkin 	} else {
463ecc93de1Smlarkin 		/*
4646ee12970Smlarkin 		 * vei_dir == VEI_DIR_IN : in instruction
465ecc93de1Smlarkin 		 *
466ecc93de1Smlarkin 		 * Read from LSR. We always report TXRDY and TSRE since we
467ecc93de1Smlarkin 		 * can process output characters immediately (at any time).
468ecc93de1Smlarkin 		 */
469ffc3523bSmlarkin 		set_return_data(vei, com1_dev.regs.lsr | LSR_TSRE | LSR_TXRDY);
470ecc93de1Smlarkin 	}
471ecc93de1Smlarkin }
472ecc93de1Smlarkin 
473ecc93de1Smlarkin /*
474ecc93de1Smlarkin  * vcpu_process_com_msr
475ecc93de1Smlarkin  *
476ecc93de1Smlarkin  * Emulate in/out instructions to the com1 (ns8250) UART modem status register.
477ecc93de1Smlarkin  *
478ecc93de1Smlarkin  * Parameters:
479ecc93de1Smlarkin  *  vei: vm exit information from vmm(4) containing information on the in/out
480ecc93de1Smlarkin  *      instruction being performed
481ecc93de1Smlarkin  */
482ecc93de1Smlarkin void
vcpu_process_com_msr(struct vm_exit * vei)48302ee787fSmlarkin vcpu_process_com_msr(struct vm_exit *vei)
484ecc93de1Smlarkin {
485ecc93de1Smlarkin 	/*
4866ee12970Smlarkin 	 * vei_dir == VEI_DIR_OUT : out instruction
487ecc93de1Smlarkin 	 *
488ecc93de1Smlarkin 	 * Write to MSR. This is an illegal operation, so we just log it and
489ecc93de1Smlarkin 	 * continue.
490ecc93de1Smlarkin 	 */
4916ee12970Smlarkin 	if (vei->vei.vei_dir == VEI_DIR_OUT) {
492ecc93de1Smlarkin 		log_warnx("%s: MSR UART write 0x%x unsupported",
493ecc93de1Smlarkin 		    __progname, vei->vei.vei_data);
494ecc93de1Smlarkin 	} else {
495ecc93de1Smlarkin 		/*
4966ee12970Smlarkin 		 * vei_dir == VEI_DIR_IN : in instruction
497ecc93de1Smlarkin 		 *
498ecc93de1Smlarkin 		 * Read from MSR. We always report DCD, DSR, and CTS.
499ecc93de1Smlarkin 		 */
500ffc3523bSmlarkin 		set_return_data(vei, com1_dev.regs.lsr | MSR_DCD | MSR_DSR |
501ffc3523bSmlarkin 		    MSR_CTS);
502ecc93de1Smlarkin 	}
503ecc93de1Smlarkin }
504ecc93de1Smlarkin 
505ecc93de1Smlarkin /*
506ecc93de1Smlarkin  * vcpu_process_com_scr
507ecc93de1Smlarkin  *
508f7499dcfSmlarkin  * Emulate in/out instructions to the com1 (ns8250) UART scratch register.
509ecc93de1Smlarkin  *
510ecc93de1Smlarkin  * Parameters:
511ecc93de1Smlarkin  *  vei: vm exit information from vmm(4) containing information on the in/out
512ecc93de1Smlarkin  *      instruction being performed
513ecc93de1Smlarkin  */
514ecc93de1Smlarkin void
vcpu_process_com_scr(struct vm_exit * vei)51502ee787fSmlarkin vcpu_process_com_scr(struct vm_exit *vei)
516ecc93de1Smlarkin {
517ecc93de1Smlarkin 	/*
5186ee12970Smlarkin 	 * vei_dir == VEI_DIR_OUT : out instruction
519ecc93de1Smlarkin 	 *
5205837d48bSmlarkin 	 * The 8250 does not have a scratch register.
521ecc93de1Smlarkin 	 */
5226ee12970Smlarkin 	if (vei->vei.vei_dir == VEI_DIR_OUT) {
5235837d48bSmlarkin 		com1_dev.regs.scr = 0xFF;
524ecc93de1Smlarkin 	} else {
525ecc93de1Smlarkin 		/*
5266ee12970Smlarkin 		 * vei_dir == VEI_DIR_IN : in instruction
527ecc93de1Smlarkin 		 *
528f7499dcfSmlarkin 		 * Read from SCR
529ecc93de1Smlarkin 		 */
530f7499dcfSmlarkin 		set_return_data(vei, com1_dev.regs.scr);
531ecc93de1Smlarkin 	}
532ecc93de1Smlarkin }
533ecc93de1Smlarkin 
534ecc93de1Smlarkin /*
535ecc93de1Smlarkin  * vcpu_process_com_ier
536ecc93de1Smlarkin  *
537ecc93de1Smlarkin  * Emulate in/out instructions to the com1 (ns8250) UART interrupt enable
538ecc93de1Smlarkin  * register.
539ecc93de1Smlarkin  *
540ecc93de1Smlarkin  * Parameters:
541ecc93de1Smlarkin  *  vei: vm exit information from vmm(4) containing information on the in/out
542ecc93de1Smlarkin  *      instruction being performed
543ecc93de1Smlarkin  */
544ecc93de1Smlarkin void
vcpu_process_com_ier(struct vm_exit * vei)54502ee787fSmlarkin vcpu_process_com_ier(struct vm_exit *vei)
546ecc93de1Smlarkin {
547ecc93de1Smlarkin 	/*
5486ee12970Smlarkin 	 * vei_dir == VEI_DIR_OUT : out instruction
549ecc93de1Smlarkin 	 *
550ecc93de1Smlarkin 	 * Write to IER
551ecc93de1Smlarkin 	 */
5526ee12970Smlarkin 	if (vei->vei.vei_dir == VEI_DIR_OUT) {
5532dda72b8Smlarkin 		if (com1_dev.regs.lcr & LCR_DLAB) {
5542dda72b8Smlarkin 			com1_dev.regs.divhi = vei->vei.vei_data;
5552dda72b8Smlarkin 			return;
5562dda72b8Smlarkin 		}
5571bce73e1Sstefan 		com1_dev.regs.ier = vei->vei.vei_data;
558ffc3523bSmlarkin 		if (com1_dev.regs.ier & IER_ETXRDY)
559ffc3523bSmlarkin 			com1_dev.regs.iir |= IIR_TXRDY;
560ecc93de1Smlarkin 	} else {
5612dda72b8Smlarkin 		if (com1_dev.regs.lcr & LCR_DLAB) {
5622dda72b8Smlarkin 			set_return_data(vei, com1_dev.regs.divhi);
5632dda72b8Smlarkin 			return;
5642dda72b8Smlarkin 		}
565ecc93de1Smlarkin 		/*
5666ee12970Smlarkin 		 * vei_dir == VEI_DIR_IN : in instruction
567ecc93de1Smlarkin 		 *
568ecc93de1Smlarkin 		 * Read from IER
569ecc93de1Smlarkin 		 */
570ffc3523bSmlarkin 		set_return_data(vei, com1_dev.regs.ier);
571ecc93de1Smlarkin 	}
572ecc93de1Smlarkin }
573ecc93de1Smlarkin 
574ecc93de1Smlarkin /*
575ecc93de1Smlarkin  * vcpu_exit_com
576ecc93de1Smlarkin  *
577ecc93de1Smlarkin  * Process com1 (ns8250) UART exits. vmd handles most basic 8250
5782dda72b8Smlarkin  * features
579ecc93de1Smlarkin  *
580ecc93de1Smlarkin  * Parameters:
581ecc93de1Smlarkin  *  vrp: vcpu run parameters containing guest state for this exit
582ecc93de1Smlarkin  *
583ecc93de1Smlarkin  * Return value:
584ecc93de1Smlarkin  *  Interrupt to inject to the guest VM, or 0xFF if no interrupt should
585ecc93de1Smlarkin  *      be injected.
586ecc93de1Smlarkin  */
587ecc93de1Smlarkin uint8_t
vcpu_exit_com(struct vm_run_params * vrp)588ecc93de1Smlarkin vcpu_exit_com(struct vm_run_params *vrp)
589ecc93de1Smlarkin {
5901bce73e1Sstefan 	uint8_t intr = 0xFF;
59102ee787fSmlarkin 	struct vm_exit *vei = vrp->vrp_exit;
592ecc93de1Smlarkin 
5931bce73e1Sstefan 	mutex_lock(&com1_dev.mutex);
5941bce73e1Sstefan 
595ecc93de1Smlarkin 	switch (vei->vei.vei_port) {
596ecc93de1Smlarkin 	case COM1_LCR:
597ecc93de1Smlarkin 		vcpu_process_com_lcr(vei);
598ecc93de1Smlarkin 		break;
599ecc93de1Smlarkin 	case COM1_IER:
600ecc93de1Smlarkin 		vcpu_process_com_ier(vei);
601ecc93de1Smlarkin 		break;
602ecc93de1Smlarkin 	case COM1_IIR:
603ecc93de1Smlarkin 		vcpu_process_com_iir(vei);
604ecc93de1Smlarkin 		break;
605ecc93de1Smlarkin 	case COM1_MCR:
606ecc93de1Smlarkin 		vcpu_process_com_mcr(vei);
607ecc93de1Smlarkin 		break;
608ecc93de1Smlarkin 	case COM1_LSR:
609ecc93de1Smlarkin 		vcpu_process_com_lsr(vei);
610ecc93de1Smlarkin 		break;
611ecc93de1Smlarkin 	case COM1_MSR:
612ecc93de1Smlarkin 		vcpu_process_com_msr(vei);
613ecc93de1Smlarkin 		break;
614ecc93de1Smlarkin 	case COM1_SCR:
615ecc93de1Smlarkin 		vcpu_process_com_scr(vei);
616ecc93de1Smlarkin 		break;
617ecc93de1Smlarkin 	case COM1_DATA:
6181bce73e1Sstefan 		intr = vcpu_process_com_data(vei, vrp->vrp_vm_id,
6191bce73e1Sstefan 		    vrp->vrp_vcpu_id);
620ecc93de1Smlarkin 		break;
621ecc93de1Smlarkin 	}
622ecc93de1Smlarkin 
6231bce73e1Sstefan 	mutex_unlock(&com1_dev.mutex);
6242dda72b8Smlarkin 
6251bce73e1Sstefan 	return (intr);
626ecc93de1Smlarkin }
627149417b6Sreyk 
628149417b6Sreyk int
ns8250_dump(int fd)629149417b6Sreyk ns8250_dump(int fd)
630149417b6Sreyk {
631149417b6Sreyk 	log_debug("%s: sending UART", __func__);
632149417b6Sreyk 	if (atomicio(vwrite, fd, &com1_dev.regs,
633149417b6Sreyk 	    sizeof(com1_dev.regs)) != sizeof(com1_dev.regs)) {
634149417b6Sreyk 		log_warnx("%s: error writing UART to fd", __func__);
635149417b6Sreyk 		return (-1);
636149417b6Sreyk 	}
637149417b6Sreyk 	return (0);
638149417b6Sreyk }
639149417b6Sreyk 
640149417b6Sreyk int
ns8250_restore(int fd,int con_fd,uint32_t vmid)641149417b6Sreyk ns8250_restore(int fd, int con_fd, uint32_t vmid)
642149417b6Sreyk {
643149417b6Sreyk 	int ret;
644149417b6Sreyk 	log_debug("%s: receiving UART", __func__);
645149417b6Sreyk 	if (atomicio(read, fd, &com1_dev.regs,
646149417b6Sreyk 	    sizeof(com1_dev.regs)) != sizeof(com1_dev.regs)) {
647149417b6Sreyk 		log_warnx("%s: error reading UART from fd", __func__);
648149417b6Sreyk 		return (-1);
649149417b6Sreyk 	}
650149417b6Sreyk 
651149417b6Sreyk 	ret = pthread_mutex_init(&com1_dev.mutex, NULL);
652149417b6Sreyk 	if (ret) {
653149417b6Sreyk 		errno = ret;
654149417b6Sreyk 		fatal("could not initialize com1 mutex");
655149417b6Sreyk 	}
656149417b6Sreyk 	com1_dev.fd = con_fd;
657149417b6Sreyk 	com1_dev.irq = 4;
6585837d48bSmlarkin 	com1_dev.portid = NS8250_COM1;
659eed20f3bSpd 	com1_dev.vmid = vmid;
660eed20f3bSpd 	com1_dev.byte_out = 0;
661eed20f3bSpd 	com1_dev.regs.divlo = 1;
662eed20f3bSpd 	com1_dev.baudrate = 115200;
663eed20f3bSpd 	com1_dev.pause_ct = (com1_dev.baudrate / 8) / 1000 * 10;
664149417b6Sreyk 
665149417b6Sreyk 	event_set(&com1_dev.event, com1_dev.fd, EV_READ | EV_PERSIST,
666149417b6Sreyk 	    com_rcv_event, (void *)(intptr_t)vmid);
667783667e4Santon 
668783667e4Santon 	event_set(&com1_dev.wake, com1_dev.fd, EV_WRITE,
669783667e4Santon 	    com_rcv_event, (void *)(intptr_t)vmid);
670783667e4Santon 
671bc11ef5cSdv 	timerclear(&com1_dev.rate_tv);
672bc11ef5cSdv 	com1_dev.rate_tv.tv_usec = 10000;
673bc11ef5cSdv 	evtimer_set(&com1_dev.rate, ratelimit, NULL);
674bc11ef5cSdv 
675bc11ef5cSdv 	vm_pipe_init(&dev_pipe, ns8250_pipe_dispatch);
676bc11ef5cSdv 
677149417b6Sreyk 	return (0);
678149417b6Sreyk }
679548054a9Spd 
680548054a9Spd void
ns8250_stop(void)6811999429cStb ns8250_stop(void)
682548054a9Spd {
683548054a9Spd 	if(event_del(&com1_dev.event))
684548054a9Spd 		log_warn("could not delete ns8250 event handler");
685bc11ef5cSdv 	event_del(&dev_pipe.read_ev);
686548054a9Spd 	evtimer_del(&com1_dev.rate);
687548054a9Spd }
688548054a9Spd 
689548054a9Spd void
ns8250_start(void)6901999429cStb ns8250_start(void)
691548054a9Spd {
692548054a9Spd 	event_add(&com1_dev.event, NULL);
693548054a9Spd 	event_add(&com1_dev.wake, NULL);
694bc11ef5cSdv 	event_add(&dev_pipe.read_ev, NULL);
695548054a9Spd 	evtimer_add(&com1_dev.rate, &com1_dev.rate_tv);
696548054a9Spd }
697