xref: /openbmc/qemu/hw/char/goldfish_tty.c (revision 28ae3179fc52d2e4d870b635c4a412aab99759e7)
18c6df16fSLaurent Vivier /*
265b4c8c7SPhilippe Mathieu-Daudé  * SPDX-License-Identifier: GPL-2.0-or-later
38c6df16fSLaurent Vivier  *
48c6df16fSLaurent Vivier  * Goldfish TTY
58c6df16fSLaurent Vivier  *
68c6df16fSLaurent Vivier  * (c) 2020 Laurent Vivier <laurent@vivier.eu>
78c6df16fSLaurent Vivier  *
88c6df16fSLaurent Vivier  */
98c6df16fSLaurent Vivier 
108c6df16fSLaurent Vivier #include "qemu/osdep.h"
118c6df16fSLaurent Vivier #include "hw/irq.h"
128c6df16fSLaurent Vivier #include "hw/qdev-properties-system.h"
138c6df16fSLaurent Vivier #include "hw/sysbus.h"
148c6df16fSLaurent Vivier #include "migration/vmstate.h"
158c6df16fSLaurent Vivier #include "chardev/char-fe.h"
168c6df16fSLaurent Vivier #include "qemu/log.h"
178c6df16fSLaurent Vivier #include "trace.h"
188c6df16fSLaurent Vivier #include "exec/address-spaces.h"
19c9e0b9a5SPhilippe Mathieu-Daudé #include "sysemu/dma.h"
208c6df16fSLaurent Vivier #include "hw/char/goldfish_tty.h"
218c6df16fSLaurent Vivier 
228c6df16fSLaurent Vivier #define GOLDFISH_TTY_VERSION 1
238c6df16fSLaurent Vivier 
248c6df16fSLaurent Vivier /* registers */
258c6df16fSLaurent Vivier 
268c6df16fSLaurent Vivier enum {
278c6df16fSLaurent Vivier     REG_PUT_CHAR      = 0x00,
288c6df16fSLaurent Vivier     REG_BYTES_READY   = 0x04,
298c6df16fSLaurent Vivier     REG_CMD           = 0x08,
308c6df16fSLaurent Vivier     REG_DATA_PTR      = 0x10,
318c6df16fSLaurent Vivier     REG_DATA_LEN      = 0x14,
328c6df16fSLaurent Vivier     REG_DATA_PTR_HIGH = 0x18,
338c6df16fSLaurent Vivier     REG_VERSION       = 0x20,
348c6df16fSLaurent Vivier };
358c6df16fSLaurent Vivier 
368c6df16fSLaurent Vivier /* commands */
378c6df16fSLaurent Vivier 
388c6df16fSLaurent Vivier enum {
398c6df16fSLaurent Vivier     CMD_INT_DISABLE   = 0x00,
408c6df16fSLaurent Vivier     CMD_INT_ENABLE    = 0x01,
418c6df16fSLaurent Vivier     CMD_WRITE_BUFFER  = 0x02,
428c6df16fSLaurent Vivier     CMD_READ_BUFFER   = 0x03,
438c6df16fSLaurent Vivier };
448c6df16fSLaurent Vivier 
goldfish_tty_read(void * opaque,hwaddr addr,unsigned size)458c6df16fSLaurent Vivier static uint64_t goldfish_tty_read(void *opaque, hwaddr addr,
468c6df16fSLaurent Vivier                                   unsigned size)
478c6df16fSLaurent Vivier {
488c6df16fSLaurent Vivier     GoldfishTTYState *s = opaque;
498c6df16fSLaurent Vivier     uint64_t value = 0;
508c6df16fSLaurent Vivier 
518c6df16fSLaurent Vivier     switch (addr) {
528c6df16fSLaurent Vivier     case REG_BYTES_READY:
538c6df16fSLaurent Vivier         value = fifo8_num_used(&s->rx_fifo);
548c6df16fSLaurent Vivier         break;
558c6df16fSLaurent Vivier     case REG_VERSION:
568c6df16fSLaurent Vivier         value = GOLDFISH_TTY_VERSION;
578c6df16fSLaurent Vivier         break;
588c6df16fSLaurent Vivier     default:
598c6df16fSLaurent Vivier         qemu_log_mask(LOG_UNIMP,
608c6df16fSLaurent Vivier                       "%s: unimplemented register read 0x%02"HWADDR_PRIx"\n",
618c6df16fSLaurent Vivier                       __func__, addr);
628c6df16fSLaurent Vivier         break;
638c6df16fSLaurent Vivier     }
648c6df16fSLaurent Vivier 
658c6df16fSLaurent Vivier     trace_goldfish_tty_read(s, addr, size, value);
668c6df16fSLaurent Vivier 
678c6df16fSLaurent Vivier     return value;
688c6df16fSLaurent Vivier }
698c6df16fSLaurent Vivier 
goldfish_tty_cmd(GoldfishTTYState * s,uint32_t cmd)708c6df16fSLaurent Vivier static void goldfish_tty_cmd(GoldfishTTYState *s, uint32_t cmd)
718c6df16fSLaurent Vivier {
728c6df16fSLaurent Vivier     uint32_t to_copy;
738c6df16fSLaurent Vivier     uint8_t data_out[GOLFISH_TTY_BUFFER_SIZE];
748c6df16fSLaurent Vivier     int len;
758c6df16fSLaurent Vivier     uint64_t ptr;
768c6df16fSLaurent Vivier 
778c6df16fSLaurent Vivier     switch (cmd) {
788c6df16fSLaurent Vivier     case CMD_INT_DISABLE:
798c6df16fSLaurent Vivier         if (s->int_enabled) {
808c6df16fSLaurent Vivier             if (!fifo8_is_empty(&s->rx_fifo)) {
818c6df16fSLaurent Vivier                 qemu_set_irq(s->irq, 0);
828c6df16fSLaurent Vivier             }
838c6df16fSLaurent Vivier             s->int_enabled = false;
848c6df16fSLaurent Vivier         }
858c6df16fSLaurent Vivier         break;
868c6df16fSLaurent Vivier     case CMD_INT_ENABLE:
878c6df16fSLaurent Vivier         if (!s->int_enabled) {
888c6df16fSLaurent Vivier             if (!fifo8_is_empty(&s->rx_fifo)) {
898c6df16fSLaurent Vivier                 qemu_set_irq(s->irq, 1);
908c6df16fSLaurent Vivier             }
918c6df16fSLaurent Vivier             s->int_enabled = true;
928c6df16fSLaurent Vivier         }
938c6df16fSLaurent Vivier         break;
948c6df16fSLaurent Vivier     case CMD_WRITE_BUFFER:
958c6df16fSLaurent Vivier         len = s->data_len;
968c6df16fSLaurent Vivier         ptr = s->data_ptr;
978c6df16fSLaurent Vivier         while (len) {
988c6df16fSLaurent Vivier             to_copy = MIN(GOLFISH_TTY_BUFFER_SIZE, len);
998c6df16fSLaurent Vivier 
100c9e0b9a5SPhilippe Mathieu-Daudé             dma_memory_read_relaxed(&address_space_memory, ptr,
101c9e0b9a5SPhilippe Mathieu-Daudé                                     data_out, to_copy);
1028c6df16fSLaurent Vivier             qemu_chr_fe_write_all(&s->chr, data_out, to_copy);
1038c6df16fSLaurent Vivier 
1048c6df16fSLaurent Vivier             len -= to_copy;
1058c6df16fSLaurent Vivier             ptr += to_copy;
1068c6df16fSLaurent Vivier         }
1078c6df16fSLaurent Vivier         break;
1088c6df16fSLaurent Vivier     case CMD_READ_BUFFER:
1098c6df16fSLaurent Vivier         len = s->data_len;
1108c6df16fSLaurent Vivier         ptr = s->data_ptr;
1118c6df16fSLaurent Vivier         while (len && !fifo8_is_empty(&s->rx_fifo)) {
11206252bf5SPhilippe Mathieu-Daudé             const uint8_t *buf = fifo8_pop_bufptr(&s->rx_fifo, len, &to_copy);
113c9e0b9a5SPhilippe Mathieu-Daudé 
114c9e0b9a5SPhilippe Mathieu-Daudé             dma_memory_write_relaxed(&address_space_memory, ptr, buf, to_copy);
1158c6df16fSLaurent Vivier 
1168c6df16fSLaurent Vivier             len -= to_copy;
1178c6df16fSLaurent Vivier             ptr += to_copy;
1188c6df16fSLaurent Vivier         }
1198c6df16fSLaurent Vivier         if (s->int_enabled && fifo8_is_empty(&s->rx_fifo)) {
1208c6df16fSLaurent Vivier             qemu_set_irq(s->irq, 0);
1218c6df16fSLaurent Vivier         }
1228c6df16fSLaurent Vivier         break;
1238c6df16fSLaurent Vivier     }
1248c6df16fSLaurent Vivier }
1258c6df16fSLaurent Vivier 
goldfish_tty_write(void * opaque,hwaddr addr,uint64_t value,unsigned size)1268c6df16fSLaurent Vivier static void goldfish_tty_write(void *opaque, hwaddr addr,
1278c6df16fSLaurent Vivier                                uint64_t value, unsigned size)
1288c6df16fSLaurent Vivier {
1298c6df16fSLaurent Vivier     GoldfishTTYState *s = opaque;
1308c6df16fSLaurent Vivier     unsigned char c;
1318c6df16fSLaurent Vivier 
1328c6df16fSLaurent Vivier     trace_goldfish_tty_write(s, addr, size, value);
1338c6df16fSLaurent Vivier 
1348c6df16fSLaurent Vivier     switch (addr) {
1358c6df16fSLaurent Vivier     case REG_PUT_CHAR:
1368c6df16fSLaurent Vivier         c = value;
1378c6df16fSLaurent Vivier         qemu_chr_fe_write_all(&s->chr, &c, sizeof(c));
1388c6df16fSLaurent Vivier         break;
1398c6df16fSLaurent Vivier     case REG_CMD:
1408c6df16fSLaurent Vivier         goldfish_tty_cmd(s, value);
1418c6df16fSLaurent Vivier         break;
1428c6df16fSLaurent Vivier     case REG_DATA_PTR:
1438c6df16fSLaurent Vivier         s->data_ptr = value;
1448c6df16fSLaurent Vivier         break;
1458c6df16fSLaurent Vivier     case REG_DATA_PTR_HIGH:
1468c6df16fSLaurent Vivier         s->data_ptr = deposit64(s->data_ptr, 32, 32, value);
1478c6df16fSLaurent Vivier         break;
1488c6df16fSLaurent Vivier     case REG_DATA_LEN:
1498c6df16fSLaurent Vivier         s->data_len = value;
1508c6df16fSLaurent Vivier         break;
1518c6df16fSLaurent Vivier     default:
1528c6df16fSLaurent Vivier         qemu_log_mask(LOG_UNIMP,
1538c6df16fSLaurent Vivier                       "%s: unimplemented register write 0x%02"HWADDR_PRIx"\n",
1548c6df16fSLaurent Vivier                       __func__, addr);
1558c6df16fSLaurent Vivier         break;
1568c6df16fSLaurent Vivier     }
1578c6df16fSLaurent Vivier }
1588c6df16fSLaurent Vivier 
1598c6df16fSLaurent Vivier static const MemoryRegionOps goldfish_tty_ops = {
1608c6df16fSLaurent Vivier     .read = goldfish_tty_read,
1618c6df16fSLaurent Vivier     .write = goldfish_tty_write,
1628c6df16fSLaurent Vivier     .endianness = DEVICE_NATIVE_ENDIAN,
1638c6df16fSLaurent Vivier     .valid.max_access_size = 4,
1648c6df16fSLaurent Vivier     .impl.max_access_size = 4,
1658c6df16fSLaurent Vivier     .impl.min_access_size = 4,
1668c6df16fSLaurent Vivier };
1678c6df16fSLaurent Vivier 
goldfish_tty_can_receive(void * opaque)1688c6df16fSLaurent Vivier static int goldfish_tty_can_receive(void *opaque)
1698c6df16fSLaurent Vivier {
1708c6df16fSLaurent Vivier     GoldfishTTYState *s = opaque;
1718c6df16fSLaurent Vivier     int available = fifo8_num_free(&s->rx_fifo);
1728c6df16fSLaurent Vivier 
1738c6df16fSLaurent Vivier     trace_goldfish_tty_can_receive(s, available);
1748c6df16fSLaurent Vivier 
1758c6df16fSLaurent Vivier     return available;
1768c6df16fSLaurent Vivier }
1778c6df16fSLaurent Vivier 
goldfish_tty_receive(void * opaque,const uint8_t * buffer,int size)1788c6df16fSLaurent Vivier static void goldfish_tty_receive(void *opaque, const uint8_t *buffer, int size)
1798c6df16fSLaurent Vivier {
1808c6df16fSLaurent Vivier     GoldfishTTYState *s = opaque;
1818c6df16fSLaurent Vivier 
1828c6df16fSLaurent Vivier     trace_goldfish_tty_receive(s, size);
1838c6df16fSLaurent Vivier 
1848c6df16fSLaurent Vivier     g_assert(size <= fifo8_num_free(&s->rx_fifo));
1858c6df16fSLaurent Vivier 
1868c6df16fSLaurent Vivier     fifo8_push_all(&s->rx_fifo, buffer, size);
1878c6df16fSLaurent Vivier 
1888c6df16fSLaurent Vivier     if (s->int_enabled && !fifo8_is_empty(&s->rx_fifo)) {
1898c6df16fSLaurent Vivier         qemu_set_irq(s->irq, 1);
1908c6df16fSLaurent Vivier     }
1918c6df16fSLaurent Vivier }
1928c6df16fSLaurent Vivier 
goldfish_tty_reset(DeviceState * dev)1938c6df16fSLaurent Vivier static void goldfish_tty_reset(DeviceState *dev)
1948c6df16fSLaurent Vivier {
1958c6df16fSLaurent Vivier     GoldfishTTYState *s = GOLDFISH_TTY(dev);
1968c6df16fSLaurent Vivier 
1978c6df16fSLaurent Vivier     trace_goldfish_tty_reset(s);
1988c6df16fSLaurent Vivier 
1998c6df16fSLaurent Vivier     fifo8_reset(&s->rx_fifo);
2008c6df16fSLaurent Vivier     s->int_enabled = false;
2018c6df16fSLaurent Vivier     s->data_ptr = 0;
2028c6df16fSLaurent Vivier     s->data_len = 0;
2038c6df16fSLaurent Vivier }
2048c6df16fSLaurent Vivier 
goldfish_tty_realize(DeviceState * dev,Error ** errp)2058c6df16fSLaurent Vivier static void goldfish_tty_realize(DeviceState *dev, Error **errp)
2068c6df16fSLaurent Vivier {
2078c6df16fSLaurent Vivier     GoldfishTTYState *s = GOLDFISH_TTY(dev);
2088c6df16fSLaurent Vivier 
2098c6df16fSLaurent Vivier     trace_goldfish_tty_realize(s);
2108c6df16fSLaurent Vivier 
2118c6df16fSLaurent Vivier     fifo8_create(&s->rx_fifo, GOLFISH_TTY_BUFFER_SIZE);
2128c6df16fSLaurent Vivier     memory_region_init_io(&s->iomem, OBJECT(s), &goldfish_tty_ops, s,
2138c6df16fSLaurent Vivier                           "goldfish_tty", 0x24);
2148c6df16fSLaurent Vivier 
2158c6df16fSLaurent Vivier     if (qemu_chr_fe_backend_connected(&s->chr)) {
2168c6df16fSLaurent Vivier         qemu_chr_fe_set_handlers(&s->chr, goldfish_tty_can_receive,
2178c6df16fSLaurent Vivier                                  goldfish_tty_receive, NULL, NULL,
2188c6df16fSLaurent Vivier                                  s, NULL, true);
2198c6df16fSLaurent Vivier     }
2208c6df16fSLaurent Vivier }
2218c6df16fSLaurent Vivier 
goldfish_tty_unrealize(DeviceState * dev)2228c6df16fSLaurent Vivier static void goldfish_tty_unrealize(DeviceState *dev)
2238c6df16fSLaurent Vivier {
2248c6df16fSLaurent Vivier     GoldfishTTYState *s = GOLDFISH_TTY(dev);
2258c6df16fSLaurent Vivier 
2268c6df16fSLaurent Vivier     trace_goldfish_tty_unrealize(s);
2278c6df16fSLaurent Vivier 
2288c6df16fSLaurent Vivier     fifo8_destroy(&s->rx_fifo);
2298c6df16fSLaurent Vivier }
2308c6df16fSLaurent Vivier 
2318c6df16fSLaurent Vivier static const VMStateDescription vmstate_goldfish_tty = {
2328c6df16fSLaurent Vivier     .name = "goldfish_tty",
2338c6df16fSLaurent Vivier     .version_id = 1,
2348c6df16fSLaurent Vivier     .minimum_version_id = 1,
2352f6cab05SRichard Henderson     .fields = (const VMStateField[]) {
2368c6df16fSLaurent Vivier         VMSTATE_UINT32(data_len, GoldfishTTYState),
2378c6df16fSLaurent Vivier         VMSTATE_UINT64(data_ptr, GoldfishTTYState),
2388c6df16fSLaurent Vivier         VMSTATE_BOOL(int_enabled, GoldfishTTYState),
2398c6df16fSLaurent Vivier         VMSTATE_FIFO8(rx_fifo, GoldfishTTYState),
2408c6df16fSLaurent Vivier         VMSTATE_END_OF_LIST()
2418c6df16fSLaurent Vivier     }
2428c6df16fSLaurent Vivier };
2438c6df16fSLaurent Vivier 
2448c6df16fSLaurent Vivier static Property goldfish_tty_properties[] = {
2458c6df16fSLaurent Vivier     DEFINE_PROP_CHR("chardev", GoldfishTTYState, chr),
2468c6df16fSLaurent Vivier     DEFINE_PROP_END_OF_LIST(),
2478c6df16fSLaurent Vivier };
2488c6df16fSLaurent Vivier 
goldfish_tty_instance_init(Object * obj)2498c6df16fSLaurent Vivier static void goldfish_tty_instance_init(Object *obj)
2508c6df16fSLaurent Vivier {
2518c6df16fSLaurent Vivier     SysBusDevice *dev = SYS_BUS_DEVICE(obj);
2528c6df16fSLaurent Vivier     GoldfishTTYState *s = GOLDFISH_TTY(obj);
2538c6df16fSLaurent Vivier 
2548c6df16fSLaurent Vivier     trace_goldfish_tty_instance_init(s);
2558c6df16fSLaurent Vivier 
2568c6df16fSLaurent Vivier     sysbus_init_mmio(dev, &s->iomem);
2578c6df16fSLaurent Vivier     sysbus_init_irq(dev, &s->irq);
2588c6df16fSLaurent Vivier }
2598c6df16fSLaurent Vivier 
goldfish_tty_class_init(ObjectClass * oc,void * data)2608c6df16fSLaurent Vivier static void goldfish_tty_class_init(ObjectClass *oc, void *data)
2618c6df16fSLaurent Vivier {
2628c6df16fSLaurent Vivier     DeviceClass *dc = DEVICE_CLASS(oc);
2638c6df16fSLaurent Vivier 
2648c6df16fSLaurent Vivier     device_class_set_props(dc, goldfish_tty_properties);
265*e3d08143SPeter Maydell     device_class_set_legacy_reset(dc, goldfish_tty_reset);
2668c6df16fSLaurent Vivier     dc->realize = goldfish_tty_realize;
2678c6df16fSLaurent Vivier     dc->unrealize = goldfish_tty_unrealize;
2688c6df16fSLaurent Vivier     dc->vmsd = &vmstate_goldfish_tty;
2698c6df16fSLaurent Vivier     set_bit(DEVICE_CATEGORY_INPUT, dc->categories);
2708c6df16fSLaurent Vivier }
2718c6df16fSLaurent Vivier 
2728c6df16fSLaurent Vivier static const TypeInfo goldfish_tty_info = {
2738c6df16fSLaurent Vivier     .name = TYPE_GOLDFISH_TTY,
2748c6df16fSLaurent Vivier     .parent = TYPE_SYS_BUS_DEVICE,
2758c6df16fSLaurent Vivier     .class_init = goldfish_tty_class_init,
2768c6df16fSLaurent Vivier     .instance_init = goldfish_tty_instance_init,
2778c6df16fSLaurent Vivier     .instance_size = sizeof(GoldfishTTYState),
2788c6df16fSLaurent Vivier };
2798c6df16fSLaurent Vivier 
goldfish_tty_register_types(void)2808c6df16fSLaurent Vivier static void goldfish_tty_register_types(void)
2818c6df16fSLaurent Vivier {
2828c6df16fSLaurent Vivier     type_register_static(&goldfish_tty_info);
2838c6df16fSLaurent Vivier }
2848c6df16fSLaurent Vivier 
2858c6df16fSLaurent Vivier type_init(goldfish_tty_register_types)
286