Add Goldfish serial port device
Goldfish serial port device emulation.
The following was taken from AOSP emulator documentation :
<AOSP>/external/qemu/GOLDFISH-VIRTUAL-HARDWARE.TXT
$QEMU=qemu-android : new code base QEMU with ranchu machine
$KERNEL=https://android.googlesource.com/kernel/goldfish.git
Relevant files:
$QEMU/hw/char/goldfish_tty.c
$KERNEL/drivers/char/goldfish_tty.c
Device properties:
Name: goldfish_tty
Id: 0 to N
IrqCount: 1
I/O Registers:
0x00 W PUT_CHAR : Write a single 8-bit value.
0x04 R BYTES_READY : Read the number of available input bytes.
0x08 W CMD : Send command (see below).
0x10 W DATA_PTR : Write kernel buffer address.
0x14 W DATA_LEN : Write kernel buffer size.
# For 64-bit guest CPUs only:
0x18 W DATA_PTR_HIGH : Write high 32 bits of kernel buffer address.
Each instance of this device implements a virtual serial port
that contains a small internal buffer where incoming data is
stored until the kernel fetches it.
The CMD I/O register is used to send various commands to the
device, identified by the following values:
0x00 CMD_INT_DISABLE Disable device.
0x01 CMD_INT_ENABLE Enable device.
0x02 CMD_WRITE_BUFFER Write buffer from kernel to device.
0x03 CMD_READ_BUFFER Read buffer from device to kernel.
Change-Id: Ide6593473f32f97e6e5b6d27fa795073a8ccf74a
diff --git a/hw/char/Makefile.objs b/hw/char/Makefile.objs
index 7170aef..6fc2d11 100644
--- a/hw/char/Makefile.objs
+++ b/hw/char/Makefile.objs
@@ -16,6 +16,7 @@
obj-$(CONFIG_SH4) += sh_serial.o
obj-$(CONFIG_PSERIES) += spapr_vty.o
obj-$(CONFIG_DIGIC) += digic-uart.o
+obj-$(CONFIG_GOLDFISH) += goldfish_tty.o
common-obj-$(CONFIG_ETRAXFS) += etraxfs_ser.o
common-obj-$(CONFIG_ISA_DEBUG) += debugcon.o
diff --git a/hw/char/goldfish_tty.c b/hw/char/goldfish_tty.c
new file mode 100644
index 0000000..a7aee8c
--- /dev/null
+++ b/hw/char/goldfish_tty.c
@@ -0,0 +1,268 @@
+/* Copyright (C) 2007-2008 The Android Open Source Project
+**
+** This software is licensed under the terms of the GNU General Public
+** License version 2, as published by the Free Software Foundation, and
+** may be copied, distributed, and modified under those terms.
+**
+** This program is distributed in the hope that 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.
+*/
+#include "exec/ram_addr.h"
+#include "migration/qemu-file.h"
+#include "sysemu/char.h"
+#include "hw/hw.h"
+#include "exec/address-spaces.h"
+#include "hw/sysbus.h"
+#include "sysemu/sysemu.h"
+
+enum {
+ TTY_PUT_CHAR = 0x00,
+ TTY_BYTES_READY = 0x04,
+ TTY_CMD = 0x08,
+
+ TTY_DATA_PTR = 0x10,
+ TTY_DATA_LEN = 0x14,
+ TTY_DATA_PTR_HIGH = 0x18,
+
+ TTY_CMD_INT_DISABLE = 0,
+ TTY_CMD_INT_ENABLE = 1,
+ TTY_CMD_WRITE_BUFFER = 2,
+ TTY_CMD_READ_BUFFER = 3,
+};
+
+struct tty_state {
+ SysBusDevice parent;
+
+ MemoryRegion iomem;
+ qemu_irq irq;
+
+ CharDriverState *cs;
+ uint64_t ptr;
+ uint32_t ptr_len;
+ uint32_t ready;
+ uint8_t data[128];
+ uint32_t data_count;
+};
+
+#define GOLDFISH_TTY_SAVE_VERSION 2
+
+#define TYPE_GOLDFISH_TTY "goldfish_tty"
+#define GOLDFISH_TTY(obj) OBJECT_CHECK(struct tty_state, (obj), TYPE_GOLDFISH_TTY)
+
+/* Number of instantiated TTYs */
+static int instance_id = 0;
+
+static void goldfish_tty_save(QEMUFile* f, void* opaque)
+{
+ struct tty_state* s = opaque;
+
+ qemu_put_be64( f, s->ptr );
+ qemu_put_be32( f, s->ptr_len );
+ qemu_put_byte( f, s->ready );
+ qemu_put_byte( f, s->data_count );
+ qemu_put_buffer( f, s->data, s->data_count );
+}
+
+static int goldfish_tty_load(QEMUFile* f, void* opaque, int version_id)
+{
+ struct tty_state* s = opaque;
+
+ if ((version_id != GOLDFISH_TTY_SAVE_VERSION) &&
+ (version_id != (GOLDFISH_TTY_SAVE_VERSION - 1))) {
+ return -1;
+ }
+ if (version_id == (GOLDFISH_TTY_SAVE_VERSION - 1)) {
+ s->ptr = (uint64_t)qemu_get_be32(f);
+ } else {
+ s->ptr = qemu_get_be64(f);
+ }
+ s->ptr_len = qemu_get_be32(f);
+ s->ready = qemu_get_byte(f);
+ s->data_count = qemu_get_byte(f);
+
+ if (qemu_get_buffer(f, s->data, s->data_count) < 0)
+ return -1;
+
+ qemu_set_irq(s->irq, s->ready && s->data_count > 0);
+ return 0;
+}
+
+static uint64_t goldfish_tty_read(void *opaque, hwaddr offset, unsigned size)
+{
+ struct tty_state *s = (struct tty_state *)opaque;
+
+ switch (offset) {
+ case TTY_BYTES_READY:
+ return s->data_count;
+ default:
+ cpu_abort(current_cpu,
+ "goldfish_tty_read: Bad offset %" HWADDR_PRIx "\n",
+ offset);
+ return 0;
+ }
+}
+
+static void goldfish_tty_write(void *opaque, hwaddr offset, uint64_t value, unsigned size)
+{
+ struct tty_state *s = (struct tty_state *)opaque;
+
+ switch(offset) {
+ case TTY_PUT_CHAR: {
+ uint8_t ch = value;
+ if(s->cs)
+ qemu_chr_fe_write(s->cs, &ch, 1);
+ } break;
+
+ case TTY_CMD:
+ switch(value) {
+ case TTY_CMD_INT_DISABLE:
+ if(s->ready) {
+ if(s->data_count > 0)
+ qemu_set_irq(s->irq, 0);
+ s->ready = 0;
+ }
+ break;
+
+ case TTY_CMD_INT_ENABLE:
+ if(!s->ready) {
+ if(s->data_count > 0)
+ qemu_set_irq(s->irq, 1);
+ s->ready = 1;
+ }
+ break;
+
+ case TTY_CMD_WRITE_BUFFER:
+ if(s->cs) {
+ int len;
+ target_ulong buf;
+
+ buf = s->ptr;
+ len = s->ptr_len;
+
+ while (len) {
+ char temp[64];
+ int to_write = sizeof(temp);
+ if (to_write > len)
+ to_write = len;
+
+ cpu_memory_rw_debug(current_cpu, buf, (uint8_t*)temp, to_write, 0);
+ qemu_chr_fe_write(s->cs, (const uint8_t*)temp, to_write);
+ buf += to_write;
+ len -= to_write;
+ }
+ }
+ break;
+
+ case TTY_CMD_READ_BUFFER:
+ if(s->ptr_len > s->data_count)
+ cpu_abort(current_cpu, "goldfish_tty_write: reading more data than available %d %d\n", s->ptr_len, s->data_count);
+ cpu_memory_rw_debug(current_cpu, s->ptr, s->data, s->ptr_len,1);
+ if(s->data_count > s->ptr_len)
+ memmove(s->data, s->data + s->ptr_len, s->data_count - s->ptr_len);
+ s->data_count -= s->ptr_len;
+ if(s->data_count == 0 && s->ready)
+ qemu_set_irq(s->irq, 0);
+ break;
+
+ default:
+ cpu_abort(current_cpu, "goldfish_tty_write: Bad command %" PRIx64 "\n", value);
+ };
+ break;
+
+ case TTY_DATA_PTR:
+ s->ptr = deposit64(s->ptr, 0, 32, value);
+ break;
+
+ case TTY_DATA_PTR_HIGH:
+ s->ptr = deposit64(s->ptr, 32, 32, value);
+ break;
+
+ case TTY_DATA_LEN:
+ s->ptr_len = value;
+ break;
+
+ default:
+ cpu_abort(current_cpu,
+ "goldfish_tty_write: Bad offset %" HWADDR_PRIx "\n",
+ offset);
+ }
+}
+
+static int tty_can_receive(void *opaque)
+{
+ struct tty_state *s = opaque;
+
+ return (sizeof(s->data) - s->data_count);
+}
+
+static void tty_receive(void *opaque, const uint8_t *buf, int size)
+{
+ struct tty_state *s = opaque;
+
+ memcpy(s->data + s->data_count, buf, size);
+ s->data_count += size;
+ if(s->data_count > 0 && s->ready)
+ qemu_set_irq(s->irq, 1);
+}
+
+static const MemoryRegionOps mips_qemu_ops = {
+ .read = goldfish_tty_read,
+ .write = goldfish_tty_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static void goldfish_tty_realize(DeviceState *dev, Error **errp)
+{
+ SysBusDevice *sbdev = SYS_BUS_DEVICE(dev);
+ struct tty_state *s = GOLDFISH_TTY(dev);
+ int i;
+
+ if ((instance_id + 1) == MAX_SERIAL_PORTS) {
+ cpu_abort(current_cpu, "goldfish_tty: MAX_SERIAL_PORTS(%d) reached\n", MAX_SERIAL_PORTS);
+ }
+
+ memory_region_init_io(&s->iomem, OBJECT(s), &mips_qemu_ops, s,
+ "goldfish_tty", 0x1000);
+ sysbus_init_mmio(sbdev, &s->iomem);
+ sysbus_init_irq(sbdev, &s->irq);
+
+ for(i = 0; i < MAX_SERIAL_PORTS; i++) {
+ if(serial_hds[i]) {
+ s->cs = serial_hds[i];
+ qemu_chr_add_handlers(serial_hds[i], tty_can_receive, tty_receive, NULL, s);
+ break;
+ }
+ }
+
+ register_savevm(NULL,
+ "goldfish_tty",
+ instance_id++,
+ GOLDFISH_TTY_SAVE_VERSION,
+ goldfish_tty_save,
+ goldfish_tty_load,
+ s);
+}
+
+static void goldfish_tty_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ dc->realize = goldfish_tty_realize;
+ dc->desc = "goldfish tty";
+}
+
+static const TypeInfo goldfish_tty_info = {
+ .name = TYPE_GOLDFISH_TTY,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(struct tty_state),
+ .class_init = goldfish_tty_class_init,
+};
+
+static void goldfish_tty_register(void)
+{
+ type_register_static(&goldfish_tty_info);
+}
+
+type_init(goldfish_tty_register);