| /* |
| * QEMU model of the UART on the SiFive E300 and U500 series SOCs. |
| * |
| * Copyright (c) 2016 Stefan O'Rear |
| * |
| * This program is free software; you can redistribute it and/or modify it |
| * under the terms and conditions of the GNU General Public License, |
| * version 2 or later, as published by the Free Software Foundation. |
| * |
| * This program is distributed in the hope it will be useful, but WITHOUT |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for |
| * more details. |
| * |
| * You should have received a copy of the GNU General Public License along with |
| * this program. If not, see <http://www.gnu.org/licenses/>. |
| */ |
| |
| #include "qemu/osdep.h" |
| #include "qapi/error.h" |
| #include "qemu/log.h" |
| #include "migration/vmstate.h" |
| #include "chardev/char.h" |
| #include "chardev/char-fe.h" |
| #include "hw/irq.h" |
| #include "hw/char/sifive_uart.h" |
| #include "hw/qdev-properties-system.h" |
| |
| #define TX_INTERRUPT_TRIGGER_DELAY_NS 100 |
| |
| /* |
| * Not yet implemented: |
| * |
| * Transmit FIFO using "qemu/fifo8.h" |
| */ |
| |
| /* Returns the state of the IP (interrupt pending) register */ |
| static uint64_t sifive_uart_ip(SiFiveUARTState *s) |
| { |
| uint64_t ret = 0; |
| |
| uint64_t txcnt = SIFIVE_UART_GET_TXCNT(s->txctrl); |
| uint64_t rxcnt = SIFIVE_UART_GET_RXCNT(s->rxctrl); |
| |
| if (txcnt != 0) { |
| ret |= SIFIVE_UART_IP_TXWM; |
| } |
| if (s->rx_fifo_len > rxcnt) { |
| ret |= SIFIVE_UART_IP_RXWM; |
| } |
| |
| return ret; |
| } |
| |
| static void sifive_uart_update_irq(SiFiveUARTState *s) |
| { |
| int cond = 0; |
| if ((s->ie & SIFIVE_UART_IE_TXWM) || |
| ((s->ie & SIFIVE_UART_IE_RXWM) && s->rx_fifo_len)) { |
| cond = 1; |
| } |
| if (cond) { |
| qemu_irq_raise(s->irq); |
| } else { |
| qemu_irq_lower(s->irq); |
| } |
| } |
| |
| static gboolean sifive_uart_xmit(void *do_not_use, GIOCondition cond, |
| void *opaque) |
| { |
| SiFiveUARTState *s = opaque; |
| int ret; |
| const uint8_t *characters; |
| uint32_t numptr = 0; |
| |
| /* instant drain the fifo when there's no back-end */ |
| if (!qemu_chr_fe_backend_connected(&s->chr)) { |
| fifo8_reset(&s->tx_fifo); |
| return G_SOURCE_REMOVE; |
| } |
| |
| if (fifo8_is_empty(&s->tx_fifo)) { |
| return G_SOURCE_REMOVE; |
| } |
| |
| /* Don't pop the FIFO in case the write fails */ |
| characters = fifo8_peek_bufptr(&s->tx_fifo, |
| fifo8_num_used(&s->tx_fifo), &numptr); |
| ret = qemu_chr_fe_write(&s->chr, characters, numptr); |
| |
| if (ret >= 0) { |
| /* We wrote the data, actually pop the fifo */ |
| fifo8_pop_bufptr(&s->tx_fifo, ret, NULL); |
| } |
| |
| if (!fifo8_is_empty(&s->tx_fifo)) { |
| guint r = qemu_chr_fe_add_watch(&s->chr, G_IO_OUT | G_IO_HUP, |
| sifive_uart_xmit, s); |
| if (!r) { |
| fifo8_reset(&s->tx_fifo); |
| return G_SOURCE_REMOVE; |
| } |
| } |
| |
| /* Clear the TX Full bit */ |
| if (!fifo8_is_full(&s->tx_fifo)) { |
| s->txfifo &= ~SIFIVE_UART_TXFIFO_FULL; |
| } |
| |
| sifive_uart_update_irq(s); |
| return G_SOURCE_REMOVE; |
| } |
| |
| static void sifive_uart_write_tx_fifo(SiFiveUARTState *s, const uint8_t *buf, |
| int size) |
| { |
| uint64_t current_time = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); |
| |
| if (size > fifo8_num_free(&s->tx_fifo)) { |
| size = fifo8_num_free(&s->tx_fifo); |
| qemu_log_mask(LOG_GUEST_ERROR, "sifive_uart: TX FIFO overflow"); |
| } |
| |
| fifo8_push_all(&s->tx_fifo, buf, size); |
| |
| if (fifo8_is_full(&s->tx_fifo)) { |
| s->txfifo |= SIFIVE_UART_TXFIFO_FULL; |
| } |
| |
| timer_mod(s->fifo_trigger_handle, current_time + |
| TX_INTERRUPT_TRIGGER_DELAY_NS); |
| } |
| |
| static uint64_t |
| sifive_uart_read(void *opaque, hwaddr addr, unsigned int size) |
| { |
| SiFiveUARTState *s = opaque; |
| unsigned char r; |
| switch (addr) { |
| case SIFIVE_UART_RXFIFO: |
| if (s->rx_fifo_len) { |
| r = s->rx_fifo[0]; |
| memmove(s->rx_fifo, s->rx_fifo + 1, s->rx_fifo_len - 1); |
| s->rx_fifo_len--; |
| qemu_chr_fe_accept_input(&s->chr); |
| sifive_uart_update_irq(s); |
| return r; |
| } |
| return 0x80000000; |
| |
| case SIFIVE_UART_TXFIFO: |
| return s->txfifo; |
| case SIFIVE_UART_IE: |
| return s->ie; |
| case SIFIVE_UART_IP: |
| return sifive_uart_ip(s); |
| case SIFIVE_UART_TXCTRL: |
| return s->txctrl; |
| case SIFIVE_UART_RXCTRL: |
| return s->rxctrl; |
| case SIFIVE_UART_DIV: |
| return s->div; |
| } |
| |
| qemu_log_mask(LOG_GUEST_ERROR, "%s: bad read: addr=0x%x\n", |
| __func__, (int)addr); |
| return 0; |
| } |
| |
| static void |
| sifive_uart_write(void *opaque, hwaddr addr, |
| uint64_t val64, unsigned int size) |
| { |
| SiFiveUARTState *s = opaque; |
| uint32_t value = val64; |
| |
| switch (addr) { |
| case SIFIVE_UART_TXFIFO: |
| sifive_uart_write_tx_fifo(s, (uint8_t *) &value, 1); |
| return; |
| case SIFIVE_UART_IE: |
| s->ie = val64; |
| sifive_uart_update_irq(s); |
| return; |
| case SIFIVE_UART_TXCTRL: |
| s->txctrl = val64; |
| return; |
| case SIFIVE_UART_RXCTRL: |
| s->rxctrl = val64; |
| return; |
| case SIFIVE_UART_DIV: |
| s->div = val64; |
| return; |
| } |
| qemu_log_mask(LOG_GUEST_ERROR, "%s: bad write: addr=0x%x v=0x%x\n", |
| __func__, (int)addr, (int)value); |
| } |
| |
| static void fifo_trigger_update(void *opaque) |
| { |
| SiFiveUARTState *s = opaque; |
| |
| sifive_uart_xmit(NULL, G_IO_OUT, s); |
| } |
| |
| static const MemoryRegionOps sifive_uart_ops = { |
| .read = sifive_uart_read, |
| .write = sifive_uart_write, |
| .endianness = DEVICE_NATIVE_ENDIAN, |
| .valid = { |
| .min_access_size = 4, |
| .max_access_size = 4 |
| } |
| }; |
| |
| static void sifive_uart_rx(void *opaque, const uint8_t *buf, int size) |
| { |
| SiFiveUARTState *s = opaque; |
| |
| /* Got a byte. */ |
| if (s->rx_fifo_len >= sizeof(s->rx_fifo)) { |
| printf("WARNING: UART dropped char.\n"); |
| return; |
| } |
| s->rx_fifo[s->rx_fifo_len++] = *buf; |
| |
| sifive_uart_update_irq(s); |
| } |
| |
| static int sifive_uart_can_rx(void *opaque) |
| { |
| SiFiveUARTState *s = opaque; |
| |
| return s->rx_fifo_len < sizeof(s->rx_fifo); |
| } |
| |
| static void sifive_uart_event(void *opaque, QEMUChrEvent event) |
| { |
| } |
| |
| static int sifive_uart_be_change(void *opaque) |
| { |
| SiFiveUARTState *s = opaque; |
| |
| qemu_chr_fe_set_handlers(&s->chr, sifive_uart_can_rx, sifive_uart_rx, |
| sifive_uart_event, sifive_uart_be_change, s, |
| NULL, true); |
| |
| return 0; |
| } |
| |
| static Property sifive_uart_properties[] = { |
| DEFINE_PROP_CHR("chardev", SiFiveUARTState, chr), |
| DEFINE_PROP_END_OF_LIST(), |
| }; |
| |
| static void sifive_uart_init(Object *obj) |
| { |
| SysBusDevice *sbd = SYS_BUS_DEVICE(obj); |
| SiFiveUARTState *s = SIFIVE_UART(obj); |
| |
| memory_region_init_io(&s->mmio, OBJECT(s), &sifive_uart_ops, s, |
| TYPE_SIFIVE_UART, SIFIVE_UART_MAX); |
| sysbus_init_mmio(sbd, &s->mmio); |
| sysbus_init_irq(sbd, &s->irq); |
| } |
| |
| static void sifive_uart_realize(DeviceState *dev, Error **errp) |
| { |
| SiFiveUARTState *s = SIFIVE_UART(dev); |
| |
| s->fifo_trigger_handle = timer_new_ns(QEMU_CLOCK_VIRTUAL, |
| fifo_trigger_update, s); |
| |
| qemu_chr_fe_set_handlers(&s->chr, sifive_uart_can_rx, sifive_uart_rx, |
| sifive_uart_event, sifive_uart_be_change, s, |
| NULL, true); |
| |
| } |
| |
| static void sifive_uart_reset_enter(Object *obj, ResetType type) |
| { |
| SiFiveUARTState *s = SIFIVE_UART(obj); |
| |
| s->txfifo = 0; |
| s->ie = 0; |
| s->ip = 0; |
| s->txctrl = 0; |
| s->rxctrl = 0; |
| s->div = 0; |
| |
| s->rx_fifo_len = 0; |
| |
| memset(s->rx_fifo, 0, SIFIVE_UART_RX_FIFO_SIZE); |
| fifo8_create(&s->tx_fifo, SIFIVE_UART_TX_FIFO_SIZE); |
| } |
| |
| static void sifive_uart_reset_hold(Object *obj, ResetType type) |
| { |
| SiFiveUARTState *s = SIFIVE_UART(obj); |
| qemu_irq_lower(s->irq); |
| } |
| |
| static const VMStateDescription vmstate_sifive_uart = { |
| .name = TYPE_SIFIVE_UART, |
| .version_id = 2, |
| .minimum_version_id = 2, |
| .fields = (const VMStateField[]) { |
| VMSTATE_UINT8_ARRAY(rx_fifo, SiFiveUARTState, |
| SIFIVE_UART_RX_FIFO_SIZE), |
| VMSTATE_UINT8(rx_fifo_len, SiFiveUARTState), |
| VMSTATE_UINT32(ie, SiFiveUARTState), |
| VMSTATE_UINT32(ip, SiFiveUARTState), |
| VMSTATE_UINT32(txctrl, SiFiveUARTState), |
| VMSTATE_UINT32(rxctrl, SiFiveUARTState), |
| VMSTATE_UINT32(div, SiFiveUARTState), |
| VMSTATE_UINT32(txfifo, SiFiveUARTState), |
| VMSTATE_FIFO8(tx_fifo, SiFiveUARTState), |
| VMSTATE_TIMER_PTR(fifo_trigger_handle, SiFiveUARTState), |
| VMSTATE_END_OF_LIST() |
| }, |
| }; |
| |
| |
| static void sifive_uart_class_init(ObjectClass *oc, void *data) |
| { |
| DeviceClass *dc = DEVICE_CLASS(oc); |
| ResettableClass *rc = RESETTABLE_CLASS(oc); |
| |
| dc->realize = sifive_uart_realize; |
| dc->vmsd = &vmstate_sifive_uart; |
| rc->phases.enter = sifive_uart_reset_enter; |
| rc->phases.hold = sifive_uart_reset_hold; |
| device_class_set_props(dc, sifive_uart_properties); |
| set_bit(DEVICE_CATEGORY_INPUT, dc->categories); |
| } |
| |
| static const TypeInfo sifive_uart_info = { |
| .name = TYPE_SIFIVE_UART, |
| .parent = TYPE_SYS_BUS_DEVICE, |
| .instance_size = sizeof(SiFiveUARTState), |
| .instance_init = sifive_uart_init, |
| .class_init = sifive_uart_class_init, |
| }; |
| |
| static void sifive_uart_register_types(void) |
| { |
| type_register_static(&sifive_uart_info); |
| } |
| |
| type_init(sifive_uart_register_types) |
| |
| /* |
| * Create UART device. |
| */ |
| SiFiveUARTState *sifive_uart_create(MemoryRegion *address_space, hwaddr base, |
| Chardev *chr, qemu_irq irq) |
| { |
| DeviceState *dev; |
| SysBusDevice *s; |
| |
| dev = qdev_new("riscv.sifive.uart"); |
| s = SYS_BUS_DEVICE(dev); |
| qdev_prop_set_chr(dev, "chardev", chr); |
| sysbus_realize_and_unref(s, &error_fatal); |
| memory_region_add_subregion(address_space, base, |
| sysbus_mmio_get_region(s, 0)); |
| sysbus_connect_irq(s, 0, irq); |
| |
| return SIFIVE_UART(dev); |
| } |