diff options
Diffstat (limited to 'hw/misc/macio')
-rw-r--r-- | hw/misc/macio/Kconfig | 11 | ||||
-rw-r--r-- | hw/misc/macio/cuda.c | 629 | ||||
-rw-r--r-- | hw/misc/macio/gpio.c | 219 | ||||
-rw-r--r-- | hw/misc/macio/mac_dbdma.c | 940 | ||||
-rw-r--r-- | hw/misc/macio/macio.c | 504 | ||||
-rw-r--r-- | hw/misc/macio/meson.build | 8 | ||||
-rw-r--r-- | hw/misc/macio/pmu.c | 871 | ||||
-rw-r--r-- | hw/misc/macio/trace-events | 42 | ||||
-rw-r--r-- | hw/misc/macio/trace.h | 1 |
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, ¤t->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, ¤t->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" |