@@ -49,10 +49,14 @@ DeviceState *pl011_create(hwaddr addr, qemu_irq irq, Chardev *chr)
}
/* Flag Register, UARTFR */
+#define PL011_FLAG_RI 0x100
#define PL011_FLAG_TXFE 0x80
#define PL011_FLAG_RXFF 0x40
#define PL011_FLAG_TXFF 0x20
#define PL011_FLAG_RXFE 0x10
+#define PL011_FLAG_DCD 0x04
+#define PL011_FLAG_DSR 0x02
+#define PL011_FLAG_CTS 0x01
/* Data Register, UARTDR */
#define DR_BE (1 << 10)
@@ -76,6 +80,13 @@ DeviceState *pl011_create(hwaddr addr, qemu_irq irq, Chardev *chr)
#define LCR_FEN (1 << 4)
#define LCR_BRK (1 << 0)
+/* Control Register, UARTCR */
+#define CR_OUT2 (1 << 13)
+#define CR_OUT1 (1 << 12)
+#define CR_RTS (1 << 11)
+#define CR_DTR (1 << 10)
+#define CR_LBE (1 << 7)
+
static const unsigned char pl011_id_arm[8] =
{ 0x11, 0x10, 0x14, 0x00, 0x0d, 0xf0, 0x05, 0xb1 };
static const unsigned char pl011_id_luminary[8] =
@@ -251,6 +262,89 @@ static void pl011_trace_baudrate_change(const PL011State *s)
s->ibrd, s->fbrd);
}
+static bool pl011_loopback_enabled(PL011State *s)
+{
+ return !!(s->cr & CR_LBE);
+}
+
+static void pl011_loopback_mdmctrl(PL011State *s)
+{
+ uint32_t cr, fr, il;
+
+ if (!pl011_loopback_enabled(s)) {
+ return;
+ }
+
+ /*
+ * Loopback software-driven modem control outputs to modem status inputs:
+ * FR.RI <= CR.Out2
+ * FR.DCD <= CR.Out1
+ * FR.CTS <= CR.RTS
+ * FR.DSR <= CR.DTR
+ *
+ * The loopback happens immediately even if this call is triggered
+ * by setting only CR.LBE.
+ *
+ * CTS/RTS updates due to enabled hardware flow controls are not
+ * dealt with here.
+ */
+ cr = s->cr;
+ fr = s->flags & ~(PL011_FLAG_RI | PL011_FLAG_DCD |
+ PL011_FLAG_DSR | PL011_FLAG_CTS);
+ fr |= (cr & CR_OUT2) ? PL011_FLAG_RI : 0;
+ fr |= (cr & CR_OUT1) ? PL011_FLAG_DCD : 0;
+ fr |= (cr & CR_RTS) ? PL011_FLAG_CTS : 0;
+ fr |= (cr & CR_DTR) ? PL011_FLAG_DSR : 0;
+
+ /* Change interrupts based on updated FR */
+ il = s->int_level & ~(INT_DSR | INT_DCD | INT_CTS | INT_RI);
+ il |= (fr & PL011_FLAG_DSR) ? INT_DSR : 0;
+ il |= (fr & PL011_FLAG_DCD) ? INT_DCD : 0;
+ il |= (fr & PL011_FLAG_CTS) ? INT_CTS : 0;
+ il |= (fr & PL011_FLAG_RI) ? INT_RI : 0;
+
+ s->flags = fr;
+ s->int_level = il;
+ pl011_update(s);
+}
+
+static void pl011_put_fifo(void *opaque, uint32_t value);
+
+static void pl011_loopback_tx(PL011State *s, uint32_t value)
+{
+ if (!pl011_loopback_enabled(s)) {
+ return;
+ }
+
+ /*
+ * Caveat:
+ *
+ * In real hardware, TX loopback happens at the serial-bit level
+ * and then reassembled by the RX logics back into bytes and placed
+ * into the RX fifo. That is, loopback happens after TX fifo.
+ *
+ * Because the real hardware TX fifo is time-drained at the frame
+ * rate governed by the configured serial format, some loopback
+ * bytes in TX fifo may still be able to get into the RX fifo
+ * that could be full at times while being drained at software
+ * pace.
+ *
+ * In such scenario, the RX draining pace is the major factor
+ * deciding which loopback bytes get into the RX fifo, unless
+ * hardware flow-control is enabled.
+ *
+ * For simplicity, the above described is not emulated.
+ */
+ pl011_put_fifo(s, value);
+}
+
+static void pl011_loopback_break(PL011State *s, int brk_enable)
+{
+ if (brk_enable) {
+ pl011_loopback_tx(s, DR_BE);
+ }
+}
+
static void pl011_write(void *opaque, hwaddr offset,
uint64_t value, unsigned size)
{
@@ -266,6 +360,7 @@ static void pl011_write(void *opaque, hwaddr offset,
/* XXX this blocks entire thread. Rewrite to use
* qemu_chr_fe_write and background I/O callbacks */
qemu_chr_fe_write_all(&s->chr, &ch, 1);
+ pl011_loopback_tx(s, ch);
s->int_level |= INT_TX;
pl011_update(s);
break;
@@ -295,13 +390,15 @@ static void pl011_write(void *opaque, hwaddr offset,
int break_enable = value & LCR_BRK;
qemu_chr_fe_ioctl(&s->chr, CHR_IOCTL_SERIAL_SET_BREAK,
&break_enable);
+ pl011_loopback_break(s, break_enable);
}
s->lcr = value;
pl011_set_read_trigger(s);
break;
case 12: /* UARTCR */
- /* ??? Need to implement the enable and loopback bits. */
+ /* ??? Need to implement the enable bit. */
s->cr = value;
+ pl011_loopback_mdmctrl(s);
break;
case 13: /* UARTIFS */
s->ifl = value;
@@ -361,12 +458,21 @@ static void pl011_put_fifo(void *opaque, uint32_t value)
static void pl011_receive(void *opaque, const uint8_t *buf, int size)
{
+ /*
+ * In loopback mode, the RX input signal is internally disconnected
+ * from the entire receiving logics; thus, all inputs are ignored,
+ * and BREAK detection on RX input signal is also not performed.
+ */
+ if (pl011_loopback_enabled(opaque)) {
+ return;
+ }
+
pl011_put_fifo(opaque, *buf);
}
static void pl011_event(void *opaque, QEMUChrEvent event)
{
- if (event == CHR_EVENT_BREAK) {
+ if (event == CHR_EVENT_BREAK && !pl011_loopback_enabled(opaque)) {
pl011_put_fifo(opaque, DR_BE);
}
}