From 2dda72b8c140e1cb92f12dd3464785f140a47017 Mon Sep 17 00:00:00 2001 From: mlarkin Date: Wed, 7 Jun 2017 14:53:28 +0000 Subject: [PATCH] vmd: Implement simulated baudrate support in the ns8250 module. The previous version was allowing an output rate that is "too fast", and linux guests would give up after 512 characters TXed ("too much work for irq4"). This diff calculates the approximate rate we can sustain at the current programmed baud rate and limits the output to that rate by inserting a HZ delay after a specified number of characters have been transmitted. This fixes the linux guest console issue. Note that the console now outputs at more or less the selected baud rate, instead of nearly instantaneously as before - if you selected 9600 in your guest VMs before, you might want to change that to 115200 now for a better console experience. krw@ "seems like a good idea to me" --- usr.sbin/vmd/ns8250.c | 121 ++++++++++++++++++++++++++++++++++++------ usr.sbin/vmd/ns8250.h | 8 ++- usr.sbin/vmd/vm.c | 23 +++++++- 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 * @@ -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 * @@ -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 @@ -1346,6 +1346,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 * 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 *); -- 2.20.1