-/* $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>
*
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)
{
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
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);
}
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
*
* 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
*
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__);
}
}
/* 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);
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 {
/*
* 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
*
* 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
}
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);
}