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