diff --git a/hw/m68k/q800.c b/hw/m68k/q800.c index fd4855047e..e4c7c9b88a 100644 --- a/hw/m68k/q800.c +++ b/hw/m68k/q800.c @@ -28,6 +28,7 @@ #include "cpu.h" #include "hw/boards.h" #include "hw/or-irq.h" +#include "hw/nmi.h" #include "elf.h" #include "hw/loader.h" #include "ui/console.h" @@ -100,13 +101,110 @@ struct GLUEState { SysBusDevice parent_obj; M68kCPU *cpu; uint8_t ipr; + uint8_t auxmode; + qemu_irq irqs[1]; + QEMUTimer *nmi_release; }; +#define GLUE_IRQ_IN_VIA1 0 +#define GLUE_IRQ_IN_VIA2 1 +#define GLUE_IRQ_IN_SONIC 2 +#define GLUE_IRQ_IN_ESCC 3 +#define GLUE_IRQ_IN_NMI 4 + +#define GLUE_IRQ_NUBUS_9 0 + +/* + * The GLUE logic on the Quadra 800 supports 2 different IRQ routing modes + * controlled from the VIA1 auxmode GPIO (port B bit 6) which are documented + * in NetBSD as follows: + * + * A/UX mode (Linux, NetBSD, auxmode GPIO low) + * + * Level 0: Spurious: ignored + * Level 1: Software + * Level 2: VIA2 (except ethernet, sound) + * Level 3: Ethernet + * Level 4: Serial (SCC) + * Level 5: Sound + * Level 6: VIA1 + * Level 7: NMIs: parity errors, RESET button, YANCC error + * + * Classic mode (default: used by MacOS, A/UX 3.0.1, auxmode GPIO high) + * + * Level 0: Spurious: ignored + * Level 1: VIA1 (clock, ADB) + * Level 2: VIA2 (NuBus, SCSI) + * Level 3: + * Level 4: Serial (SCC) + * Level 5: + * Level 6: + * Level 7: Non-maskable: parity errors, RESET button + * + * Note that despite references to A/UX mode in Linux and NetBSD, at least + * A/UX 3.0.1 still uses Classic mode. + */ + static void GLUE_set_irq(void *opaque, int irq, int level) { GLUEState *s = opaque; int i; + if (s->auxmode) { + /* Classic mode */ + switch (irq) { + case GLUE_IRQ_IN_VIA1: + irq = 0; + break; + + case GLUE_IRQ_IN_VIA2: + irq = 1; + break; + + case GLUE_IRQ_IN_SONIC: + /* Route to VIA2 instead */ + qemu_set_irq(s->irqs[GLUE_IRQ_NUBUS_9], level); + return; + + case GLUE_IRQ_IN_ESCC: + irq = 3; + break; + + case GLUE_IRQ_IN_NMI: + irq = 6; + break; + + default: + g_assert_not_reached(); + } + } else { + /* A/UX mode */ + switch (irq) { + case GLUE_IRQ_IN_VIA1: + irq = 5; + break; + + case GLUE_IRQ_IN_VIA2: + irq = 1; + break; + + case GLUE_IRQ_IN_SONIC: + irq = 2; + break; + + case GLUE_IRQ_IN_ESCC: + irq = 3; + break; + + case GLUE_IRQ_IN_NMI: + irq = 6; + break; + + default: + g_assert_not_reached(); + } + } + if (level) { s->ipr |= 1 << irq; } else { @@ -122,11 +220,37 @@ static void GLUE_set_irq(void *opaque, int irq, int level) m68k_set_irq_level(s->cpu, 0, 0); } +static void glue_auxmode_set_irq(void *opaque, int irq, int level) +{ + GLUEState *s = GLUE(opaque); + + s->auxmode = level; +} + +static void glue_nmi(NMIState *n, int cpu_index, Error **errp) +{ + GLUEState *s = GLUE(n); + + /* Hold NMI active for 100ms */ + GLUE_set_irq(s, GLUE_IRQ_IN_NMI, 1); + timer_mod(s->nmi_release, qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL) + 100); +} + +static void glue_nmi_release(void *opaque) +{ + GLUEState *s = GLUE(opaque); + + GLUE_set_irq(s, GLUE_IRQ_IN_NMI, 0); +} + static void glue_reset(DeviceState *dev) { GLUEState *s = GLUE(dev); s->ipr = 0; + s->auxmode = 0; + + timer_del(s->nmi_release); } static const VMStateDescription vmstate_glue = { @@ -135,6 +259,8 @@ static const VMStateDescription vmstate_glue = { .minimum_version_id = 0, .fields = (VMStateField[]) { VMSTATE_UINT8(ipr, GLUEState), + VMSTATE_UINT8(auxmode, GLUEState), + VMSTATE_TIMER_PTR(nmi_release, GLUEState), VMSTATE_END_OF_LIST(), }, }; @@ -150,20 +276,36 @@ static Property glue_properties[] = { DEFINE_PROP_END_OF_LIST(), }; +static void glue_finalize(Object *obj) +{ + GLUEState *s = GLUE(obj); + + timer_free(s->nmi_release); +} + static void glue_init(Object *obj) { DeviceState *dev = DEVICE(obj); + GLUEState *s = GLUE(dev); qdev_init_gpio_in(dev, GLUE_set_irq, 8); + qdev_init_gpio_in_named(dev, glue_auxmode_set_irq, "auxmode", 1); + + qdev_init_gpio_out(dev, s->irqs, 1); + + /* NMI release timer */ + s->nmi_release = timer_new_ms(QEMU_CLOCK_VIRTUAL, glue_nmi_release, s); } static void glue_class_init(ObjectClass *klass, void *data) { DeviceClass *dc = DEVICE_CLASS(klass); + NMIClass *nc = NMI_CLASS(klass); dc->vmsd = &vmstate_glue; dc->reset = glue_reset; device_class_set_props(dc, glue_properties); + nc->nmi_monitor_handler = glue_nmi; } static const TypeInfo glue_info = { @@ -171,7 +313,12 @@ static const TypeInfo glue_info = { .parent = TYPE_SYS_BUS_DEVICE, .instance_size = sizeof(GLUEState), .instance_init = glue_init, + .instance_finalize = glue_finalize, .class_init = glue_class_init, + .interfaces = (InterfaceInfo[]) { + { TYPE_NMI }, + { } + }, }; static void main_cpu_reset(void *opaque) @@ -284,7 +431,10 @@ static void q800_init(MachineState *machine) sysbus = SYS_BUS_DEVICE(via1_dev); sysbus_realize_and_unref(sysbus, &error_fatal); sysbus_mmio_map(sysbus, 1, VIA_BASE); - sysbus_connect_irq(sysbus, 0, qdev_get_gpio_in(glue, 0)); + sysbus_connect_irq(sysbus, 0, qdev_get_gpio_in(glue, GLUE_IRQ_IN_VIA1)); + /* A/UX mode */ + qdev_connect_gpio_out(via1_dev, 0, + qdev_get_gpio_in_named(glue, "auxmode", 0)); adb_bus = qdev_get_child_bus(via1_dev, "adb.0"); dev = qdev_new(TYPE_ADB_KEYBOARD); @@ -297,7 +447,7 @@ static void q800_init(MachineState *machine) sysbus = SYS_BUS_DEVICE(via2_dev); sysbus_realize_and_unref(sysbus, &error_fatal); sysbus_mmio_map(sysbus, 1, VIA_BASE + VIA_SIZE); - sysbus_connect_irq(sysbus, 0, qdev_get_gpio_in(glue, 1)); + sysbus_connect_irq(sysbus, 0, qdev_get_gpio_in(glue, GLUE_IRQ_IN_VIA2)); /* MACSONIC */ @@ -330,7 +480,7 @@ static void q800_init(MachineState *machine) sysbus = SYS_BUS_DEVICE(dev); sysbus_realize_and_unref(sysbus, &error_fatal); sysbus_mmio_map(sysbus, 0, SONIC_BASE); - sysbus_connect_irq(sysbus, 0, qdev_get_gpio_in(glue, 2)); + sysbus_connect_irq(sysbus, 0, qdev_get_gpio_in(glue, GLUE_IRQ_IN_SONIC)); memory_region_init_rom(dp8393x_prom, NULL, "dp8393x-q800.prom", SONIC_PROM_SIZE, &error_fatal); @@ -366,7 +516,8 @@ static void q800_init(MachineState *machine) qdev_realize_and_unref(escc_orgate, NULL, &error_fatal); sysbus_connect_irq(sysbus, 0, qdev_get_gpio_in(escc_orgate, 0)); sysbus_connect_irq(sysbus, 1, qdev_get_gpio_in(escc_orgate, 1)); - qdev_connect_gpio_out(DEVICE(escc_orgate), 0, qdev_get_gpio_in(glue, 3)); + qdev_connect_gpio_out(DEVICE(escc_orgate), 0, + qdev_get_gpio_in(glue, GLUE_IRQ_IN_ESCC)); sysbus_mmio_map(sysbus, 0, SCC_BASE); /* SCSI */ @@ -416,6 +567,14 @@ static void q800_init(MachineState *machine) VIA2_NUBUS_IRQ_9 + i)); } + /* + * Since the framebuffer in slot 0x9 uses a separate IRQ, wire the unused + * IRQ via GLUE for use by SONIC Ethernet in classic mode + */ + qdev_connect_gpio_out(glue, GLUE_IRQ_NUBUS_9, + qdev_get_gpio_in_named(via2_dev, "nubus-irq", + VIA2_NUBUS_IRQ_9)); + nubus = &NUBUS_BRIDGE(dev)->bus; /* framebuffer in nubus slot #9 */ @@ -425,7 +584,7 @@ static void q800_init(MachineState *machine) qdev_prop_set_uint32(dev, "width", graphic_width); qdev_prop_set_uint32(dev, "height", graphic_height); qdev_prop_set_uint8(dev, "depth", graphic_depth); - if (graphic_width == 1152 && graphic_height == 870 && graphic_depth == 8) { + if (graphic_width == 1152 && graphic_height == 870) { qdev_prop_set_uint8(dev, "display", MACFB_DISPLAY_APPLE_21_COLOR); } else { qdev_prop_set_uint8(dev, "display", MACFB_DISPLAY_VGA); diff --git a/hw/misc/mac_via.c b/hw/misc/mac_via.c index 993bac017d..b378e6b305 100644 --- a/hw/misc/mac_via.c +++ b/hw/misc/mac_via.c @@ -130,6 +130,10 @@ * On SE/30, vertical sync interrupt enable. * 0=enabled. This vSync interrupt shows up * as a slot $E interrupt. + * On Quadra 800 this bit toggles A/UX mode which + * configures the glue logic to deliver some IRQs + * at different levels compared to a classic + * Mac. */ #define VIA1B_vADBS2 0x20 /* ADB state input bit 1 (unused on IIfx) */ #define VIA1B_vADBS1 0x10 /* ADB state input bit 0 (unused on IIfx) */ @@ -876,6 +880,21 @@ static void via1_adb_update(MOS6522Q800VIA1State *v1s) } } +static void via1_auxmode_update(MOS6522Q800VIA1State *v1s) +{ + MOS6522State *s = MOS6522(v1s); + int oldirq, irq; + + oldirq = (v1s->last_b & VIA1B_vMystery) ? 1 : 0; + irq = (s->b & VIA1B_vMystery) ? 1 : 0; + + /* Check to see if the A/UX mode bit has changed */ + if (irq != oldirq) { + trace_via1_auxmode(irq); + qemu_set_irq(v1s->auxmode_irq, irq); + } +} + static uint64_t mos6522_q800_via1_read(void *opaque, hwaddr addr, unsigned size) { MOS6522Q800VIA1State *s = MOS6522_Q800_VIA1(opaque); @@ -898,6 +917,7 @@ static void mos6522_q800_via1_write(void *opaque, hwaddr addr, uint64_t val, case VIA_REG_B: via1_rtc_update(v1s); via1_adb_update(v1s); + via1_auxmode_update(v1s); v1s->last_b = ms->b; break; @@ -1042,6 +1062,9 @@ static void mos6522_q800_via1_init(Object *obj) TYPE_ADB_BUS, DEVICE(v1s), "adb.0"); qdev_init_gpio_in(DEVICE(obj), via1_irq_request, VIA1_IRQ_NB); + + /* A/UX mode */ + qdev_init_gpio_out(DEVICE(obj), &v1s->auxmode_irq, 1); } static const VMStateDescription vmstate_q800_via1 = { diff --git a/hw/misc/trace-events b/hw/misc/trace-events index ede413965b..2da96d167a 100644 --- a/hw/misc/trace-events +++ b/hw/misc/trace-events @@ -228,6 +228,7 @@ via1_rtc_cmd_pram_sect_write(int sector, int offset, int addr, int value) "secto via1_adb_send(const char *state, uint8_t data, const char *vadbint) "state %s data=0x%02x vADBInt=%s" via1_adb_receive(const char *state, uint8_t data, const char *vadbint, int status, int index, int size) "state %s data=0x%02x vADBInt=%s status=0x%x index=%d size=%d" via1_adb_poll(uint8_t data, const char *vadbint, int status, int index, int size) "data=0x%02x vADBInt=%s status=0x%x index=%d size=%d" +via1_auxmode(int mode) "setting auxmode to %d" # grlib_ahb_apb_pnp.c grlib_ahb_pnp_read(uint64_t addr, uint32_t value) "AHB PnP read addr:0x%03"PRIx64" data:0x%08x" diff --git a/include/hw/misc/mac_via.h b/include/hw/misc/mac_via.h index 4506abe5d0..b445565866 100644 --- a/include/hw/misc/mac_via.h +++ b/include/hw/misc/mac_via.h @@ -43,6 +43,7 @@ struct MOS6522Q800VIA1State { MemoryRegion via_mem; qemu_irq irqs[VIA1_IRQ_NB]; + qemu_irq auxmode_irq; uint8_t last_b; /* RTC */