diff options
-rw-r--r-- | usr.sbin/vmd/ns8250.c | 121 | ||||
-rw-r--r-- | usr.sbin/vmd/ns8250.h | 8 | ||||
-rw-r--r-- | usr.sbin/vmd/vm.c | 23 | ||||
-rw-r--r-- | usr.sbin/vmd/vmm.h | 1 |
4 files changed, 136 insertions, 17 deletions
diff --git a/usr.sbin/vmd/ns8250.c b/usr.sbin/vmd/ns8250.c index a5978fa2189..91c417b13a1 100644 --- a/usr.sbin/vmd/ns8250.c +++ b/usr.sbin/vmd/ns8250.c @@ -1,4 +1,4 @@ -/* $OpenBSD: ns8250.c,v 1.8 2017/05/08 09:08:40 reyk Exp $ */ +/* $OpenBSD: ns8250.c,v 1.9 2017/06/07 14:53:28 mlarkin Exp $ */ /* * Copyright (c) 2016 Mike Larkin <mlarkin@openbsd.org> * @@ -39,6 +39,26 @@ struct ns8250_dev com1_dev; static void com_rcv_event(int, short, void *); static void com_rcv(struct ns8250_dev *, uint32_t, uint32_t); +/* + * ratelimit + * + * Timeout callback function used when we have to slow down the output rate + * from the emulated serial port. + * + * Parameters: + * fd: unused + * type: unused + * arg: unused + */ +static void +ratelimit(int fd, short type, void *arg) +{ + /* Set TXRDY and clear "no pending interrupt" */ + com1_dev.regs.iir |= IIR_TXRDY; + com1_dev.regs.iir &= ~IIR_NOPEND; + vcpu_assert_pic_irq(com1_dev.vmid, 0, com1_dev.irq); +} + void ns8250_init(int fd, uint32_t vmid) { @@ -53,10 +73,34 @@ ns8250_init(int fd, uint32_t vmid) com1_dev.fd = fd; com1_dev.irq = 4; com1_dev.rcv_pending = 0; + com1_dev.vmid = vmid; + com1_dev.byte_out = 0; + com1_dev.regs.divlo = 1; + com1_dev.baudrate = 115200; + + /* + * Our serial port is essentially instantaneous, with infinite + * baudrate capability. To adjust for the selected baudrate, + * we calculate how many characters could be transmitted in a 10ms + * period (pause_ct) and then delay 10ms after each pause_ct sized + * group of characters have been transmitted. Since it takes nearly + * zero time to send the actual characters, the total amount of time + * spent is roughly equal to what it would be on real hardware. + * + * To make things simple, we don't adjust for different sized bytes + * (and parity, stop bits, etc) and simply assume each character + * output is 8 bits. + */ + com1_dev.pause_ct = (com1_dev.baudrate / 8) / 1000 * 10; event_set(&com1_dev.event, com1_dev.fd, EV_READ | EV_PERSIST, com_rcv_event, (void *)(intptr_t)vmid); event_add(&com1_dev.event, NULL); + + /* Rate limiter for simulating baud rate */ + timerclear(&com1_dev.rate_tv); + com1_dev.rate_tv.tv_usec = 10000; + evtimer_set(&com1_dev.rate, ratelimit, NULL); } static void @@ -79,7 +123,7 @@ com_rcv_event(int fd, short kind, void *arg) com_rcv(&com1_dev, (uintptr_t)arg, 0); /* If pending interrupt, inject */ - if ((com1_dev.regs.iir & 0x1) == 0) { + if ((com1_dev.regs.iir & IIR_NOPEND) == 0) { /* XXX: vcpu_id */ vcpu_assert_pic_irq((uintptr_t)arg, 0, com1_dev.irq); } @@ -119,17 +163,16 @@ com_rcv(struct ns8250_dev *com, uint32_t vm_id, uint32_t vcpu_id) else { com->regs.lsr |= LSR_RXRDY; com->regs.data = ch; - /* XXX these ier and iir bits should be IER_x and IIR_x */ - if (com->regs.ier & 0x1) { - com->regs.iir |= (2 << 1); - com->regs.iir &= ~0x1; + + if (com->regs.ier & IER_ERXRDY) { + com->regs.iir |= IIR_RXRDY; + com->regs.iir &= ~IIR_NOPEND; } } com->rcv_pending = fd_hasdata(com->fd); } - /* * vcpu_process_com_data * @@ -154,14 +197,29 @@ vcpu_process_com_data(union vm_exit *vei, uint32_t vm_id, uint32_t vcpu_id) * reporting) */ if (vei->vei.vei_dir == VEI_DIR_OUT) { + if (com1_dev.regs.lcr & LCR_DLAB) { + com1_dev.regs.divlo = vei->vei.vei_data; + return 0xFF; + } + write(com1_dev.fd, &vei->vei.vei_data, 1); + com1_dev.byte_out++; + if (com1_dev.regs.ier & IER_ETXRDY) { - /* Set TXRDY */ - com1_dev.regs.iir |= IIR_TXRDY; - /* Set "interrupt pending" (IIR low bit cleared) */ - com1_dev.regs.iir &= ~0x1; + /* Limit output rate if needed */ + if (com1_dev.byte_out % com1_dev.pause_ct == 0) { + evtimer_add(&com1_dev.rate, &com1_dev.rate_tv); + } else { + /* Set TXRDY and clear "no pending interrupt" */ + com1_dev.regs.iir |= IIR_TXRDY; + com1_dev.regs.iir &= ~IIR_NOPEND; + } } } else { + if (com1_dev.regs.lcr & LCR_DLAB) { + set_return_data(vei, com1_dev.regs.divlo); + return 0xFF; + } /* * vei_dir == VEI_DIR_IN : in instruction * @@ -175,7 +233,6 @@ vcpu_process_com_data(union vm_exit *vei, uint32_t vm_id, uint32_t vcpu_id) com1_dev.regs.data = 0x0; com1_dev.regs.lsr &= ~LSR_RXRDY; } else { - /* XXX should this be com1_dev.data or 0xff? */ set_return_data(vei, com1_dev.regs.data); log_warnx("%s: guest reading com1 when not ready", __func__); } @@ -195,7 +252,7 @@ vcpu_process_com_data(union vm_exit *vei, uint32_t vm_id, uint32_t vcpu_id) } /* If pending interrupt, make sure it gets injected */ - if ((com1_dev.regs.iir & 0x1) == 0) + if ((com1_dev.regs.iir & IIR_NOPEND) == 0) return (com1_dev.irq); return (0xFF); @@ -213,12 +270,33 @@ vcpu_process_com_data(union vm_exit *vei, uint32_t vm_id, uint32_t vcpu_id) void vcpu_process_com_lcr(union vm_exit *vei) { + uint8_t data = (uint8_t)vei->vei.vei_data; + uint16_t divisor; + /* * vei_dir == VEI_DIR_OUT : out instruction * * Write content to line control register */ if (vei->vei.vei_dir == VEI_DIR_OUT) { + if (com1_dev.regs.lcr & LCR_DLAB) { + if (!(data & LCR_DLAB)) { + if (com1_dev.regs.divlo == 0 && + com1_dev.regs.divhi == 0) { + log_warnx("%s: ignoring invalid " + "baudrate", __func__); + } else { + divisor = com1_dev.regs.divlo | + com1_dev.regs.divhi << 8; + com1_dev.baudrate = 115200 / divisor; + com1_dev.pause_ct = + (com1_dev.baudrate / 8) / 1000 * 10; + } + + log_debug("%s: set baudrate = %d", __func__, + com1_dev.baudrate); + } + } com1_dev.regs.lcr = (uint8_t)vei->vei.vei_data; } else { /* @@ -419,10 +497,18 @@ vcpu_process_com_ier(union vm_exit *vei) * Write to IER */ if (vei->vei.vei_dir == VEI_DIR_OUT) { + if (com1_dev.regs.lcr & LCR_DLAB) { + com1_dev.regs.divhi = vei->vei.vei_data; + return; + } com1_dev.regs.ier = vei->vei.vei_data; if (com1_dev.regs.ier & IER_ETXRDY) com1_dev.regs.iir |= IIR_TXRDY; } else { + if (com1_dev.regs.lcr & LCR_DLAB) { + set_return_data(vei, com1_dev.regs.divhi); + return; + } /* * vei_dir == VEI_DIR_IN : in instruction * @@ -436,8 +522,7 @@ vcpu_process_com_ier(union vm_exit *vei) * vcpu_exit_com * * Process com1 (ns8250) UART exits. vmd handles most basic 8250 - * features with the exception of the divisor latch (eg, no baud - * rate support) + * features * * Parameters: * vrp: vcpu run parameters containing guest state for this exit @@ -483,6 +568,12 @@ vcpu_exit_com(struct vm_run_params *vrp) } mutex_unlock(&com1_dev.mutex); + + if ((com1_dev.regs.iir & IIR_NOPEND)) { + /* XXX: vcpu_id */ + vcpu_deassert_pic_irq(com1_dev.vmid, 0, com1_dev.irq); + } + return (intr); } diff --git a/usr.sbin/vmd/ns8250.h b/usr.sbin/vmd/ns8250.h index 5d481d918b3..6c861107e67 100644 --- a/usr.sbin/vmd/ns8250.h +++ b/usr.sbin/vmd/ns8250.h @@ -1,4 +1,4 @@ -/* $OpenBSD: ns8250.h,v 1.4 2017/05/08 09:08:40 reyk Exp $ */ +/* $OpenBSD: ns8250.h,v 1.5 2017/06/07 14:53:28 mlarkin Exp $ */ /* * Copyright (c) 2016 Mike Larkin <mlarkin@openbsd.org> * @@ -47,9 +47,15 @@ struct ns8250_dev { pthread_mutex_t mutex; struct ns8250_regs regs; struct event event; + struct event rate; + struct timeval rate_tv; int fd; int irq; int rcv_pending; + uint32_t vmid; + uint64_t byte_out; + uint32_t baudrate; + uint32_t pause_ct; }; void ns8250_init(int, uint32_t); diff --git a/usr.sbin/vmd/vm.c b/usr.sbin/vmd/vm.c index 2d38bc0f5e3..1c4e354422b 100644 --- a/usr.sbin/vmd/vm.c +++ b/usr.sbin/vmd/vm.c @@ -1,4 +1,4 @@ -/* $OpenBSD: vm.c,v 1.19 2017/05/30 17:56:47 tedu Exp $ */ +/* $OpenBSD: vm.c,v 1.20 2017/06/07 14:53:28 mlarkin Exp $ */ /* * Copyright (c) 2015 Mike Larkin <mlarkin@openbsd.org> @@ -1347,6 +1347,27 @@ vcpu_assert_pic_irq(uint32_t vm_id, uint32_t vcpu_id, int irq) } /* + * vcpu_deassert_pic_irq + * + * Clears the specified IRQ on the supplied vcpu/vm + * + * Parameters: + * vm_id: VM ID to clear in + * vcpu_id: VCPU ID to clear in + * irq: IRQ to clear + */ +void +vcpu_deassert_pic_irq(uint32_t vm_id, uint32_t vcpu_id, int irq) +{ + i8259_deassert_irq(irq); + + if (!i8259_is_pending()) { + if (vcpu_pic_intr(vm_id, vcpu_id, 0)) + fatalx("%s: can't deassert INTR", __func__); + } +} + +/* * fd_hasdata * * Determines if data can be read from a file descriptor. diff --git a/usr.sbin/vmd/vmm.h b/usr.sbin/vmd/vmm.h index fd94125c9df..084554fc87c 100644 --- a/usr.sbin/vmd/vmm.h +++ b/usr.sbin/vmd/vmm.h @@ -19,5 +19,6 @@ typedef uint8_t (*io_fn_t)(struct vm_run_params *); void vcpu_assert_pic_irq(uint32_t, uint32_t, int); +void vcpu_deassert_pic_irq(uint32_t, uint32_t, int); void set_return_data(union vm_exit *, uint32_t); void get_input_data(union vm_exit *, uint32_t *); |