aboutsummaryrefslogtreecommitdiffstats
path: root/hw/misc/macio
diff options
context:
space:
mode:
Diffstat (limited to 'hw/misc/macio')
-rw-r--r--hw/misc/macio/Kconfig11
-rw-r--r--hw/misc/macio/cuda.c629
-rw-r--r--hw/misc/macio/gpio.c219
-rw-r--r--hw/misc/macio/mac_dbdma.c940
-rw-r--r--hw/misc/macio/macio.c504
-rw-r--r--hw/misc/macio/meson.build8
-rw-r--r--hw/misc/macio/pmu.c871
-rw-r--r--hw/misc/macio/trace-events42
-rw-r--r--hw/misc/macio/trace.h1
9 files changed, 3225 insertions, 0 deletions
diff --git a/hw/misc/macio/Kconfig b/hw/misc/macio/Kconfig
new file mode 100644
index 000000000..c6caeb672
--- /dev/null
+++ b/hw/misc/macio/Kconfig
@@ -0,0 +1,11 @@
+config CUDA
+ bool
+
+config MAC_PMU
+ bool
+
+config MAC_DBDMA
+ bool
+
+config MACIO_GPIO
+ bool
diff --git a/hw/misc/macio/cuda.c b/hw/misc/macio/cuda.c
new file mode 100644
index 000000000..e917a6a09
--- /dev/null
+++ b/hw/misc/macio/cuda.c
@@ -0,0 +1,629 @@
+/*
+ * QEMU PowerMac CUDA device support
+ *
+ * Copyright (c) 2004-2007 Fabrice Bellard
+ * Copyright (c) 2007 Jocelyn Mayer
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu-common.h"
+#include "hw/ppc/mac.h"
+#include "hw/qdev-properties.h"
+#include "migration/vmstate.h"
+#include "hw/input/adb.h"
+#include "hw/misc/mos6522.h"
+#include "hw/misc/macio/cuda.h"
+#include "qapi/error.h"
+#include "qemu/timer.h"
+#include "sysemu/runstate.h"
+#include "qapi/error.h"
+#include "qemu/cutils.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+#include "trace.h"
+
+/* Bits in B data register: all active low */
+#define TREQ 0x08 /* Transfer request (input) */
+#define TACK 0x10 /* Transfer acknowledge (output) */
+#define TIP 0x20 /* Transfer in progress (output) */
+
+/* commands (1st byte) */
+#define ADB_PACKET 0
+#define CUDA_PACKET 1
+#define ERROR_PACKET 2
+#define TIMER_PACKET 3
+#define POWER_PACKET 4
+#define MACIIC_PACKET 5
+#define PMU_PACKET 6
+
+#define CUDA_TIMER_FREQ (4700000 / 6)
+
+/* CUDA returns time_t's offset from Jan 1, 1904, not 1970 */
+#define RTC_OFFSET 2082844800
+
+static void cuda_receive_packet_from_host(CUDAState *s,
+ const uint8_t *data, int len);
+
+/* MacOS uses timer 1 for calibration on startup, so we use
+ * the timebase frequency and cuda_get_counter_value() with
+ * cuda_get_load_time() to steer MacOS to calculate calibrate its timers
+ * correctly for both TCG and KVM (see commit b981289c49 "PPC: Cuda: Use cuda
+ * timer to expose tbfreq to guest" for more information) */
+
+static uint64_t cuda_get_counter_value(MOS6522State *s, MOS6522Timer *ti)
+{
+ MOS6522CUDAState *mcs = container_of(s, MOS6522CUDAState, parent_obj);
+ CUDAState *cs = container_of(mcs, CUDAState, mos6522_cuda);
+
+ /* Reverse of the tb calculation algorithm that Mac OS X uses on bootup */
+ uint64_t tb_diff = muldiv64(qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL),
+ cs->tb_frequency, NANOSECONDS_PER_SECOND) -
+ ti->load_time;
+
+ return (tb_diff * 0xBF401675E5DULL) / (cs->tb_frequency << 24);
+}
+
+static uint64_t cuda_get_load_time(MOS6522State *s, MOS6522Timer *ti)
+{
+ MOS6522CUDAState *mcs = container_of(s, MOS6522CUDAState, parent_obj);
+ CUDAState *cs = container_of(mcs, CUDAState, mos6522_cuda);
+
+ uint64_t load_time = muldiv64(qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL),
+ cs->tb_frequency, NANOSECONDS_PER_SECOND);
+ return load_time;
+}
+
+static void cuda_set_sr_int(void *opaque)
+{
+ CUDAState *s = opaque;
+ MOS6522CUDAState *mcs = &s->mos6522_cuda;
+ MOS6522State *ms = MOS6522(mcs);
+ MOS6522DeviceClass *mdc = MOS6522_GET_CLASS(ms);
+
+ mdc->set_sr_int(ms);
+}
+
+static void cuda_delay_set_sr_int(CUDAState *s)
+{
+ int64_t expire;
+
+ trace_cuda_delay_set_sr_int();
+
+ expire = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + s->sr_delay_ns;
+ timer_mod(s->sr_delay_timer, expire);
+}
+
+/* NOTE: TIP and TREQ are negated */
+static void cuda_update(CUDAState *s)
+{
+ MOS6522CUDAState *mcs = &s->mos6522_cuda;
+ MOS6522State *ms = MOS6522(mcs);
+ ADBBusState *adb_bus = &s->adb_bus;
+ int packet_received, len;
+
+ packet_received = 0;
+ if (!(ms->b & TIP)) {
+ /* transfer requested from host */
+
+ if (ms->acr & SR_OUT) {
+ /* data output */
+ if ((ms->b & (TACK | TIP)) != (s->last_b & (TACK | TIP))) {
+ if (s->data_out_index < sizeof(s->data_out)) {
+ if (s->data_out_index == 0) {
+ adb_autopoll_block(adb_bus);
+ }
+ trace_cuda_data_send(ms->sr);
+ s->data_out[s->data_out_index++] = ms->sr;
+ cuda_delay_set_sr_int(s);
+ }
+ }
+ } else {
+ if (s->data_in_index < s->data_in_size) {
+ /* data input */
+ if ((ms->b & (TACK | TIP)) != (s->last_b & (TACK | TIP))) {
+ ms->sr = s->data_in[s->data_in_index++];
+ trace_cuda_data_recv(ms->sr);
+ /* indicate end of transfer */
+ if (s->data_in_index >= s->data_in_size) {
+ ms->b = (ms->b | TREQ);
+ adb_autopoll_unblock(adb_bus);
+ }
+ cuda_delay_set_sr_int(s);
+ }
+ }
+ }
+ } else {
+ /* no transfer requested: handle sync case */
+ if ((s->last_b & TIP) && (ms->b & TACK) != (s->last_b & TACK)) {
+ /* update TREQ state each time TACK change state */
+ if (ms->b & TACK) {
+ ms->b = (ms->b | TREQ);
+ } else {
+ ms->b = (ms->b & ~TREQ);
+ }
+ cuda_delay_set_sr_int(s);
+ } else {
+ if (!(s->last_b & TIP)) {
+ /* handle end of host to cuda transfer */
+ packet_received = (s->data_out_index > 0);
+ /* always an IRQ at the end of transfer */
+ cuda_delay_set_sr_int(s);
+ }
+ /* signal if there is data to read */
+ if (s->data_in_index < s->data_in_size) {
+ ms->b = (ms->b & ~TREQ);
+ }
+ }
+ }
+
+ s->last_acr = ms->acr;
+ s->last_b = ms->b;
+
+ /* NOTE: cuda_receive_packet_from_host() can call cuda_update()
+ recursively */
+ if (packet_received) {
+ len = s->data_out_index;
+ s->data_out_index = 0;
+ cuda_receive_packet_from_host(s, s->data_out, len);
+ }
+}
+
+static void cuda_send_packet_to_host(CUDAState *s,
+ const uint8_t *data, int len)
+{
+ int i;
+
+ trace_cuda_packet_send(len);
+ for (i = 0; i < len; i++) {
+ trace_cuda_packet_send_data(i, data[i]);
+ }
+
+ memcpy(s->data_in, data, len);
+ s->data_in_size = len;
+ s->data_in_index = 0;
+ cuda_update(s);
+ cuda_delay_set_sr_int(s);
+}
+
+static void cuda_adb_poll(void *opaque)
+{
+ CUDAState *s = opaque;
+ ADBBusState *adb_bus = &s->adb_bus;
+ uint8_t obuf[ADB_MAX_OUT_LEN + 2];
+ int olen;
+
+ olen = adb_poll(adb_bus, obuf + 2, adb_bus->autopoll_mask);
+ if (olen > 0) {
+ obuf[0] = ADB_PACKET;
+ obuf[1] = 0x40; /* polled data */
+ cuda_send_packet_to_host(s, obuf, olen + 2);
+ }
+}
+
+/* description of commands */
+typedef struct CudaCommand {
+ uint8_t command;
+ const char *name;
+ bool (*handler)(CUDAState *s,
+ const uint8_t *in_args, int in_len,
+ uint8_t *out_args, int *out_len);
+} CudaCommand;
+
+static bool cuda_cmd_autopoll(CUDAState *s,
+ const uint8_t *in_data, int in_len,
+ uint8_t *out_data, int *out_len)
+{
+ ADBBusState *adb_bus = &s->adb_bus;
+ bool autopoll;
+
+ if (in_len != 1) {
+ return false;
+ }
+
+ autopoll = (in_data[0] != 0) ? true : false;
+
+ adb_set_autopoll_enabled(adb_bus, autopoll);
+ return true;
+}
+
+static bool cuda_cmd_set_autorate(CUDAState *s,
+ const uint8_t *in_data, int in_len,
+ uint8_t *out_data, int *out_len)
+{
+ ADBBusState *adb_bus = &s->adb_bus;
+
+ if (in_len != 1) {
+ return false;
+ }
+
+ /* we don't want a period of 0 ms */
+ /* FIXME: check what real hardware does */
+ if (in_data[0] == 0) {
+ return false;
+ }
+
+ adb_set_autopoll_rate_ms(adb_bus, in_data[0]);
+ return true;
+}
+
+static bool cuda_cmd_set_device_list(CUDAState *s,
+ const uint8_t *in_data, int in_len,
+ uint8_t *out_data, int *out_len)
+{
+ ADBBusState *adb_bus = &s->adb_bus;
+ uint16_t mask;
+
+ if (in_len != 2) {
+ return false;
+ }
+
+ mask = (((uint16_t)in_data[0]) << 8) | in_data[1];
+
+ adb_set_autopoll_mask(adb_bus, mask);
+ return true;
+}
+
+static bool cuda_cmd_powerdown(CUDAState *s,
+ const uint8_t *in_data, int in_len,
+ uint8_t *out_data, int *out_len)
+{
+ if (in_len != 0) {
+ return false;
+ }
+
+ qemu_system_shutdown_request(SHUTDOWN_CAUSE_GUEST_SHUTDOWN);
+ return true;
+}
+
+static bool cuda_cmd_reset_system(CUDAState *s,
+ const uint8_t *in_data, int in_len,
+ uint8_t *out_data, int *out_len)
+{
+ if (in_len != 0) {
+ return false;
+ }
+
+ qemu_system_reset_request(SHUTDOWN_CAUSE_GUEST_RESET);
+ return true;
+}
+
+static bool cuda_cmd_set_file_server_flag(CUDAState *s,
+ const uint8_t *in_data, int in_len,
+ uint8_t *out_data, int *out_len)
+{
+ if (in_len != 1) {
+ return false;
+ }
+
+ qemu_log_mask(LOG_UNIMP,
+ "CUDA: unimplemented command FILE_SERVER_FLAG %d\n",
+ in_data[0]);
+ return true;
+}
+
+static bool cuda_cmd_set_power_message(CUDAState *s,
+ const uint8_t *in_data, int in_len,
+ uint8_t *out_data, int *out_len)
+{
+ if (in_len != 1) {
+ return false;
+ }
+
+ qemu_log_mask(LOG_UNIMP,
+ "CUDA: unimplemented command SET_POWER_MESSAGE %d\n",
+ in_data[0]);
+ return true;
+}
+
+static bool cuda_cmd_get_time(CUDAState *s,
+ const uint8_t *in_data, int in_len,
+ uint8_t *out_data, int *out_len)
+{
+ uint32_t ti;
+
+ if (in_len != 0) {
+ return false;
+ }
+
+ ti = s->tick_offset + (qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL)
+ / NANOSECONDS_PER_SECOND);
+ out_data[0] = ti >> 24;
+ out_data[1] = ti >> 16;
+ out_data[2] = ti >> 8;
+ out_data[3] = ti;
+ *out_len = 4;
+ return true;
+}
+
+static bool cuda_cmd_set_time(CUDAState *s,
+ const uint8_t *in_data, int in_len,
+ uint8_t *out_data, int *out_len)
+{
+ uint32_t ti;
+
+ if (in_len != 4) {
+ return false;
+ }
+
+ ti = (((uint32_t)in_data[0]) << 24) + (((uint32_t)in_data[1]) << 16)
+ + (((uint32_t)in_data[2]) << 8) + in_data[3];
+ s->tick_offset = ti - (qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL)
+ / NANOSECONDS_PER_SECOND);
+ return true;
+}
+
+static const CudaCommand handlers[] = {
+ { CUDA_AUTOPOLL, "AUTOPOLL", cuda_cmd_autopoll },
+ { CUDA_SET_AUTO_RATE, "SET_AUTO_RATE", cuda_cmd_set_autorate },
+ { CUDA_SET_DEVICE_LIST, "SET_DEVICE_LIST", cuda_cmd_set_device_list },
+ { CUDA_POWERDOWN, "POWERDOWN", cuda_cmd_powerdown },
+ { CUDA_RESET_SYSTEM, "RESET_SYSTEM", cuda_cmd_reset_system },
+ { CUDA_FILE_SERVER_FLAG, "FILE_SERVER_FLAG",
+ cuda_cmd_set_file_server_flag },
+ { CUDA_SET_POWER_MESSAGES, "SET_POWER_MESSAGES",
+ cuda_cmd_set_power_message },
+ { CUDA_GET_TIME, "GET_TIME", cuda_cmd_get_time },
+ { CUDA_SET_TIME, "SET_TIME", cuda_cmd_set_time },
+};
+
+static void cuda_receive_packet(CUDAState *s,
+ const uint8_t *data, int len)
+{
+ uint8_t obuf[16] = { CUDA_PACKET, 0, data[0] };
+ int i, out_len = 0;
+
+ for (i = 0; i < ARRAY_SIZE(handlers); i++) {
+ const CudaCommand *desc = &handlers[i];
+ if (desc->command == data[0]) {
+ trace_cuda_receive_packet_cmd(desc->name);
+ out_len = 0;
+ if (desc->handler(s, data + 1, len - 1, obuf + 3, &out_len)) {
+ cuda_send_packet_to_host(s, obuf, 3 + out_len);
+ } else {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "CUDA: %s: wrong parameters %d\n",
+ desc->name, len);
+ obuf[0] = ERROR_PACKET;
+ obuf[1] = 0x5; /* bad parameters */
+ obuf[2] = CUDA_PACKET;
+ obuf[3] = data[0];
+ cuda_send_packet_to_host(s, obuf, 4);
+ }
+ return;
+ }
+ }
+
+ qemu_log_mask(LOG_GUEST_ERROR, "CUDA: unknown command 0x%02x\n", data[0]);
+ obuf[0] = ERROR_PACKET;
+ obuf[1] = 0x2; /* unknown command */
+ obuf[2] = CUDA_PACKET;
+ obuf[3] = data[0];
+ cuda_send_packet_to_host(s, obuf, 4);
+}
+
+static void cuda_receive_packet_from_host(CUDAState *s,
+ const uint8_t *data, int len)
+{
+ int i;
+
+ trace_cuda_packet_receive(len);
+ for (i = 0; i < len; i++) {
+ trace_cuda_packet_receive_data(i, data[i]);
+ }
+
+ switch(data[0]) {
+ case ADB_PACKET:
+ {
+ uint8_t obuf[ADB_MAX_OUT_LEN + 3];
+ int olen;
+ olen = adb_request(&s->adb_bus, obuf + 2, data + 1, len - 1);
+ if (olen > 0) {
+ obuf[0] = ADB_PACKET;
+ obuf[1] = 0x00;
+ cuda_send_packet_to_host(s, obuf, olen + 2);
+ } else {
+ /* error */
+ obuf[0] = ADB_PACKET;
+ obuf[1] = -olen;
+ obuf[2] = data[1];
+ olen = 0;
+ cuda_send_packet_to_host(s, obuf, olen + 3);
+ }
+ }
+ break;
+ case CUDA_PACKET:
+ cuda_receive_packet(s, data + 1, len - 1);
+ break;
+ }
+}
+
+static uint64_t mos6522_cuda_read(void *opaque, hwaddr addr, unsigned size)
+{
+ CUDAState *s = opaque;
+ MOS6522CUDAState *mcs = &s->mos6522_cuda;
+ MOS6522State *ms = MOS6522(mcs);
+
+ addr = (addr >> 9) & 0xf;
+ return mos6522_read(ms, addr, size);
+}
+
+static void mos6522_cuda_write(void *opaque, hwaddr addr, uint64_t val,
+ unsigned size)
+{
+ CUDAState *s = opaque;
+ MOS6522CUDAState *mcs = &s->mos6522_cuda;
+ MOS6522State *ms = MOS6522(mcs);
+
+ addr = (addr >> 9) & 0xf;
+ mos6522_write(ms, addr, val, size);
+}
+
+static const MemoryRegionOps mos6522_cuda_ops = {
+ .read = mos6522_cuda_read,
+ .write = mos6522_cuda_write,
+ .endianness = DEVICE_BIG_ENDIAN,
+ .valid = {
+ .min_access_size = 1,
+ .max_access_size = 1,
+ },
+};
+
+static const VMStateDescription vmstate_cuda = {
+ .name = "cuda",
+ .version_id = 6,
+ .minimum_version_id = 6,
+ .fields = (VMStateField[]) {
+ VMSTATE_STRUCT(mos6522_cuda.parent_obj, CUDAState, 0, vmstate_mos6522,
+ MOS6522State),
+ VMSTATE_UINT8(last_b, CUDAState),
+ VMSTATE_UINT8(last_acr, CUDAState),
+ VMSTATE_INT32(data_in_size, CUDAState),
+ VMSTATE_INT32(data_in_index, CUDAState),
+ VMSTATE_INT32(data_out_index, CUDAState),
+ VMSTATE_BUFFER(data_in, CUDAState),
+ VMSTATE_BUFFER(data_out, CUDAState),
+ VMSTATE_UINT32(tick_offset, CUDAState),
+ VMSTATE_TIMER_PTR(sr_delay_timer, CUDAState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void cuda_reset(DeviceState *dev)
+{
+ CUDAState *s = CUDA(dev);
+ ADBBusState *adb_bus = &s->adb_bus;
+
+ s->data_in_size = 0;
+ s->data_in_index = 0;
+ s->data_out_index = 0;
+
+ adb_set_autopoll_enabled(adb_bus, false);
+}
+
+static void cuda_realize(DeviceState *dev, Error **errp)
+{
+ CUDAState *s = CUDA(dev);
+ SysBusDevice *sbd;
+ ADBBusState *adb_bus = &s->adb_bus;
+ struct tm tm;
+
+ if (!sysbus_realize(SYS_BUS_DEVICE(&s->mos6522_cuda), errp)) {
+ return;
+ }
+
+ /* Pass IRQ from 6522 */
+ sbd = SYS_BUS_DEVICE(s);
+ sysbus_pass_irq(sbd, SYS_BUS_DEVICE(&s->mos6522_cuda));
+
+ qemu_get_timedate(&tm, 0);
+ s->tick_offset = (uint32_t)mktimegm(&tm) + RTC_OFFSET;
+
+ s->sr_delay_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, cuda_set_sr_int, s);
+ s->sr_delay_ns = 20 * SCALE_US;
+
+ adb_register_autopoll_callback(adb_bus, cuda_adb_poll, s);
+}
+
+static void cuda_init(Object *obj)
+{
+ CUDAState *s = CUDA(obj);
+ SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
+
+ object_initialize_child(obj, "mos6522-cuda", &s->mos6522_cuda,
+ TYPE_MOS6522_CUDA);
+
+ memory_region_init_io(&s->mem, obj, &mos6522_cuda_ops, s, "cuda", 0x2000);
+ sysbus_init_mmio(sbd, &s->mem);
+
+ qbus_init(&s->adb_bus, sizeof(s->adb_bus), TYPE_ADB_BUS,
+ DEVICE(obj), "adb.0");
+}
+
+static Property cuda_properties[] = {
+ DEFINE_PROP_UINT64("timebase-frequency", CUDAState, tb_frequency, 0),
+ DEFINE_PROP_END_OF_LIST()
+};
+
+static void cuda_class_init(ObjectClass *oc, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(oc);
+
+ dc->realize = cuda_realize;
+ dc->reset = cuda_reset;
+ dc->vmsd = &vmstate_cuda;
+ device_class_set_props(dc, cuda_properties);
+ set_bit(DEVICE_CATEGORY_BRIDGE, dc->categories);
+}
+
+static const TypeInfo cuda_type_info = {
+ .name = TYPE_CUDA,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(CUDAState),
+ .instance_init = cuda_init,
+ .class_init = cuda_class_init,
+};
+
+static void mos6522_cuda_portB_write(MOS6522State *s)
+{
+ MOS6522CUDAState *mcs = container_of(s, MOS6522CUDAState, parent_obj);
+ CUDAState *cs = container_of(mcs, CUDAState, mos6522_cuda);
+
+ cuda_update(cs);
+}
+
+static void mos6522_cuda_reset(DeviceState *dev)
+{
+ MOS6522State *ms = MOS6522(dev);
+ MOS6522DeviceClass *mdc = MOS6522_GET_CLASS(ms);
+
+ mdc->parent_reset(dev);
+
+ ms->timers[0].frequency = CUDA_TIMER_FREQ;
+ ms->timers[1].frequency = (SCALE_US * 6000) / 4700;
+}
+
+static void mos6522_cuda_class_init(ObjectClass *oc, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(oc);
+ MOS6522DeviceClass *mdc = MOS6522_CLASS(oc);
+
+ dc->reset = mos6522_cuda_reset;
+ mdc->portB_write = mos6522_cuda_portB_write;
+ mdc->get_timer1_counter_value = cuda_get_counter_value;
+ mdc->get_timer2_counter_value = cuda_get_counter_value;
+ mdc->get_timer1_load_time = cuda_get_load_time;
+ mdc->get_timer2_load_time = cuda_get_load_time;
+}
+
+static const TypeInfo mos6522_cuda_type_info = {
+ .name = TYPE_MOS6522_CUDA,
+ .parent = TYPE_MOS6522,
+ .instance_size = sizeof(MOS6522CUDAState),
+ .class_init = mos6522_cuda_class_init,
+};
+
+static void cuda_register_types(void)
+{
+ type_register_static(&mos6522_cuda_type_info);
+ type_register_static(&cuda_type_info);
+}
+
+type_init(cuda_register_types)
diff --git a/hw/misc/macio/gpio.c b/hw/misc/macio/gpio.c
new file mode 100644
index 000000000..b1bcf830c
--- /dev/null
+++ b/hw/misc/macio/gpio.c
@@ -0,0 +1,219 @@
+/*
+ * PowerMac NewWorld MacIO GPIO emulation
+ *
+ * Copyright (c) 2016 Benjamin Herrenschmidt
+ * Copyright (c) 2018 Mark Cave-Ayland
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "qemu/osdep.h"
+#include "hw/ppc/mac.h"
+#include "hw/qdev-properties.h"
+#include "migration/vmstate.h"
+#include "hw/misc/macio/macio.h"
+#include "hw/misc/macio/gpio.h"
+#include "hw/nmi.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+#include "trace.h"
+
+
+void macio_set_gpio(MacIOGPIOState *s, uint32_t gpio, bool state)
+{
+ uint8_t new_reg;
+
+ trace_macio_set_gpio(gpio, state);
+
+ if (s->gpio_regs[gpio] & 4) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "GPIO: Setting GPIO %d while it's an output\n", gpio);
+ }
+
+ new_reg = s->gpio_regs[gpio] & ~2;
+ if (state) {
+ new_reg |= 2;
+ }
+
+ if (new_reg == s->gpio_regs[gpio]) {
+ return;
+ }
+
+ s->gpio_regs[gpio] = new_reg;
+
+ /*
+ * Note that we probably need to get access to the MPIC config to
+ * decode polarity since qemu always use "raise" regardless.
+ *
+ * For now, we hard wire known GPIOs
+ */
+
+ switch (gpio) {
+ case 1:
+ /* Level low */
+ if (!state) {
+ trace_macio_gpio_irq_assert(gpio);
+ qemu_irq_raise(s->gpio_extirqs[gpio]);
+ } else {
+ trace_macio_gpio_irq_deassert(gpio);
+ qemu_irq_lower(s->gpio_extirqs[gpio]);
+ }
+ break;
+
+ case 9:
+ /* Edge, triggered by NMI below */
+ if (state) {
+ trace_macio_gpio_irq_assert(gpio);
+ qemu_irq_raise(s->gpio_extirqs[gpio]);
+ } else {
+ trace_macio_gpio_irq_deassert(gpio);
+ qemu_irq_lower(s->gpio_extirqs[gpio]);
+ }
+ break;
+
+ default:
+ qemu_log_mask(LOG_UNIMP, "GPIO: setting unimplemented GPIO %d", gpio);
+ }
+}
+
+static void macio_gpio_write(void *opaque, hwaddr addr, uint64_t value,
+ unsigned size)
+{
+ MacIOGPIOState *s = opaque;
+ uint8_t ibit;
+
+ trace_macio_gpio_write(addr, value);
+
+ /* Levels regs are read-only */
+ if (addr < 8) {
+ return;
+ }
+
+ addr -= 8;
+ if (addr < 36) {
+ value &= ~2;
+
+ if (value & 4) {
+ ibit = (value & 1) << 1;
+ } else {
+ ibit = s->gpio_regs[addr] & 2;
+ }
+
+ s->gpio_regs[addr] = value | ibit;
+ }
+}
+
+static uint64_t macio_gpio_read(void *opaque, hwaddr addr, unsigned size)
+{
+ MacIOGPIOState *s = opaque;
+ uint64_t val = 0;
+
+ /* Levels regs */
+ if (addr < 8) {
+ val = s->gpio_levels[addr];
+ } else {
+ addr -= 8;
+
+ if (addr < 36) {
+ val = s->gpio_regs[addr];
+ }
+ }
+
+ trace_macio_gpio_write(addr, val);
+ return val;
+}
+
+static const MemoryRegionOps macio_gpio_ops = {
+ .read = macio_gpio_read,
+ .write = macio_gpio_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .impl = {
+ .min_access_size = 1,
+ .max_access_size = 1,
+ },
+};
+
+static void macio_gpio_init(Object *obj)
+{
+ SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
+ MacIOGPIOState *s = MACIO_GPIO(obj);
+ int i;
+
+ for (i = 0; i < 10; i++) {
+ sysbus_init_irq(sbd, &s->gpio_extirqs[i]);
+ }
+
+ memory_region_init_io(&s->gpiomem, OBJECT(s), &macio_gpio_ops, obj,
+ "gpio", 0x30);
+ sysbus_init_mmio(sbd, &s->gpiomem);
+}
+
+static const VMStateDescription vmstate_macio_gpio = {
+ .name = "macio_gpio",
+ .version_id = 0,
+ .minimum_version_id = 0,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT8_ARRAY(gpio_levels, MacIOGPIOState, 8),
+ VMSTATE_UINT8_ARRAY(gpio_regs, MacIOGPIOState, 36),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void macio_gpio_reset(DeviceState *dev)
+{
+ MacIOGPIOState *s = MACIO_GPIO(dev);
+
+ /* GPIO 1 is up by default */
+ macio_set_gpio(s, 1, true);
+}
+
+static void macio_gpio_nmi(NMIState *n, int cpu_index, Error **errp)
+{
+ macio_set_gpio(MACIO_GPIO(n), 9, true);
+ macio_set_gpio(MACIO_GPIO(n), 9, false);
+}
+
+static void macio_gpio_class_init(ObjectClass *oc, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(oc);
+ NMIClass *nc = NMI_CLASS(oc);
+
+ dc->reset = macio_gpio_reset;
+ dc->vmsd = &vmstate_macio_gpio;
+ nc->nmi_monitor_handler = macio_gpio_nmi;
+}
+
+static const TypeInfo macio_gpio_init_info = {
+ .name = TYPE_MACIO_GPIO,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(MacIOGPIOState),
+ .instance_init = macio_gpio_init,
+ .class_init = macio_gpio_class_init,
+ .interfaces = (InterfaceInfo[]) {
+ { TYPE_NMI },
+ { }
+ },
+};
+
+static void macio_gpio_register_types(void)
+{
+ type_register_static(&macio_gpio_init_info);
+}
+
+type_init(macio_gpio_register_types)
diff --git a/hw/misc/macio/mac_dbdma.c b/hw/misc/macio/mac_dbdma.c
new file mode 100644
index 000000000..e220f1a92
--- /dev/null
+++ b/hw/misc/macio/mac_dbdma.c
@@ -0,0 +1,940 @@
+/*
+ * PowerMac descriptor-based DMA emulation
+ *
+ * Copyright (c) 2005-2007 Fabrice Bellard
+ * Copyright (c) 2007 Jocelyn Mayer
+ * Copyright (c) 2009 Laurent Vivier
+ *
+ * some parts from linux-2.6.28, arch/powerpc/include/asm/dbdma.h
+ *
+ * Definitions for using the Apple Descriptor-Based DMA controller
+ * in Power Macintosh computers.
+ *
+ * Copyright (C) 1996 Paul Mackerras.
+ *
+ * some parts from mol 0.9.71
+ *
+ * Descriptor based DMA emulation
+ *
+ * Copyright (C) 1998-2004 Samuel Rydh (samuel@ibrium.se)
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "qemu/osdep.h"
+#include "hw/irq.h"
+#include "hw/ppc/mac_dbdma.h"
+#include "migration/vmstate.h"
+#include "qemu/main-loop.h"
+#include "qemu/module.h"
+#include "qemu/log.h"
+#include "sysemu/dma.h"
+
+/* debug DBDMA */
+#define DEBUG_DBDMA 0
+#define DEBUG_DBDMA_CHANMASK ((1ull << DBDMA_CHANNELS) - 1)
+
+#define DBDMA_DPRINTF(fmt, ...) do { \
+ if (DEBUG_DBDMA) { \
+ printf("DBDMA: " fmt , ## __VA_ARGS__); \
+ } \
+} while (0)
+
+#define DBDMA_DPRINTFCH(ch, fmt, ...) do { \
+ if (DEBUG_DBDMA) { \
+ if ((1ul << (ch)->channel) & DEBUG_DBDMA_CHANMASK) { \
+ printf("DBDMA[%02x]: " fmt , (ch)->channel, ## __VA_ARGS__); \
+ } \
+ } \
+} while (0)
+
+/*
+ */
+
+static DBDMAState *dbdma_from_ch(DBDMA_channel *ch)
+{
+ return container_of(ch, DBDMAState, channels[ch->channel]);
+}
+
+#if DEBUG_DBDMA
+static void dump_dbdma_cmd(DBDMA_channel *ch, dbdma_cmd *cmd)
+{
+ DBDMA_DPRINTFCH(ch, "dbdma_cmd %p\n", cmd);
+ DBDMA_DPRINTFCH(ch, " req_count 0x%04x\n", le16_to_cpu(cmd->req_count));
+ DBDMA_DPRINTFCH(ch, " command 0x%04x\n", le16_to_cpu(cmd->command));
+ DBDMA_DPRINTFCH(ch, " phy_addr 0x%08x\n", le32_to_cpu(cmd->phy_addr));
+ DBDMA_DPRINTFCH(ch, " cmd_dep 0x%08x\n", le32_to_cpu(cmd->cmd_dep));
+ DBDMA_DPRINTFCH(ch, " res_count 0x%04x\n", le16_to_cpu(cmd->res_count));
+ DBDMA_DPRINTFCH(ch, " xfer_status 0x%04x\n",
+ le16_to_cpu(cmd->xfer_status));
+}
+#else
+static void dump_dbdma_cmd(DBDMA_channel *ch, dbdma_cmd *cmd)
+{
+}
+#endif
+static void dbdma_cmdptr_load(DBDMA_channel *ch)
+{
+ DBDMA_DPRINTFCH(ch, "dbdma_cmdptr_load 0x%08x\n",
+ ch->regs[DBDMA_CMDPTR_LO]);
+ dma_memory_read(&address_space_memory, ch->regs[DBDMA_CMDPTR_LO],
+ &ch->current, sizeof(dbdma_cmd));
+}
+
+static void dbdma_cmdptr_save(DBDMA_channel *ch)
+{
+ DBDMA_DPRINTFCH(ch, "-> update 0x%08x stat=0x%08x, res=0x%04x\n",
+ ch->regs[DBDMA_CMDPTR_LO],
+ le16_to_cpu(ch->current.xfer_status),
+ le16_to_cpu(ch->current.res_count));
+ dma_memory_write(&address_space_memory, ch->regs[DBDMA_CMDPTR_LO],
+ &ch->current, sizeof(dbdma_cmd));
+}
+
+static void kill_channel(DBDMA_channel *ch)
+{
+ DBDMA_DPRINTFCH(ch, "kill_channel\n");
+
+ ch->regs[DBDMA_STATUS] |= DEAD;
+ ch->regs[DBDMA_STATUS] &= ~ACTIVE;
+
+ qemu_irq_raise(ch->irq);
+}
+
+static void conditional_interrupt(DBDMA_channel *ch)
+{
+ dbdma_cmd *current = &ch->current;
+ uint16_t intr;
+ uint16_t sel_mask, sel_value;
+ uint32_t status;
+ int cond;
+
+ DBDMA_DPRINTFCH(ch, "%s\n", __func__);
+
+ intr = le16_to_cpu(current->command) & INTR_MASK;
+
+ switch(intr) {
+ case INTR_NEVER: /* don't interrupt */
+ return;
+ case INTR_ALWAYS: /* always interrupt */
+ qemu_irq_raise(ch->irq);
+ DBDMA_DPRINTFCH(ch, "%s: raise\n", __func__);
+ return;
+ }
+
+ status = ch->regs[DBDMA_STATUS] & DEVSTAT;
+
+ sel_mask = (ch->regs[DBDMA_INTR_SEL] >> 16) & 0x0f;
+ sel_value = ch->regs[DBDMA_INTR_SEL] & 0x0f;
+
+ cond = (status & sel_mask) == (sel_value & sel_mask);
+
+ switch(intr) {
+ case INTR_IFSET: /* intr if condition bit is 1 */
+ if (cond) {
+ qemu_irq_raise(ch->irq);
+ DBDMA_DPRINTFCH(ch, "%s: raise\n", __func__);
+ }
+ return;
+ case INTR_IFCLR: /* intr if condition bit is 0 */
+ if (!cond) {
+ qemu_irq_raise(ch->irq);
+ DBDMA_DPRINTFCH(ch, "%s: raise\n", __func__);
+ }
+ return;
+ }
+}
+
+static int conditional_wait(DBDMA_channel *ch)
+{
+ dbdma_cmd *current = &ch->current;
+ uint16_t wait;
+ uint16_t sel_mask, sel_value;
+ uint32_t status;
+ int cond;
+ int res = 0;
+
+ wait = le16_to_cpu(current->command) & WAIT_MASK;
+ switch(wait) {
+ case WAIT_NEVER: /* don't wait */
+ return 0;
+ case WAIT_ALWAYS: /* always wait */
+ DBDMA_DPRINTFCH(ch, " [WAIT_ALWAYS]\n");
+ return 1;
+ }
+
+ status = ch->regs[DBDMA_STATUS] & DEVSTAT;
+
+ sel_mask = (ch->regs[DBDMA_WAIT_SEL] >> 16) & 0x0f;
+ sel_value = ch->regs[DBDMA_WAIT_SEL] & 0x0f;
+
+ cond = (status & sel_mask) == (sel_value & sel_mask);
+
+ switch(wait) {
+ case WAIT_IFSET: /* wait if condition bit is 1 */
+ if (cond) {
+ res = 1;
+ }
+ DBDMA_DPRINTFCH(ch, " [WAIT_IFSET=%d]\n", res);
+ break;
+ case WAIT_IFCLR: /* wait if condition bit is 0 */
+ if (!cond) {
+ res = 1;
+ }
+ DBDMA_DPRINTFCH(ch, " [WAIT_IFCLR=%d]\n", res);
+ break;
+ }
+ return res;
+}
+
+static void next(DBDMA_channel *ch)
+{
+ uint32_t cp;
+
+ ch->regs[DBDMA_STATUS] &= ~BT;
+
+ cp = ch->regs[DBDMA_CMDPTR_LO];
+ ch->regs[DBDMA_CMDPTR_LO] = cp + sizeof(dbdma_cmd);
+ dbdma_cmdptr_load(ch);
+}
+
+static void branch(DBDMA_channel *ch)
+{
+ dbdma_cmd *current = &ch->current;
+
+ ch->regs[DBDMA_CMDPTR_LO] = le32_to_cpu(current->cmd_dep);
+ ch->regs[DBDMA_STATUS] |= BT;
+ dbdma_cmdptr_load(ch);
+}
+
+static void conditional_branch(DBDMA_channel *ch)
+{
+ dbdma_cmd *current = &ch->current;
+ uint16_t br;
+ uint16_t sel_mask, sel_value;
+ uint32_t status;
+ int cond;
+
+ /* check if we must branch */
+
+ br = le16_to_cpu(current->command) & BR_MASK;
+
+ switch(br) {
+ case BR_NEVER: /* don't branch */
+ next(ch);
+ return;
+ case BR_ALWAYS: /* always branch */
+ DBDMA_DPRINTFCH(ch, " [BR_ALWAYS]\n");
+ branch(ch);
+ return;
+ }
+
+ status = ch->regs[DBDMA_STATUS] & DEVSTAT;
+
+ sel_mask = (ch->regs[DBDMA_BRANCH_SEL] >> 16) & 0x0f;
+ sel_value = ch->regs[DBDMA_BRANCH_SEL] & 0x0f;
+
+ cond = (status & sel_mask) == (sel_value & sel_mask);
+
+ switch(br) {
+ case BR_IFSET: /* branch if condition bit is 1 */
+ if (cond) {
+ DBDMA_DPRINTFCH(ch, " [BR_IFSET = 1]\n");
+ branch(ch);
+ } else {
+ DBDMA_DPRINTFCH(ch, " [BR_IFSET = 0]\n");
+ next(ch);
+ }
+ return;
+ case BR_IFCLR: /* branch if condition bit is 0 */
+ if (!cond) {
+ DBDMA_DPRINTFCH(ch, " [BR_IFCLR = 1]\n");
+ branch(ch);
+ } else {
+ DBDMA_DPRINTFCH(ch, " [BR_IFCLR = 0]\n");
+ next(ch);
+ }
+ return;
+ }
+}
+
+static void channel_run(DBDMA_channel *ch);
+
+static void dbdma_end(DBDMA_io *io)
+{
+ DBDMA_channel *ch = io->channel;
+ dbdma_cmd *current = &ch->current;
+
+ DBDMA_DPRINTFCH(ch, "%s\n", __func__);
+
+ if (conditional_wait(ch))
+ goto wait;
+
+ current->xfer_status = cpu_to_le16(ch->regs[DBDMA_STATUS]);
+ current->res_count = cpu_to_le16(io->len);
+ dbdma_cmdptr_save(ch);
+ if (io->is_last)
+ ch->regs[DBDMA_STATUS] &= ~FLUSH;
+
+ conditional_interrupt(ch);
+ conditional_branch(ch);
+
+wait:
+ /* Indicate that we're ready for a new DMA round */
+ ch->io.processing = false;
+
+ if ((ch->regs[DBDMA_STATUS] & RUN) &&
+ (ch->regs[DBDMA_STATUS] & ACTIVE))
+ channel_run(ch);
+}
+
+static void start_output(DBDMA_channel *ch, int key, uint32_t addr,
+ uint16_t req_count, int is_last)
+{
+ DBDMA_DPRINTFCH(ch, "start_output\n");
+
+ /* KEY_REGS, KEY_DEVICE and KEY_STREAM
+ * are not implemented in the mac-io chip
+ */
+
+ DBDMA_DPRINTFCH(ch, "addr 0x%x key 0x%x\n", addr, key);
+ if (!addr || key > KEY_STREAM3) {
+ kill_channel(ch);
+ return;
+ }
+
+ ch->io.addr = addr;
+ ch->io.len = req_count;
+ ch->io.is_last = is_last;
+ ch->io.dma_end = dbdma_end;
+ ch->io.is_dma_out = 1;
+ ch->io.processing = true;
+ if (ch->rw) {
+ ch->rw(&ch->io);
+ }
+}
+
+static void start_input(DBDMA_channel *ch, int key, uint32_t addr,
+ uint16_t req_count, int is_last)
+{
+ DBDMA_DPRINTFCH(ch, "start_input\n");
+
+ /* KEY_REGS, KEY_DEVICE and KEY_STREAM
+ * are not implemented in the mac-io chip
+ */
+
+ DBDMA_DPRINTFCH(ch, "addr 0x%x key 0x%x\n", addr, key);
+ if (!addr || key > KEY_STREAM3) {
+ kill_channel(ch);
+ return;
+ }
+
+ ch->io.addr = addr;
+ ch->io.len = req_count;
+ ch->io.is_last = is_last;
+ ch->io.dma_end = dbdma_end;
+ ch->io.is_dma_out = 0;
+ ch->io.processing = true;
+ if (ch->rw) {
+ ch->rw(&ch->io);
+ }
+}
+
+static void load_word(DBDMA_channel *ch, int key, uint32_t addr,
+ uint16_t len)
+{
+ dbdma_cmd *current = &ch->current;
+
+ DBDMA_DPRINTFCH(ch, "load_word %d bytes, addr=%08x\n", len, addr);
+
+ /* only implements KEY_SYSTEM */
+
+ if (key != KEY_SYSTEM) {
+ printf("DBDMA: LOAD_WORD, unimplemented key %x\n", key);
+ kill_channel(ch);
+ return;
+ }
+
+ dma_memory_read(&address_space_memory, addr, &current->cmd_dep, len);
+
+ if (conditional_wait(ch))
+ goto wait;
+
+ current->xfer_status = cpu_to_le16(ch->regs[DBDMA_STATUS]);
+ dbdma_cmdptr_save(ch);
+ ch->regs[DBDMA_STATUS] &= ~FLUSH;
+
+ conditional_interrupt(ch);
+ next(ch);
+
+wait:
+ DBDMA_kick(dbdma_from_ch(ch));
+}
+
+static void store_word(DBDMA_channel *ch, int key, uint32_t addr,
+ uint16_t len)
+{
+ dbdma_cmd *current = &ch->current;
+
+ DBDMA_DPRINTFCH(ch, "store_word %d bytes, addr=%08x pa=%x\n",
+ len, addr, le32_to_cpu(current->cmd_dep));
+
+ /* only implements KEY_SYSTEM */
+
+ if (key != KEY_SYSTEM) {
+ printf("DBDMA: STORE_WORD, unimplemented key %x\n", key);
+ kill_channel(ch);
+ return;
+ }
+
+ dma_memory_write(&address_space_memory, addr, &current->cmd_dep, len);
+
+ if (conditional_wait(ch))
+ goto wait;
+
+ current->xfer_status = cpu_to_le16(ch->regs[DBDMA_STATUS]);
+ dbdma_cmdptr_save(ch);
+ ch->regs[DBDMA_STATUS] &= ~FLUSH;
+
+ conditional_interrupt(ch);
+ next(ch);
+
+wait:
+ DBDMA_kick(dbdma_from_ch(ch));
+}
+
+static void nop(DBDMA_channel *ch)
+{
+ dbdma_cmd *current = &ch->current;
+
+ if (conditional_wait(ch))
+ goto wait;
+
+ current->xfer_status = cpu_to_le16(ch->regs[DBDMA_STATUS]);
+ dbdma_cmdptr_save(ch);
+
+ conditional_interrupt(ch);
+ conditional_branch(ch);
+
+wait:
+ DBDMA_kick(dbdma_from_ch(ch));
+}
+
+static void stop(DBDMA_channel *ch)
+{
+ ch->regs[DBDMA_STATUS] &= ~(ACTIVE);
+
+ /* the stop command does not increment command pointer */
+}
+
+static void channel_run(DBDMA_channel *ch)
+{
+ dbdma_cmd *current = &ch->current;
+ uint16_t cmd, key;
+ uint16_t req_count;
+ uint32_t phy_addr;
+
+ DBDMA_DPRINTFCH(ch, "channel_run\n");
+ dump_dbdma_cmd(ch, current);
+
+ /* clear WAKE flag at command fetch */
+
+ ch->regs[DBDMA_STATUS] &= ~WAKE;
+
+ cmd = le16_to_cpu(current->command) & COMMAND_MASK;
+
+ switch (cmd) {
+ case DBDMA_NOP:
+ nop(ch);
+ return;
+
+ case DBDMA_STOP:
+ stop(ch);
+ return;
+ }
+
+ key = le16_to_cpu(current->command) & 0x0700;
+ req_count = le16_to_cpu(current->req_count);
+ phy_addr = le32_to_cpu(current->phy_addr);
+
+ if (key == KEY_STREAM4) {
+ printf("command %x, invalid key 4\n", cmd);
+ kill_channel(ch);
+ return;
+ }
+
+ switch (cmd) {
+ case OUTPUT_MORE:
+ DBDMA_DPRINTFCH(ch, "* OUTPUT_MORE *\n");
+ start_output(ch, key, phy_addr, req_count, 0);
+ return;
+
+ case OUTPUT_LAST:
+ DBDMA_DPRINTFCH(ch, "* OUTPUT_LAST *\n");
+ start_output(ch, key, phy_addr, req_count, 1);
+ return;
+
+ case INPUT_MORE:
+ DBDMA_DPRINTFCH(ch, "* INPUT_MORE *\n");
+ start_input(ch, key, phy_addr, req_count, 0);
+ return;
+
+ case INPUT_LAST:
+ DBDMA_DPRINTFCH(ch, "* INPUT_LAST *\n");
+ start_input(ch, key, phy_addr, req_count, 1);
+ return;
+ }
+
+ if (key < KEY_REGS) {
+ printf("command %x, invalid key %x\n", cmd, key);
+ key = KEY_SYSTEM;
+ }
+
+ /* for LOAD_WORD and STORE_WORD, req_count is on 3 bits
+ * and BRANCH is invalid
+ */
+
+ req_count = req_count & 0x0007;
+ if (req_count & 0x4) {
+ req_count = 4;
+ phy_addr &= ~3;
+ } else if (req_count & 0x2) {
+ req_count = 2;
+ phy_addr &= ~1;
+ } else
+ req_count = 1;
+
+ switch (cmd) {
+ case LOAD_WORD:
+ DBDMA_DPRINTFCH(ch, "* LOAD_WORD *\n");
+ load_word(ch, key, phy_addr, req_count);
+ return;
+
+ case STORE_WORD:
+ DBDMA_DPRINTFCH(ch, "* STORE_WORD *\n");
+ store_word(ch, key, phy_addr, req_count);
+ return;
+ }
+}
+
+static void DBDMA_run(DBDMAState *s)
+{
+ int channel;
+
+ for (channel = 0; channel < DBDMA_CHANNELS; channel++) {
+ DBDMA_channel *ch = &s->channels[channel];
+ uint32_t status = ch->regs[DBDMA_STATUS];
+ if (!ch->io.processing && (status & RUN) && (status & ACTIVE)) {
+ channel_run(ch);
+ }
+ }
+}
+
+static void DBDMA_run_bh(void *opaque)
+{
+ DBDMAState *s = opaque;
+
+ DBDMA_DPRINTF("-> DBDMA_run_bh\n");
+ DBDMA_run(s);
+ DBDMA_DPRINTF("<- DBDMA_run_bh\n");
+}
+
+void DBDMA_kick(DBDMAState *dbdma)
+{
+ qemu_bh_schedule(dbdma->bh);
+}
+
+void DBDMA_register_channel(void *dbdma, int nchan, qemu_irq irq,
+ DBDMA_rw rw, DBDMA_flush flush,
+ void *opaque)
+{
+ DBDMAState *s = dbdma;
+ DBDMA_channel *ch = &s->channels[nchan];
+
+ DBDMA_DPRINTFCH(ch, "DBDMA_register_channel 0x%x\n", nchan);
+
+ assert(rw);
+ assert(flush);
+
+ ch->irq = irq;
+ ch->rw = rw;
+ ch->flush = flush;
+ ch->io.opaque = opaque;
+}
+
+static void dbdma_control_write(DBDMA_channel *ch)
+{
+ uint16_t mask, value;
+ uint32_t status;
+ bool do_flush = false;
+
+ mask = (ch->regs[DBDMA_CONTROL] >> 16) & 0xffff;
+ value = ch->regs[DBDMA_CONTROL] & 0xffff;
+
+ /* This is the status register which we'll update
+ * appropriately and store back
+ */
+ status = ch->regs[DBDMA_STATUS];
+
+ /* RUN and PAUSE are bits under SW control only
+ * FLUSH and WAKE are set by SW and cleared by HW
+ * DEAD, ACTIVE and BT are only under HW control
+ *
+ * We handle ACTIVE separately at the end of the
+ * logic to ensure all cases are covered.
+ */
+
+ /* Setting RUN will tentatively activate the channel
+ */
+ if ((mask & RUN) && (value & RUN)) {
+ status |= RUN;
+ DBDMA_DPRINTFCH(ch, " Setting RUN !\n");
+ }
+
+ /* Clearing RUN 1->0 will stop the channel */
+ if ((mask & RUN) && !(value & RUN)) {
+ /* This has the side effect of clearing the DEAD bit */
+ status &= ~(DEAD | RUN);
+ DBDMA_DPRINTFCH(ch, " Clearing RUN !\n");
+ }
+
+ /* Setting WAKE wakes up an idle channel if it's running
+ *
+ * Note: The doc doesn't say so but assume that only works
+ * on a channel whose RUN bit is set.
+ *
+ * We set WAKE in status, it's not terribly useful as it will
+ * be cleared on the next command fetch but it seems to mimmic
+ * the HW behaviour and is useful for the way we handle
+ * ACTIVE further down.
+ */
+ if ((mask & WAKE) && (value & WAKE) && (status & RUN)) {
+ status |= WAKE;
+ DBDMA_DPRINTFCH(ch, " Setting WAKE !\n");
+ }
+
+ /* PAUSE being set will deactivate (or prevent activation)
+ * of the channel. We just copy it over for now, ACTIVE will
+ * be re-evaluated later.
+ */
+ if (mask & PAUSE) {
+ status = (status & ~PAUSE) | (value & PAUSE);
+ DBDMA_DPRINTFCH(ch, " %sing PAUSE !\n",
+ (value & PAUSE) ? "sett" : "clear");
+ }
+
+ /* FLUSH is its own thing */
+ if ((mask & FLUSH) && (value & FLUSH)) {
+ DBDMA_DPRINTFCH(ch, " Setting FLUSH !\n");
+ /* We set flush directly in the status register, we do *NOT*
+ * set it in "status" so that it gets naturally cleared when
+ * we update the status register further down. That way it
+ * will be set only during the HW flush operation so it is
+ * visible to any completions happening during that time.
+ */
+ ch->regs[DBDMA_STATUS] |= FLUSH;
+ do_flush = true;
+ }
+
+ /* If either RUN or PAUSE is clear, so should ACTIVE be,
+ * otherwise, ACTIVE will be set if we modified RUN, PAUSE or
+ * set WAKE. That means that PAUSE was just cleared, RUN was
+ * just set or WAKE was just set.
+ */
+ if ((status & PAUSE) || !(status & RUN)) {
+ status &= ~ACTIVE;
+ DBDMA_DPRINTFCH(ch, " -> ACTIVE down !\n");
+
+ /* We stopped processing, we want the underlying HW command
+ * to complete *before* we clear the ACTIVE bit. Otherwise
+ * we can get into a situation where the command status will
+ * have RUN or ACTIVE not set which is going to confuse the
+ * MacOS driver.
+ */
+ do_flush = true;
+ } else if (mask & (RUN | PAUSE)) {
+ status |= ACTIVE;
+ DBDMA_DPRINTFCH(ch, " -> ACTIVE up !\n");
+ } else if ((mask & WAKE) && (value & WAKE)) {
+ status |= ACTIVE;
+ DBDMA_DPRINTFCH(ch, " -> ACTIVE up !\n");
+ }
+
+ DBDMA_DPRINTFCH(ch, " new status=0x%08x\n", status);
+
+ /* If we need to flush the underlying HW, do it now, this happens
+ * both on FLUSH commands and when stopping the channel for safety.
+ */
+ if (do_flush && ch->flush) {
+ ch->flush(&ch->io);
+ }
+
+ /* Finally update the status register image */
+ ch->regs[DBDMA_STATUS] = status;
+
+ /* If active, make sure the BH gets to run */
+ if (status & ACTIVE) {
+ DBDMA_kick(dbdma_from_ch(ch));
+ }
+}
+
+static void dbdma_write(void *opaque, hwaddr addr,
+ uint64_t value, unsigned size)
+{
+ int channel = addr >> DBDMA_CHANNEL_SHIFT;
+ DBDMAState *s = opaque;
+ DBDMA_channel *ch = &s->channels[channel];
+ int reg = (addr - (channel << DBDMA_CHANNEL_SHIFT)) >> 2;
+
+ DBDMA_DPRINTFCH(ch, "writel 0x" TARGET_FMT_plx " <= 0x%08"PRIx64"\n",
+ addr, value);
+ DBDMA_DPRINTFCH(ch, "channel 0x%x reg 0x%x\n",
+ (uint32_t)addr >> DBDMA_CHANNEL_SHIFT, reg);
+
+ /* cmdptr cannot be modified if channel is ACTIVE */
+
+ if (reg == DBDMA_CMDPTR_LO && (ch->regs[DBDMA_STATUS] & ACTIVE)) {
+ return;
+ }
+
+ ch->regs[reg] = value;
+
+ switch(reg) {
+ case DBDMA_CONTROL:
+ dbdma_control_write(ch);
+ break;
+ case DBDMA_CMDPTR_LO:
+ /* 16-byte aligned */
+ ch->regs[DBDMA_CMDPTR_LO] &= ~0xf;
+ dbdma_cmdptr_load(ch);
+ break;
+ case DBDMA_STATUS:
+ case DBDMA_INTR_SEL:
+ case DBDMA_BRANCH_SEL:
+ case DBDMA_WAIT_SEL:
+ /* nothing to do */
+ break;
+ case DBDMA_XFER_MODE:
+ case DBDMA_CMDPTR_HI:
+ case DBDMA_DATA2PTR_HI:
+ case DBDMA_DATA2PTR_LO:
+ case DBDMA_ADDRESS_HI:
+ case DBDMA_BRANCH_ADDR_HI:
+ case DBDMA_RES1:
+ case DBDMA_RES2:
+ case DBDMA_RES3:
+ case DBDMA_RES4:
+ /* unused */
+ break;
+ }
+}
+
+static uint64_t dbdma_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ uint32_t value;
+ int channel = addr >> DBDMA_CHANNEL_SHIFT;
+ DBDMAState *s = opaque;
+ DBDMA_channel *ch = &s->channels[channel];
+ int reg = (addr - (channel << DBDMA_CHANNEL_SHIFT)) >> 2;
+
+ value = ch->regs[reg];
+
+ switch(reg) {
+ case DBDMA_CONTROL:
+ value = ch->regs[DBDMA_STATUS];
+ break;
+ case DBDMA_STATUS:
+ case DBDMA_CMDPTR_LO:
+ case DBDMA_INTR_SEL:
+ case DBDMA_BRANCH_SEL:
+ case DBDMA_WAIT_SEL:
+ /* nothing to do */
+ break;
+ case DBDMA_XFER_MODE:
+ case DBDMA_CMDPTR_HI:
+ case DBDMA_DATA2PTR_HI:
+ case DBDMA_DATA2PTR_LO:
+ case DBDMA_ADDRESS_HI:
+ case DBDMA_BRANCH_ADDR_HI:
+ /* unused */
+ value = 0;
+ break;
+ case DBDMA_RES1:
+ case DBDMA_RES2:
+ case DBDMA_RES3:
+ case DBDMA_RES4:
+ /* reserved */
+ break;
+ }
+
+ DBDMA_DPRINTFCH(ch, "readl 0x" TARGET_FMT_plx " => 0x%08x\n", addr, value);
+ DBDMA_DPRINTFCH(ch, "channel 0x%x reg 0x%x\n",
+ (uint32_t)addr >> DBDMA_CHANNEL_SHIFT, reg);
+
+ return value;
+}
+
+static const MemoryRegionOps dbdma_ops = {
+ .read = dbdma_read,
+ .write = dbdma_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+};
+
+static const VMStateDescription vmstate_dbdma_io = {
+ .name = "dbdma_io",
+ .version_id = 0,
+ .minimum_version_id = 0,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT64(addr, struct DBDMA_io),
+ VMSTATE_INT32(len, struct DBDMA_io),
+ VMSTATE_INT32(is_last, struct DBDMA_io),
+ VMSTATE_INT32(is_dma_out, struct DBDMA_io),
+ VMSTATE_BOOL(processing, struct DBDMA_io),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const VMStateDescription vmstate_dbdma_cmd = {
+ .name = "dbdma_cmd",
+ .version_id = 0,
+ .minimum_version_id = 0,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT16(req_count, dbdma_cmd),
+ VMSTATE_UINT16(command, dbdma_cmd),
+ VMSTATE_UINT32(phy_addr, dbdma_cmd),
+ VMSTATE_UINT32(cmd_dep, dbdma_cmd),
+ VMSTATE_UINT16(res_count, dbdma_cmd),
+ VMSTATE_UINT16(xfer_status, dbdma_cmd),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const VMStateDescription vmstate_dbdma_channel = {
+ .name = "dbdma_channel",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32_ARRAY(regs, struct DBDMA_channel, DBDMA_REGS),
+ VMSTATE_STRUCT(io, struct DBDMA_channel, 0, vmstate_dbdma_io, DBDMA_io),
+ VMSTATE_STRUCT(current, struct DBDMA_channel, 0, vmstate_dbdma_cmd,
+ dbdma_cmd),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const VMStateDescription vmstate_dbdma = {
+ .name = "dbdma",
+ .version_id = 3,
+ .minimum_version_id = 3,
+ .fields = (VMStateField[]) {
+ VMSTATE_STRUCT_ARRAY(channels, DBDMAState, DBDMA_CHANNELS, 1,
+ vmstate_dbdma_channel, DBDMA_channel),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void mac_dbdma_reset(DeviceState *d)
+{
+ DBDMAState *s = MAC_DBDMA(d);
+ int i;
+
+ for (i = 0; i < DBDMA_CHANNELS; i++) {
+ memset(s->channels[i].regs, 0, DBDMA_SIZE);
+ }
+}
+
+static void dbdma_unassigned_rw(DBDMA_io *io)
+{
+ DBDMA_channel *ch = io->channel;
+ dbdma_cmd *current = &ch->current;
+ uint16_t cmd;
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: use of unassigned channel %d\n",
+ __func__, ch->channel);
+ ch->io.processing = false;
+
+ cmd = le16_to_cpu(current->command) & COMMAND_MASK;
+ if (cmd == OUTPUT_MORE || cmd == OUTPUT_LAST ||
+ cmd == INPUT_MORE || cmd == INPUT_LAST) {
+ current->xfer_status = cpu_to_le16(ch->regs[DBDMA_STATUS]);
+ current->res_count = cpu_to_le16(io->len);
+ dbdma_cmdptr_save(ch);
+ }
+}
+
+static void dbdma_unassigned_flush(DBDMA_io *io)
+{
+ DBDMA_channel *ch = io->channel;
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: use of unassigned channel %d\n",
+ __func__, ch->channel);
+}
+
+static void mac_dbdma_init(Object *obj)
+{
+ SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
+ DBDMAState *s = MAC_DBDMA(obj);
+ int i;
+
+ for (i = 0; i < DBDMA_CHANNELS; i++) {
+ DBDMA_channel *ch = &s->channels[i];
+
+ ch->rw = dbdma_unassigned_rw;
+ ch->flush = dbdma_unassigned_flush;
+ ch->channel = i;
+ ch->io.channel = ch;
+ }
+
+ memory_region_init_io(&s->mem, obj, &dbdma_ops, s, "dbdma", 0x1000);
+ sysbus_init_mmio(sbd, &s->mem);
+}
+
+static void mac_dbdma_realize(DeviceState *dev, Error **errp)
+{
+ DBDMAState *s = MAC_DBDMA(dev);
+
+ s->bh = qemu_bh_new(DBDMA_run_bh, s);
+}
+
+static void mac_dbdma_class_init(ObjectClass *oc, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(oc);
+
+ dc->realize = mac_dbdma_realize;
+ dc->reset = mac_dbdma_reset;
+ dc->vmsd = &vmstate_dbdma;
+}
+
+static const TypeInfo mac_dbdma_type_info = {
+ .name = TYPE_MAC_DBDMA,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(DBDMAState),
+ .instance_init = mac_dbdma_init,
+ .class_init = mac_dbdma_class_init
+};
+
+static void mac_dbdma_register_types(void)
+{
+ type_register_static(&mac_dbdma_type_info);
+}
+
+type_init(mac_dbdma_register_types)
diff --git a/hw/misc/macio/macio.c b/hw/misc/macio/macio.c
new file mode 100644
index 000000000..c1fad43f6
--- /dev/null
+++ b/hw/misc/macio/macio.c
@@ -0,0 +1,504 @@
+/*
+ * PowerMac MacIO device emulation
+ *
+ * Copyright (c) 2005-2007 Fabrice Bellard
+ * Copyright (c) 2007 Jocelyn Mayer
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "qemu/osdep.h"
+#include "qapi/error.h"
+#include "qemu/module.h"
+#include "hw/ppc/mac.h"
+#include "hw/misc/macio/cuda.h"
+#include "hw/pci/pci.h"
+#include "hw/ppc/mac_dbdma.h"
+#include "hw/qdev-properties.h"
+#include "migration/vmstate.h"
+#include "hw/char/escc.h"
+#include "hw/misc/macio/macio.h"
+#include "hw/intc/heathrow_pic.h"
+#include "trace.h"
+
+/* Note: this code is strongly inspirated from the corresponding code
+ * in PearPC */
+
+/*
+ * The mac-io has two interfaces to the ESCC. One is called "escc-legacy",
+ * while the other one is the normal, current ESCC interface.
+ *
+ * The magic below creates memory aliases to spawn the escc-legacy device
+ * purely by rerouting the respective registers to our escc region. This
+ * works because the only difference between the two memory regions is the
+ * register layout, not their semantics.
+ *
+ * Reference: ftp://ftp.software.ibm.com/rs6000/technology/spec/chrp/inwork/CHRP_IORef_1.0.pdf
+ */
+static void macio_escc_legacy_setup(MacIOState *s)
+{
+ ESCCState *escc = ESCC(&s->escc);
+ SysBusDevice *sbd = SYS_BUS_DEVICE(escc);
+ MemoryRegion *escc_legacy = g_new(MemoryRegion, 1);
+ MemoryRegion *bar = &s->bar;
+ int i;
+ static const int maps[] = {
+ 0x00, 0x00, /* Command B */
+ 0x02, 0x20, /* Command A */
+ 0x04, 0x10, /* Data B */
+ 0x06, 0x30, /* Data A */
+ 0x08, 0x40, /* Enhancement B */
+ 0x0A, 0x50, /* Enhancement A */
+ 0x80, 0x80, /* Recovery count */
+ 0x90, 0x90, /* Start A */
+ 0xa0, 0xa0, /* Start B */
+ 0xb0, 0xb0, /* Detect AB */
+ };
+
+ memory_region_init(escc_legacy, OBJECT(s), "escc-legacy", 256);
+ for (i = 0; i < ARRAY_SIZE(maps); i += 2) {
+ MemoryRegion *port = g_new(MemoryRegion, 1);
+ memory_region_init_alias(port, OBJECT(s), "escc-legacy-port",
+ sysbus_mmio_get_region(sbd, 0),
+ maps[i + 1], 0x2);
+ memory_region_add_subregion(escc_legacy, maps[i], port);
+ }
+
+ memory_region_add_subregion(bar, 0x12000, escc_legacy);
+}
+
+static void macio_bar_setup(MacIOState *s)
+{
+ ESCCState *escc = ESCC(&s->escc);
+ SysBusDevice *sbd = SYS_BUS_DEVICE(escc);
+ MemoryRegion *bar = &s->bar;
+
+ memory_region_add_subregion(bar, 0x13000, sysbus_mmio_get_region(sbd, 0));
+ macio_escc_legacy_setup(s);
+}
+
+static void macio_common_realize(PCIDevice *d, Error **errp)
+{
+ MacIOState *s = MACIO(d);
+ SysBusDevice *sysbus_dev;
+
+ if (!qdev_realize(DEVICE(&s->dbdma), BUS(&s->macio_bus), errp)) {
+ return;
+ }
+ sysbus_dev = SYS_BUS_DEVICE(&s->dbdma);
+ memory_region_add_subregion(&s->bar, 0x08000,
+ sysbus_mmio_get_region(sysbus_dev, 0));
+
+ qdev_prop_set_uint32(DEVICE(&s->escc), "disabled", 0);
+ qdev_prop_set_uint32(DEVICE(&s->escc), "frequency", ESCC_CLOCK);
+ qdev_prop_set_uint32(DEVICE(&s->escc), "it_shift", 4);
+ qdev_prop_set_uint32(DEVICE(&s->escc), "chnBtype", escc_serial);
+ qdev_prop_set_uint32(DEVICE(&s->escc), "chnAtype", escc_serial);
+ if (!qdev_realize(DEVICE(&s->escc), BUS(&s->macio_bus), errp)) {
+ return;
+ }
+
+ macio_bar_setup(s);
+ pci_register_bar(d, 0, PCI_BASE_ADDRESS_SPACE_MEMORY, &s->bar);
+}
+
+static void macio_realize_ide(MacIOState *s, MACIOIDEState *ide,
+ qemu_irq irq0, qemu_irq irq1, int dmaid,
+ Error **errp)
+{
+ SysBusDevice *sysbus_dev;
+
+ sysbus_dev = SYS_BUS_DEVICE(ide);
+ sysbus_connect_irq(sysbus_dev, 0, irq0);
+ sysbus_connect_irq(sysbus_dev, 1, irq1);
+ qdev_prop_set_uint32(DEVICE(ide), "channel", dmaid);
+ object_property_set_link(OBJECT(ide), "dbdma", OBJECT(&s->dbdma),
+ &error_abort);
+ macio_ide_register_dma(ide);
+
+ qdev_realize(DEVICE(ide), BUS(&s->macio_bus), errp);
+}
+
+static void macio_oldworld_realize(PCIDevice *d, Error **errp)
+{
+ MacIOState *s = MACIO(d);
+ OldWorldMacIOState *os = OLDWORLD_MACIO(d);
+ DeviceState *pic_dev = DEVICE(&os->pic);
+ Error *err = NULL;
+ SysBusDevice *sysbus_dev;
+
+ macio_common_realize(d, &err);
+ if (err) {
+ error_propagate(errp, err);
+ return;
+ }
+
+ /* Heathrow PIC */
+ if (!qdev_realize(DEVICE(&os->pic), BUS(&s->macio_bus), errp)) {
+ return;
+ }
+ sysbus_dev = SYS_BUS_DEVICE(&os->pic);
+ memory_region_add_subregion(&s->bar, 0x0,
+ sysbus_mmio_get_region(sysbus_dev, 0));
+
+ qdev_prop_set_uint64(DEVICE(&s->cuda), "timebase-frequency",
+ s->frequency);
+ if (!qdev_realize(DEVICE(&s->cuda), BUS(&s->macio_bus), errp)) {
+ return;
+ }
+ sysbus_dev = SYS_BUS_DEVICE(&s->cuda);
+ memory_region_add_subregion(&s->bar, 0x16000,
+ sysbus_mmio_get_region(sysbus_dev, 0));
+ sysbus_connect_irq(sysbus_dev, 0, qdev_get_gpio_in(pic_dev,
+ OLDWORLD_CUDA_IRQ));
+
+ sysbus_dev = SYS_BUS_DEVICE(&s->escc);
+ sysbus_connect_irq(sysbus_dev, 0, qdev_get_gpio_in(pic_dev,
+ OLDWORLD_ESCCB_IRQ));
+ sysbus_connect_irq(sysbus_dev, 1, qdev_get_gpio_in(pic_dev,
+ OLDWORLD_ESCCA_IRQ));
+
+ if (!qdev_realize(DEVICE(&os->nvram), BUS(&s->macio_bus), errp)) {
+ return;
+ }
+ sysbus_dev = SYS_BUS_DEVICE(&os->nvram);
+ memory_region_add_subregion(&s->bar, 0x60000,
+ sysbus_mmio_get_region(sysbus_dev, 0));
+ pmac_format_nvram_partition(&os->nvram, os->nvram.size);
+
+ /* IDE buses */
+ macio_realize_ide(s, &os->ide[0],
+ qdev_get_gpio_in(pic_dev, OLDWORLD_IDE0_IRQ),
+ qdev_get_gpio_in(pic_dev, OLDWORLD_IDE0_DMA_IRQ),
+ 0x16, &err);
+ if (err) {
+ error_propagate(errp, err);
+ return;
+ }
+
+ macio_realize_ide(s, &os->ide[1],
+ qdev_get_gpio_in(pic_dev, OLDWORLD_IDE1_IRQ),
+ qdev_get_gpio_in(pic_dev, OLDWORLD_IDE1_DMA_IRQ),
+ 0x1a, &err);
+ if (err) {
+ error_propagate(errp, err);
+ return;
+ }
+}
+
+static void macio_init_ide(MacIOState *s, MACIOIDEState *ide, int index)
+{
+ gchar *name = g_strdup_printf("ide[%i]", index);
+ uint32_t addr = 0x1f000 + ((index + 1) * 0x1000);
+
+ object_initialize_child(OBJECT(s), name, ide, TYPE_MACIO_IDE);
+ qdev_prop_set_uint32(DEVICE(ide), "addr", addr);
+ memory_region_add_subregion(&s->bar, addr, &ide->mem);
+ g_free(name);
+}
+
+static void macio_oldworld_init(Object *obj)
+{
+ MacIOState *s = MACIO(obj);
+ OldWorldMacIOState *os = OLDWORLD_MACIO(obj);
+ DeviceState *dev;
+ int i;
+
+ object_initialize_child(OBJECT(s), "pic", &os->pic, TYPE_HEATHROW);
+
+ object_initialize_child(OBJECT(s), "cuda", &s->cuda, TYPE_CUDA);
+
+ object_initialize_child(OBJECT(s), "nvram", &os->nvram, TYPE_MACIO_NVRAM);
+ dev = DEVICE(&os->nvram);
+ qdev_prop_set_uint32(dev, "size", 0x2000);
+ qdev_prop_set_uint32(dev, "it_shift", 4);
+
+ for (i = 0; i < 2; i++) {
+ macio_init_ide(s, &os->ide[i], i);
+ }
+}
+
+static void timer_write(void *opaque, hwaddr addr, uint64_t value,
+ unsigned size)
+{
+ trace_macio_timer_write(addr, size, value);
+}
+
+static uint64_t timer_read(void *opaque, hwaddr addr, unsigned size)
+{
+ uint32_t value = 0;
+ uint64_t systime = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
+ uint64_t kltime;
+
+ kltime = muldiv64(systime, 4194300, NANOSECONDS_PER_SECOND * 4);
+ kltime = muldiv64(kltime, 18432000, 1048575);
+
+ switch (addr) {
+ case 0x38:
+ value = kltime;
+ break;
+ case 0x3c:
+ value = kltime >> 32;
+ break;
+ }
+
+ trace_macio_timer_read(addr, size, value);
+ return value;
+}
+
+static const MemoryRegionOps timer_ops = {
+ .read = timer_read,
+ .write = timer_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+static void macio_newworld_realize(PCIDevice *d, Error **errp)
+{
+ MacIOState *s = MACIO(d);
+ NewWorldMacIOState *ns = NEWWORLD_MACIO(d);
+ DeviceState *pic_dev = DEVICE(&ns->pic);
+ Error *err = NULL;
+ SysBusDevice *sysbus_dev;
+ MemoryRegion *timer_memory = NULL;
+
+ macio_common_realize(d, &err);
+ if (err) {
+ error_propagate(errp, err);
+ return;
+ }
+
+ /* OpenPIC */
+ qdev_prop_set_uint32(pic_dev, "model", OPENPIC_MODEL_KEYLARGO);
+ sysbus_dev = SYS_BUS_DEVICE(&ns->pic);
+ sysbus_realize_and_unref(sysbus_dev, &error_fatal);
+ memory_region_add_subregion(&s->bar, 0x40000,
+ sysbus_mmio_get_region(sysbus_dev, 0));
+
+ sysbus_dev = SYS_BUS_DEVICE(&s->escc);
+ sysbus_connect_irq(sysbus_dev, 0, qdev_get_gpio_in(pic_dev,
+ NEWWORLD_ESCCB_IRQ));
+ sysbus_connect_irq(sysbus_dev, 1, qdev_get_gpio_in(pic_dev,
+ NEWWORLD_ESCCA_IRQ));
+
+ /* IDE buses */
+ macio_realize_ide(s, &ns->ide[0],
+ qdev_get_gpio_in(pic_dev, NEWWORLD_IDE0_IRQ),
+ qdev_get_gpio_in(pic_dev, NEWWORLD_IDE0_DMA_IRQ),
+ 0x16, &err);
+ if (err) {
+ error_propagate(errp, err);
+ return;
+ }
+
+ macio_realize_ide(s, &ns->ide[1],
+ qdev_get_gpio_in(pic_dev, NEWWORLD_IDE1_IRQ),
+ qdev_get_gpio_in(pic_dev, NEWWORLD_IDE1_DMA_IRQ),
+ 0x1a, &err);
+ if (err) {
+ error_propagate(errp, err);
+ return;
+ }
+
+ /* Timer */
+ timer_memory = g_new(MemoryRegion, 1);
+ memory_region_init_io(timer_memory, OBJECT(s), &timer_ops, NULL, "timer",
+ 0x1000);
+ memory_region_add_subregion(&s->bar, 0x15000, timer_memory);
+
+ if (ns->has_pmu) {
+ /* GPIOs */
+ if (!qdev_realize(DEVICE(&ns->gpio), BUS(&s->macio_bus), errp)) {
+ return;
+ }
+ sysbus_dev = SYS_BUS_DEVICE(&ns->gpio);
+ sysbus_connect_irq(sysbus_dev, 1, qdev_get_gpio_in(pic_dev,
+ NEWWORLD_EXTING_GPIO1));
+ sysbus_connect_irq(sysbus_dev, 9, qdev_get_gpio_in(pic_dev,
+ NEWWORLD_EXTING_GPIO9));
+ memory_region_add_subregion(&s->bar, 0x50,
+ sysbus_mmio_get_region(sysbus_dev, 0));
+
+ /* PMU */
+ object_initialize_child(OBJECT(s), "pmu", &s->pmu, TYPE_VIA_PMU);
+ object_property_set_link(OBJECT(&s->pmu), "gpio", OBJECT(sysbus_dev),
+ &error_abort);
+ qdev_prop_set_bit(DEVICE(&s->pmu), "has-adb", ns->has_adb);
+ if (!qdev_realize(DEVICE(&s->pmu), BUS(&s->macio_bus), errp)) {
+ return;
+ }
+ sysbus_dev = SYS_BUS_DEVICE(&s->pmu);
+ sysbus_connect_irq(sysbus_dev, 0, qdev_get_gpio_in(pic_dev,
+ NEWWORLD_PMU_IRQ));
+ memory_region_add_subregion(&s->bar, 0x16000,
+ sysbus_mmio_get_region(sysbus_dev, 0));
+ } else {
+ object_unparent(OBJECT(&ns->gpio));
+
+ /* CUDA */
+ object_initialize_child(OBJECT(s), "cuda", &s->cuda, TYPE_CUDA);
+ qdev_prop_set_uint64(DEVICE(&s->cuda), "timebase-frequency",
+ s->frequency);
+
+ if (!qdev_realize(DEVICE(&s->cuda), BUS(&s->macio_bus), errp)) {
+ return;
+ }
+ sysbus_dev = SYS_BUS_DEVICE(&s->cuda);
+ sysbus_connect_irq(sysbus_dev, 0, qdev_get_gpio_in(pic_dev,
+ NEWWORLD_CUDA_IRQ));
+ memory_region_add_subregion(&s->bar, 0x16000,
+ sysbus_mmio_get_region(sysbus_dev, 0));
+ }
+}
+
+static void macio_newworld_init(Object *obj)
+{
+ MacIOState *s = MACIO(obj);
+ NewWorldMacIOState *ns = NEWWORLD_MACIO(obj);
+ int i;
+
+ object_initialize_child(OBJECT(s), "pic", &ns->pic, TYPE_OPENPIC);
+
+ object_initialize_child(OBJECT(s), "gpio", &ns->gpio, TYPE_MACIO_GPIO);
+
+ for (i = 0; i < 2; i++) {
+ macio_init_ide(s, &ns->ide[i], i);
+ }
+}
+
+static void macio_instance_init(Object *obj)
+{
+ MacIOState *s = MACIO(obj);
+
+ memory_region_init(&s->bar, obj, "macio", 0x80000);
+
+ qbus_init(&s->macio_bus, sizeof(s->macio_bus), TYPE_MACIO_BUS,
+ DEVICE(obj), "macio.0");
+
+ object_initialize_child(OBJECT(s), "dbdma", &s->dbdma, TYPE_MAC_DBDMA);
+
+ object_initialize_child(OBJECT(s), "escc", &s->escc, TYPE_ESCC);
+}
+
+static const VMStateDescription vmstate_macio_oldworld = {
+ .name = "macio-oldworld",
+ .version_id = 0,
+ .minimum_version_id = 0,
+ .fields = (VMStateField[]) {
+ VMSTATE_PCI_DEVICE(parent_obj.parent, OldWorldMacIOState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void macio_oldworld_class_init(ObjectClass *oc, void *data)
+{
+ PCIDeviceClass *pdc = PCI_DEVICE_CLASS(oc);
+ DeviceClass *dc = DEVICE_CLASS(oc);
+
+ pdc->realize = macio_oldworld_realize;
+ pdc->device_id = PCI_DEVICE_ID_APPLE_343S1201;
+ dc->vmsd = &vmstate_macio_oldworld;
+}
+
+static const VMStateDescription vmstate_macio_newworld = {
+ .name = "macio-newworld",
+ .version_id = 0,
+ .minimum_version_id = 0,
+ .fields = (VMStateField[]) {
+ VMSTATE_PCI_DEVICE(parent_obj.parent, NewWorldMacIOState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static Property macio_newworld_properties[] = {
+ DEFINE_PROP_BOOL("has-pmu", NewWorldMacIOState, has_pmu, false),
+ DEFINE_PROP_BOOL("has-adb", NewWorldMacIOState, has_adb, false),
+ DEFINE_PROP_END_OF_LIST()
+};
+
+static void macio_newworld_class_init(ObjectClass *oc, void *data)
+{
+ PCIDeviceClass *pdc = PCI_DEVICE_CLASS(oc);
+ DeviceClass *dc = DEVICE_CLASS(oc);
+
+ pdc->realize = macio_newworld_realize;
+ pdc->device_id = PCI_DEVICE_ID_APPLE_UNI_N_KEYL;
+ dc->vmsd = &vmstate_macio_newworld;
+ device_class_set_props(dc, macio_newworld_properties);
+}
+
+static Property macio_properties[] = {
+ DEFINE_PROP_UINT64("frequency", MacIOState, frequency, 0),
+ DEFINE_PROP_END_OF_LIST()
+};
+
+static void macio_class_init(ObjectClass *klass, void *data)
+{
+ PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ k->vendor_id = PCI_VENDOR_ID_APPLE;
+ k->class_id = PCI_CLASS_OTHERS << 8;
+ device_class_set_props(dc, macio_properties);
+ set_bit(DEVICE_CATEGORY_BRIDGE, dc->categories);
+}
+
+static const TypeInfo macio_bus_info = {
+ .name = TYPE_MACIO_BUS,
+ .parent = TYPE_SYSTEM_BUS,
+ .instance_size = sizeof(MacIOBusState),
+};
+
+static const TypeInfo macio_oldworld_type_info = {
+ .name = TYPE_OLDWORLD_MACIO,
+ .parent = TYPE_MACIO,
+ .instance_size = sizeof(OldWorldMacIOState),
+ .instance_init = macio_oldworld_init,
+ .class_init = macio_oldworld_class_init,
+};
+
+static const TypeInfo macio_newworld_type_info = {
+ .name = TYPE_NEWWORLD_MACIO,
+ .parent = TYPE_MACIO,
+ .instance_size = sizeof(NewWorldMacIOState),
+ .instance_init = macio_newworld_init,
+ .class_init = macio_newworld_class_init,
+};
+
+static const TypeInfo macio_type_info = {
+ .name = TYPE_MACIO,
+ .parent = TYPE_PCI_DEVICE,
+ .instance_size = sizeof(MacIOState),
+ .instance_init = macio_instance_init,
+ .abstract = true,
+ .class_init = macio_class_init,
+ .interfaces = (InterfaceInfo[]) {
+ { INTERFACE_CONVENTIONAL_PCI_DEVICE },
+ { },
+ },
+};
+
+static void macio_register_types(void)
+{
+ type_register_static(&macio_bus_info);
+ type_register_static(&macio_type_info);
+ type_register_static(&macio_oldworld_type_info);
+ type_register_static(&macio_newworld_type_info);
+}
+
+type_init(macio_register_types)
diff --git a/hw/misc/macio/meson.build b/hw/misc/macio/meson.build
new file mode 100644
index 000000000..17282da20
--- /dev/null
+++ b/hw/misc/macio/meson.build
@@ -0,0 +1,8 @@
+macio_ss = ss.source_set()
+macio_ss.add(files('macio.c'))
+macio_ss.add(when: 'CONFIG_CUDA', if_true: files('cuda.c'))
+macio_ss.add(when: 'CONFIG_MACIO_GPIO', if_true: files('gpio.c'))
+macio_ss.add(when: 'CONFIG_MAC_DBDMA', if_true: files('mac_dbdma.c'))
+macio_ss.add(when: 'CONFIG_MAC_PMU', if_true: files('pmu.c'))
+
+softmmu_ss.add_all(when: 'CONFIG_MACIO', if_true: macio_ss)
diff --git a/hw/misc/macio/pmu.c b/hw/misc/macio/pmu.c
new file mode 100644
index 000000000..eb39c6469
--- /dev/null
+++ b/hw/misc/macio/pmu.c
@@ -0,0 +1,871 @@
+/*
+ * QEMU PowerMac PMU device support
+ *
+ * Copyright (c) 2016 Benjamin Herrenschmidt, IBM Corp.
+ * Copyright (c) 2018 Mark Cave-Ayland
+ *
+ * Based on the CUDA device by:
+ *
+ * Copyright (c) 2004-2007 Fabrice Bellard
+ * Copyright (c) 2007 Jocelyn Mayer
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu-common.h"
+#include "hw/ppc/mac.h"
+#include "hw/qdev-properties.h"
+#include "migration/vmstate.h"
+#include "hw/input/adb.h"
+#include "hw/irq.h"
+#include "hw/misc/mos6522.h"
+#include "hw/misc/macio/gpio.h"
+#include "hw/misc/macio/pmu.h"
+#include "qapi/error.h"
+#include "qemu/timer.h"
+#include "sysemu/runstate.h"
+#include "qapi/error.h"
+#include "qemu/cutils.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+#include "trace.h"
+
+
+/* Bits in B data register: all active low */
+#define TACK 0x08 /* Transfer request (input) */
+#define TREQ 0x10 /* Transfer acknowledge (output) */
+
+/* PMU returns time_t's offset from Jan 1, 1904, not 1970 */
+#define RTC_OFFSET 2082844800
+
+#define VIA_TIMER_FREQ (4700000 / 6)
+
+static void via_update_irq(PMUState *s)
+{
+ MOS6522PMUState *mps = MOS6522_PMU(&s->mos6522_pmu);
+ MOS6522State *ms = MOS6522(mps);
+
+ bool new_state = !!(ms->ifr & ms->ier & (SR_INT | T1_INT | T2_INT));
+
+ if (new_state != s->via_irq_state) {
+ s->via_irq_state = new_state;
+ qemu_set_irq(s->via_irq, new_state);
+ }
+}
+
+static void via_set_sr_int(void *opaque)
+{
+ PMUState *s = opaque;
+ MOS6522PMUState *mps = MOS6522_PMU(&s->mos6522_pmu);
+ MOS6522State *ms = MOS6522(mps);
+ MOS6522DeviceClass *mdc = MOS6522_GET_CLASS(ms);
+
+ mdc->set_sr_int(ms);
+}
+
+static void pmu_update_extirq(PMUState *s)
+{
+ if ((s->intbits & s->intmask) != 0) {
+ macio_set_gpio(s->gpio, 1, false);
+ } else {
+ macio_set_gpio(s->gpio, 1, true);
+ }
+}
+
+static void pmu_adb_poll(void *opaque)
+{
+ PMUState *s = opaque;
+ ADBBusState *adb_bus = &s->adb_bus;
+ int olen;
+
+ if (!(s->intbits & PMU_INT_ADB)) {
+ olen = adb_poll(adb_bus, s->adb_reply, adb_bus->autopoll_mask);
+ trace_pmu_adb_poll(olen);
+
+ if (olen > 0) {
+ s->adb_reply_size = olen;
+ s->intbits |= PMU_INT_ADB | PMU_INT_ADB_AUTO;
+ pmu_update_extirq(s);
+ }
+ }
+}
+
+static void pmu_one_sec_timer(void *opaque)
+{
+ PMUState *s = opaque;
+
+ trace_pmu_one_sec_timer();
+
+ s->intbits |= PMU_INT_TICK;
+ pmu_update_extirq(s);
+ s->one_sec_target += 1000;
+
+ timer_mod(s->one_sec_timer, s->one_sec_target);
+}
+
+static void pmu_cmd_int_ack(PMUState *s,
+ const uint8_t *in_data, uint8_t in_len,
+ uint8_t *out_data, uint8_t *out_len)
+{
+ if (in_len != 0) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "PMU: INT_ACK command, invalid len: %d want: 0\n",
+ in_len);
+ return;
+ }
+
+ /* Make appropriate reply packet */
+ if (s->intbits & PMU_INT_ADB) {
+ if (!s->adb_reply_size) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "Odd, PMU_INT_ADB set with no reply in buffer\n");
+ }
+
+ memcpy(out_data + 1, s->adb_reply, s->adb_reply_size);
+ out_data[0] = s->intbits & (PMU_INT_ADB | PMU_INT_ADB_AUTO);
+ *out_len = s->adb_reply_size + 1;
+ s->intbits &= ~(PMU_INT_ADB | PMU_INT_ADB_AUTO);
+ s->adb_reply_size = 0;
+ } else {
+ out_data[0] = s->intbits;
+ s->intbits = 0;
+ *out_len = 1;
+ }
+
+ pmu_update_extirq(s);
+}
+
+static void pmu_cmd_set_int_mask(PMUState *s,
+ const uint8_t *in_data, uint8_t in_len,
+ uint8_t *out_data, uint8_t *out_len)
+{
+ if (in_len != 1) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "PMU: SET_INT_MASK command, invalid len: %d want: 1\n",
+ in_len);
+ return;
+ }
+
+ trace_pmu_cmd_set_int_mask(s->intmask);
+ s->intmask = in_data[0];
+
+ pmu_update_extirq(s);
+}
+
+static void pmu_cmd_set_adb_autopoll(PMUState *s, uint16_t mask)
+{
+ ADBBusState *adb_bus = &s->adb_bus;
+
+ trace_pmu_cmd_set_adb_autopoll(mask);
+
+ if (mask) {
+ adb_set_autopoll_mask(adb_bus, mask);
+ adb_set_autopoll_enabled(adb_bus, true);
+ } else {
+ adb_set_autopoll_enabled(adb_bus, false);
+ }
+}
+
+static void pmu_cmd_adb(PMUState *s,
+ const uint8_t *in_data, uint8_t in_len,
+ uint8_t *out_data, uint8_t *out_len)
+{
+ int len, adblen;
+ uint8_t adb_cmd[255];
+
+ if (in_len < 2) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "PMU: ADB PACKET, invalid len: %d want at least 2\n",
+ in_len);
+ return;
+ }
+
+ *out_len = 0;
+
+ if (!s->has_adb) {
+ trace_pmu_cmd_adb_nobus();
+ return;
+ }
+
+ /* Set autopoll is a special form of the command */
+ if (in_data[0] == 0 && in_data[1] == 0x86) {
+ uint16_t mask = in_data[2];
+ mask = (mask << 8) | in_data[3];
+ if (in_len != 4) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "PMU: ADB Autopoll requires 4 bytes, got %d\n",
+ in_len);
+ return;
+ }
+
+ pmu_cmd_set_adb_autopoll(s, mask);
+ return;
+ }
+
+ trace_pmu_cmd_adb_request(in_len, in_data[0], in_data[1], in_data[2],
+ in_data[3], in_data[4]);
+
+ *out_len = 0;
+
+ /* Check ADB len */
+ adblen = in_data[2];
+ if (adblen > (in_len - 3)) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "PMU: ADB len is %d > %d (in_len -3)...erroring\n",
+ adblen, in_len - 3);
+ len = -1;
+ } else if (adblen > 252) {
+ qemu_log_mask(LOG_GUEST_ERROR, "PMU: ADB command too big!\n");
+ len = -1;
+ } else {
+ /* Format command */
+ adb_cmd[0] = in_data[0];
+ memcpy(&adb_cmd[1], &in_data[3], in_len - 3);
+ len = adb_request(&s->adb_bus, s->adb_reply + 2, adb_cmd, in_len - 2);
+
+ trace_pmu_cmd_adb_reply(len);
+ }
+
+ if (len > 0) {
+ /* XXX Check this */
+ s->adb_reply_size = len + 2;
+ s->adb_reply[0] = 0x01;
+ s->adb_reply[1] = len;
+ } else {
+ /* XXX Check this */
+ s->adb_reply_size = 1;
+ s->adb_reply[0] = 0x00;
+ }
+
+ s->intbits |= PMU_INT_ADB;
+ pmu_update_extirq(s);
+}
+
+static void pmu_cmd_adb_poll_off(PMUState *s,
+ const uint8_t *in_data, uint8_t in_len,
+ uint8_t *out_data, uint8_t *out_len)
+{
+ ADBBusState *adb_bus = &s->adb_bus;
+
+ if (in_len != 0) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "PMU: ADB POLL OFF command, invalid len: %d want: 0\n",
+ in_len);
+ return;
+ }
+
+ if (s->has_adb) {
+ adb_set_autopoll_enabled(adb_bus, false);
+ }
+}
+
+static void pmu_cmd_shutdown(PMUState *s,
+ const uint8_t *in_data, uint8_t in_len,
+ uint8_t *out_data, uint8_t *out_len)
+{
+ if (in_len != 4) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "PMU: SHUTDOWN command, invalid len: %d want: 4\n",
+ in_len);
+ return;
+ }
+
+ *out_len = 1;
+ out_data[0] = 0;
+
+ if (in_data[0] != 'M' || in_data[1] != 'A' || in_data[2] != 'T' ||
+ in_data[3] != 'T') {
+
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "PMU: SHUTDOWN command, Bad MATT signature\n");
+ return;
+ }
+
+ qemu_system_shutdown_request(SHUTDOWN_CAUSE_GUEST_SHUTDOWN);
+}
+
+static void pmu_cmd_reset(PMUState *s,
+ const uint8_t *in_data, uint8_t in_len,
+ uint8_t *out_data, uint8_t *out_len)
+{
+ if (in_len != 0) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "PMU: RESET command, invalid len: %d want: 0\n",
+ in_len);
+ return;
+ }
+
+ qemu_system_reset_request(SHUTDOWN_CAUSE_GUEST_RESET);
+}
+
+static void pmu_cmd_get_rtc(PMUState *s,
+ const uint8_t *in_data, uint8_t in_len,
+ uint8_t *out_data, uint8_t *out_len)
+{
+ uint32_t ti;
+
+ if (in_len != 0) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "PMU: GET_RTC command, invalid len: %d want: 0\n",
+ in_len);
+ return;
+ }
+
+ ti = s->tick_offset + (qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL)
+ / NANOSECONDS_PER_SECOND);
+ out_data[0] = ti >> 24;
+ out_data[1] = ti >> 16;
+ out_data[2] = ti >> 8;
+ out_data[3] = ti;
+ *out_len = 4;
+}
+
+static void pmu_cmd_set_rtc(PMUState *s,
+ const uint8_t *in_data, uint8_t in_len,
+ uint8_t *out_data, uint8_t *out_len)
+{
+ uint32_t ti;
+
+ if (in_len != 4) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "PMU: SET_RTC command, invalid len: %d want: 4\n",
+ in_len);
+ return;
+ }
+
+ ti = (((uint32_t)in_data[0]) << 24) + (((uint32_t)in_data[1]) << 16)
+ + (((uint32_t)in_data[2]) << 8) + in_data[3];
+
+ s->tick_offset = ti - (qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL)
+ / NANOSECONDS_PER_SECOND);
+}
+
+static void pmu_cmd_system_ready(PMUState *s,
+ const uint8_t *in_data, uint8_t in_len,
+ uint8_t *out_data, uint8_t *out_len)
+{
+ /* Do nothing */
+}
+
+static void pmu_cmd_get_version(PMUState *s,
+ const uint8_t *in_data, uint8_t in_len,
+ uint8_t *out_data, uint8_t *out_len)
+{
+ *out_len = 1;
+ *out_data = 1; /* ??? Check what Apple does */
+}
+
+static void pmu_cmd_power_events(PMUState *s,
+ const uint8_t *in_data, uint8_t in_len,
+ uint8_t *out_data, uint8_t *out_len)
+{
+ if (in_len < 1) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "PMU: POWER EVENTS command, invalid len %d, want at least 1\n",
+ in_len);
+ return;
+ }
+
+ switch (in_data[0]) {
+ /* Dummies for now */
+ case PMU_PWR_GET_POWERUP_EVENTS:
+ *out_len = 2;
+ out_data[0] = 0;
+ out_data[1] = 0;
+ break;
+ case PMU_PWR_SET_POWERUP_EVENTS:
+ case PMU_PWR_CLR_POWERUP_EVENTS:
+ break;
+ case PMU_PWR_GET_WAKEUP_EVENTS:
+ *out_len = 2;
+ out_data[0] = 0;
+ out_data[1] = 0;
+ break;
+ case PMU_PWR_SET_WAKEUP_EVENTS:
+ case PMU_PWR_CLR_WAKEUP_EVENTS:
+ break;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "PMU: POWER EVENTS unknown subcommand 0x%02x\n",
+ in_data[0]);
+ }
+}
+
+static void pmu_cmd_get_cover(PMUState *s,
+ const uint8_t *in_data, uint8_t in_len,
+ uint8_t *out_data, uint8_t *out_len)
+{
+ /* Not 100% sure here, will have to check what a real Mac
+ * returns other than byte 0 bit 0 is LID closed on laptops
+ */
+ *out_len = 1;
+ *out_data = 0x00;
+}
+
+static void pmu_cmd_download_status(PMUState *s,
+ const uint8_t *in_data, uint8_t in_len,
+ uint8_t *out_data, uint8_t *out_len)
+{
+ /* This has to do with PMU firmware updates as far as I can tell.
+ *
+ * We return 0x62 which is what OpenPMU expects
+ */
+ *out_len = 1;
+ *out_data = 0x62;
+}
+
+static void pmu_cmd_read_pmu_ram(PMUState *s,
+ const uint8_t *in_data, uint8_t in_len,
+ uint8_t *out_data, uint8_t *out_len)
+{
+ if (in_len < 3) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "PMU: READ_PMU_RAM command, invalid len %d, expected 3\n",
+ in_len);
+ return;
+ }
+
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "PMU: Unsupported READ_PMU_RAM, args: %02x %02x %02x\n",
+ in_data[0], in_data[1], in_data[2]);
+
+ *out_len = 0;
+}
+
+/* description of commands */
+typedef struct PMUCmdHandler {
+ uint8_t command;
+ const char *name;
+ void (*handler)(PMUState *s,
+ const uint8_t *in_args, uint8_t in_len,
+ uint8_t *out_args, uint8_t *out_len);
+} PMUCmdHandler;
+
+static const PMUCmdHandler PMUCmdHandlers[] = {
+ { PMU_INT_ACK, "INT ACK", pmu_cmd_int_ack },
+ { PMU_SET_INTR_MASK, "SET INT MASK", pmu_cmd_set_int_mask },
+ { PMU_ADB_CMD, "ADB COMMAND", pmu_cmd_adb },
+ { PMU_ADB_POLL_OFF, "ADB POLL OFF", pmu_cmd_adb_poll_off },
+ { PMU_RESET, "REBOOT", pmu_cmd_reset },
+ { PMU_SHUTDOWN, "SHUTDOWN", pmu_cmd_shutdown },
+ { PMU_READ_RTC, "GET RTC", pmu_cmd_get_rtc },
+ { PMU_SET_RTC, "SET RTC", pmu_cmd_set_rtc },
+ { PMU_SYSTEM_READY, "SYSTEM READY", pmu_cmd_system_ready },
+ { PMU_GET_VERSION, "GET VERSION", pmu_cmd_get_version },
+ { PMU_POWER_EVENTS, "POWER EVENTS", pmu_cmd_power_events },
+ { PMU_GET_COVER, "GET_COVER", pmu_cmd_get_cover },
+ { PMU_DOWNLOAD_STATUS, "DOWNLOAD STATUS", pmu_cmd_download_status },
+ { PMU_READ_PMU_RAM, "READ PMGR RAM", pmu_cmd_read_pmu_ram },
+};
+
+static void pmu_dispatch_cmd(PMUState *s)
+{
+ unsigned int i;
+
+ /* No response by default */
+ s->cmd_rsp_sz = 0;
+
+ for (i = 0; i < ARRAY_SIZE(PMUCmdHandlers); i++) {
+ const PMUCmdHandler *desc = &PMUCmdHandlers[i];
+
+ if (desc->command != s->cmd) {
+ continue;
+ }
+
+ trace_pmu_dispatch_cmd(desc->name);
+ desc->handler(s, s->cmd_buf, s->cmd_buf_pos,
+ s->cmd_rsp, &s->cmd_rsp_sz);
+
+ if (s->rsplen != -1 && s->rsplen != s->cmd_rsp_sz) {
+ trace_pmu_debug_protocol_string("QEMU internal cmd resp mismatch!");
+ } else {
+ trace_pmu_debug_protocol_resp_size(s->cmd_rsp_sz);
+ }
+
+ return;
+ }
+
+ trace_pmu_dispatch_unknown_cmd(s->cmd);
+
+ /* Manufacture fake response with 0's */
+ if (s->rsplen == -1) {
+ s->cmd_rsp_sz = 0;
+ } else {
+ s->cmd_rsp_sz = s->rsplen;
+ memset(s->cmd_rsp, 0, s->rsplen);
+ }
+}
+
+static void pmu_update(PMUState *s)
+{
+ MOS6522PMUState *mps = &s->mos6522_pmu;
+ MOS6522State *ms = MOS6522(mps);
+ ADBBusState *adb_bus = &s->adb_bus;
+
+ /* Only react to changes in reg B */
+ if (ms->b == s->last_b) {
+ return;
+ }
+ s->last_b = ms->b;
+
+ /* Check the TREQ / TACK state */
+ switch (ms->b & (TREQ | TACK)) {
+ case TREQ:
+ /* This is an ack release, handle it and bail out */
+ ms->b |= TACK;
+ s->last_b = ms->b;
+
+ trace_pmu_debug_protocol_string("handshake: TREQ high, setting TACK");
+ return;
+ case TACK:
+ /* This is a valid request, handle below */
+ break;
+ case TREQ | TACK:
+ /* This is an idle state */
+ return;
+ default:
+ /* Invalid state, log and ignore */
+ trace_pmu_debug_protocol_error(ms->b);
+ return;
+ }
+
+ /* If we wanted to handle commands asynchronously, this is where
+ * we would delay the clearing of TACK until we are ready to send
+ * the response
+ */
+
+ /* We have a request, handshake TACK so we don't stay in
+ * an invalid state. If we were concurrent with the OS we
+ * should only do this after we grabbed the SR but that isn't
+ * a problem here.
+ */
+
+ trace_pmu_debug_protocol_clear_treq(s->cmd_state);
+
+ ms->b &= ~TACK;
+ s->last_b = ms->b;
+
+ /* Act according to state */
+ switch (s->cmd_state) {
+ case pmu_state_idle:
+ if (!(ms->acr & SR_OUT)) {
+ trace_pmu_debug_protocol_string("protocol error! "
+ "state idle, ACR reading");
+ break;
+ }
+
+ s->cmd = ms->sr;
+ via_set_sr_int(s);
+ s->cmdlen = pmu_data_len[s->cmd][0];
+ s->rsplen = pmu_data_len[s->cmd][1];
+ s->cmd_buf_pos = 0;
+ s->cmd_rsp_pos = 0;
+ s->cmd_state = pmu_state_cmd;
+
+ adb_autopoll_block(adb_bus);
+ trace_pmu_debug_protocol_cmd(s->cmd, s->cmdlen, s->rsplen);
+ break;
+
+ case pmu_state_cmd:
+ if (!(ms->acr & SR_OUT)) {
+ trace_pmu_debug_protocol_string("protocol error! "
+ "state cmd, ACR reading");
+ break;
+ }
+
+ if (s->cmdlen == -1) {
+ trace_pmu_debug_protocol_cmdlen(ms->sr);
+
+ s->cmdlen = ms->sr;
+ if (s->cmdlen > sizeof(s->cmd_buf)) {
+ trace_pmu_debug_protocol_cmd_toobig(s->cmdlen);
+ }
+ } else if (s->cmd_buf_pos < sizeof(s->cmd_buf)) {
+ s->cmd_buf[s->cmd_buf_pos++] = ms->sr;
+ }
+
+ via_set_sr_int(s);
+ break;
+
+ case pmu_state_rsp:
+ if (ms->acr & SR_OUT) {
+ trace_pmu_debug_protocol_string("protocol error! "
+ "state resp, ACR writing");
+ break;
+ }
+
+ if (s->rsplen == -1) {
+ trace_pmu_debug_protocol_cmd_send_resp_size(s->cmd_rsp_sz);
+
+ ms->sr = s->cmd_rsp_sz;
+ s->rsplen = s->cmd_rsp_sz;
+ } else if (s->cmd_rsp_pos < s->cmd_rsp_sz) {
+ trace_pmu_debug_protocol_cmd_send_resp(s->cmd_rsp_pos, s->rsplen);
+
+ ms->sr = s->cmd_rsp[s->cmd_rsp_pos++];
+ }
+
+ via_set_sr_int(s);
+ break;
+ }
+
+ /* Check for state completion */
+ if (s->cmd_state == pmu_state_cmd && s->cmdlen == s->cmd_buf_pos) {
+ trace_pmu_debug_protocol_string("Command reception complete, "
+ "dispatching...");
+
+ pmu_dispatch_cmd(s);
+ s->cmd_state = pmu_state_rsp;
+ }
+
+ if (s->cmd_state == pmu_state_rsp && s->rsplen == s->cmd_rsp_pos) {
+ trace_pmu_debug_protocol_cmd_resp_complete(ms->ier);
+
+ adb_autopoll_unblock(adb_bus);
+ s->cmd_state = pmu_state_idle;
+ }
+}
+
+static uint64_t mos6522_pmu_read(void *opaque, hwaddr addr, unsigned size)
+{
+ PMUState *s = opaque;
+ MOS6522PMUState *mps = &s->mos6522_pmu;
+ MOS6522State *ms = MOS6522(mps);
+
+ addr = (addr >> 9) & 0xf;
+ return mos6522_read(ms, addr, size);
+}
+
+static void mos6522_pmu_write(void *opaque, hwaddr addr, uint64_t val,
+ unsigned size)
+{
+ PMUState *s = opaque;
+ MOS6522PMUState *mps = &s->mos6522_pmu;
+ MOS6522State *ms = MOS6522(mps);
+
+ addr = (addr >> 9) & 0xf;
+ mos6522_write(ms, addr, val, size);
+}
+
+static const MemoryRegionOps mos6522_pmu_ops = {
+ .read = mos6522_pmu_read,
+ .write = mos6522_pmu_write,
+ .endianness = DEVICE_BIG_ENDIAN,
+ .impl = {
+ .min_access_size = 1,
+ .max_access_size = 1,
+ },
+};
+
+static bool pmu_adb_state_needed(void *opaque)
+{
+ PMUState *s = opaque;
+
+ return s->has_adb;
+}
+
+static const VMStateDescription vmstate_pmu_adb = {
+ .name = "pmu/adb",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .needed = pmu_adb_state_needed,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT8(adb_reply_size, PMUState),
+ VMSTATE_BUFFER(adb_reply, PMUState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const VMStateDescription vmstate_pmu = {
+ .name = "pmu",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_STRUCT(mos6522_pmu.parent_obj, PMUState, 0, vmstate_mos6522,
+ MOS6522State),
+ VMSTATE_UINT8(last_b, PMUState),
+ VMSTATE_UINT8(cmd, PMUState),
+ VMSTATE_UINT32(cmdlen, PMUState),
+ VMSTATE_UINT32(rsplen, PMUState),
+ VMSTATE_UINT8(cmd_buf_pos, PMUState),
+ VMSTATE_BUFFER(cmd_buf, PMUState),
+ VMSTATE_UINT8(cmd_rsp_pos, PMUState),
+ VMSTATE_UINT8(cmd_rsp_sz, PMUState),
+ VMSTATE_BUFFER(cmd_rsp, PMUState),
+ VMSTATE_UINT8(intbits, PMUState),
+ VMSTATE_UINT8(intmask, PMUState),
+ VMSTATE_UINT32(tick_offset, PMUState),
+ VMSTATE_TIMER_PTR(one_sec_timer, PMUState),
+ VMSTATE_INT64(one_sec_target, PMUState),
+ VMSTATE_END_OF_LIST()
+ },
+ .subsections = (const VMStateDescription * []) {
+ &vmstate_pmu_adb,
+ NULL
+ }
+};
+
+static void pmu_reset(DeviceState *dev)
+{
+ PMUState *s = VIA_PMU(dev);
+
+ /* OpenBIOS needs to do this? MacOS 9 needs it */
+ s->intmask = PMU_INT_ADB | PMU_INT_TICK;
+ s->intbits = 0;
+
+ s->cmd_state = pmu_state_idle;
+}
+
+static void pmu_realize(DeviceState *dev, Error **errp)
+{
+ PMUState *s = VIA_PMU(dev);
+ SysBusDevice *sbd;
+ ADBBusState *adb_bus = &s->adb_bus;
+ struct tm tm;
+
+ if (!sysbus_realize(SYS_BUS_DEVICE(&s->mos6522_pmu), errp)) {
+ return;
+ }
+
+ /* Pass IRQ from 6522 */
+ sbd = SYS_BUS_DEVICE(s);
+ sysbus_pass_irq(sbd, SYS_BUS_DEVICE(&s->mos6522_pmu));
+
+ qemu_get_timedate(&tm, 0);
+ s->tick_offset = (uint32_t)mktimegm(&tm) + RTC_OFFSET;
+ s->one_sec_timer = timer_new_ms(QEMU_CLOCK_VIRTUAL, pmu_one_sec_timer, s);
+ s->one_sec_target = qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL) + 1000;
+ timer_mod(s->one_sec_timer, s->one_sec_target);
+
+ if (s->has_adb) {
+ qbus_init(&s->adb_bus, sizeof(s->adb_bus), TYPE_ADB_BUS,
+ dev, "adb.0");
+ adb_register_autopoll_callback(adb_bus, pmu_adb_poll, s);
+ }
+}
+
+static void pmu_init(Object *obj)
+{
+ SysBusDevice *d = SYS_BUS_DEVICE(obj);
+ PMUState *s = VIA_PMU(obj);
+
+ object_property_add_link(obj, "gpio", TYPE_MACIO_GPIO,
+ (Object **) &s->gpio,
+ qdev_prop_allow_set_link_before_realize,
+ 0);
+
+ object_initialize_child(obj, "mos6522-pmu", &s->mos6522_pmu,
+ TYPE_MOS6522_PMU);
+
+ memory_region_init_io(&s->mem, obj, &mos6522_pmu_ops, s, "via-pmu",
+ 0x2000);
+ sysbus_init_mmio(d, &s->mem);
+}
+
+static Property pmu_properties[] = {
+ DEFINE_PROP_BOOL("has-adb", PMUState, has_adb, true),
+ DEFINE_PROP_END_OF_LIST()
+};
+
+static void pmu_class_init(ObjectClass *oc, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(oc);
+
+ dc->realize = pmu_realize;
+ dc->reset = pmu_reset;
+ dc->vmsd = &vmstate_pmu;
+ device_class_set_props(dc, pmu_properties);
+ set_bit(DEVICE_CATEGORY_BRIDGE, dc->categories);
+}
+
+static const TypeInfo pmu_type_info = {
+ .name = TYPE_VIA_PMU,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(PMUState),
+ .instance_init = pmu_init,
+ .class_init = pmu_class_init,
+};
+
+static void mos6522_pmu_portB_write(MOS6522State *s)
+{
+ MOS6522PMUState *mps = container_of(s, MOS6522PMUState, parent_obj);
+ PMUState *ps = container_of(mps, PMUState, mos6522_pmu);
+
+ if ((s->pcr & 0xe0) == 0x20 || (s->pcr & 0xe0) == 0x60) {
+ s->ifr &= ~CB2_INT;
+ }
+ s->ifr &= ~CB1_INT;
+
+ via_update_irq(ps);
+ pmu_update(ps);
+}
+
+static void mos6522_pmu_portA_write(MOS6522State *s)
+{
+ MOS6522PMUState *mps = container_of(s, MOS6522PMUState, parent_obj);
+ PMUState *ps = container_of(mps, PMUState, mos6522_pmu);
+
+ if ((s->pcr & 0x0e) == 0x02 || (s->pcr & 0x0e) == 0x06) {
+ s->ifr &= ~CA2_INT;
+ }
+ s->ifr &= ~CA1_INT;
+
+ via_update_irq(ps);
+}
+
+static void mos6522_pmu_reset(DeviceState *dev)
+{
+ MOS6522State *ms = MOS6522(dev);
+ MOS6522PMUState *mps = container_of(ms, MOS6522PMUState, parent_obj);
+ PMUState *s = container_of(mps, PMUState, mos6522_pmu);
+ MOS6522DeviceClass *mdc = MOS6522_GET_CLASS(ms);
+
+ mdc->parent_reset(dev);
+
+ ms->timers[0].frequency = VIA_TIMER_FREQ;
+ ms->timers[1].frequency = (SCALE_US * 6000) / 4700;
+
+ s->last_b = ms->b = TACK | TREQ;
+}
+
+static void mos6522_pmu_class_init(ObjectClass *oc, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(oc);
+ MOS6522DeviceClass *mdc = MOS6522_CLASS(oc);
+
+ dc->reset = mos6522_pmu_reset;
+ mdc->portB_write = mos6522_pmu_portB_write;
+ mdc->portA_write = mos6522_pmu_portA_write;
+}
+
+static const TypeInfo mos6522_pmu_type_info = {
+ .name = TYPE_MOS6522_PMU,
+ .parent = TYPE_MOS6522,
+ .instance_size = sizeof(MOS6522PMUState),
+ .class_init = mos6522_pmu_class_init,
+};
+
+static void pmu_register_types(void)
+{
+ type_register_static(&pmu_type_info);
+ type_register_static(&mos6522_pmu_type_info);
+}
+
+type_init(pmu_register_types)
diff --git a/hw/misc/macio/trace-events b/hw/misc/macio/trace-events
new file mode 100644
index 000000000..ad4b9d1c0
--- /dev/null
+++ b/hw/misc/macio/trace-events
@@ -0,0 +1,42 @@
+# See docs/devel/tracing.rst for syntax documentation.
+
+# cuda.c
+cuda_delay_set_sr_int(void) ""
+cuda_data_send(uint8_t data) "send: 0x%02x"
+cuda_data_recv(uint8_t data) "recv: 0x%02x"
+cuda_receive_packet_cmd(const char *cmd) "handling command %s"
+cuda_packet_receive(int len) "length %d"
+cuda_packet_receive_data(int i, const uint8_t data) "[%d] 0x%02x"
+cuda_packet_send(int len) "length %d"
+cuda_packet_send_data(int i, const uint8_t data) "[%d] 0x%02x"
+
+# macio.c
+macio_timer_write(uint64_t addr, unsigned len, uint64_t val) "write addr 0x%"PRIx64 " len %d val 0x%"PRIx64
+macio_timer_read(uint64_t addr, unsigned len, uint32_t val) "read addr 0x%"PRIx64 " len %d val 0x%"PRIx32
+
+# gpio.c
+macio_set_gpio(int gpio, bool state) "setting GPIO %d to %d"
+macio_gpio_irq_assert(int gpio) "asserting GPIO %d"
+macio_gpio_irq_deassert(int gpio) "deasserting GPIO %d"
+macio_gpio_write(uint64_t addr, uint64_t val) "addr: 0x%"PRIx64" value: 0x%"PRIx64
+
+# pmu.c
+pmu_adb_poll(int olen) "ADB autopoll, olen=%d"
+pmu_one_sec_timer(void) "PMU one sec..."
+pmu_cmd_set_int_mask(int intmask) "Setting PMU int mask to 0x%02x"
+pmu_cmd_set_adb_autopoll(int mask) "ADB set autopoll, mask=0x%04x"
+pmu_cmd_adb_nobus(void) "ADB PACKET with no ADB bus!"
+pmu_cmd_adb_request(int inlen, int indata0, int indata1, int indata2, int indata3, int indata4) "ADB request: len=%d, cmd=0x%02x, pflags=0x%02x, adblen=%d: 0x%02x 0x%02x..."
+pmu_cmd_adb_reply(int len) "ADB reply is %d bytes"
+pmu_dispatch_cmd(const char *name) "handling command %s"
+pmu_dispatch_unknown_cmd(int cmd) "Unknown PMU command 0x%02x"
+pmu_debug_protocol_string(const char *str) "%s"
+pmu_debug_protocol_resp_size(int size) "sending %d resp bytes"
+pmu_debug_protocol_error(int portB) "protocol error! portB=0x%02x"
+pmu_debug_protocol_clear_treq(int state) "TREQ cleared, clearing TACK, state: %d"
+pmu_debug_protocol_cmd(int cmd, int cmdlen, int rsplen) "Got command byte 0x%02x, clen=%d, rlen=%d"
+pmu_debug_protocol_cmdlen(int len) "got cmd length byte: %d"
+pmu_debug_protocol_cmd_toobig(int len) "command too big (%d bytes)"
+pmu_debug_protocol_cmd_send_resp_size(int len) "sending length byte: %d"
+pmu_debug_protocol_cmd_send_resp(int pos, int len) "sending byte: %d/%d"
+pmu_debug_protocol_cmd_resp_complete(int ier) "Response send complete. IER=0x%02x"
diff --git a/hw/misc/macio/trace.h b/hw/misc/macio/trace.h
new file mode 100644
index 000000000..34a3cf1b4
--- /dev/null
+++ b/hw/misc/macio/trace.h
@@ -0,0 +1 @@
+#include "trace/trace-hw_misc_macio.h"