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