Skip to content

Commit

Permalink
mos6522: implement edge-triggering for CA1/2 and CB1/2 control line IRQs
Browse files Browse the repository at this point in the history
The mos6522 datasheet describes how the control lines IRQs are edge-triggered
according to the configuration in the PCR register. Implement the logic according
to the datasheet so that the interrupt bits in IFR are latched when the edge is
detected, and cleared when reading portA/portB or writing to IFR as necessary.

To maintain bisectibility this change also updates the SCSI, SCSI data, Nubus
and VIA2 60Hz/1Hz clocks in the q800 machine to be negative edge-triggered as
confirmed by the PCR programming in all of Linux, NetBSD and MacOS.

Signed-off-by: Mark Cave-Ayland <mark.cave-ayland@ilande.co.uk>
Reviewed-by: Laurent Vivier <laurent@vivier.eu>
Message-Id: <20220305150957.5053-12-mark.cave-ayland@ilande.co.uk>
Signed-off-by: Mark Cave-Ayland <mark.cave-ayland@ilande.co.uk>
  • Loading branch information
mcayland committed Mar 9, 2022
1 parent 677a472 commit b793b4e
Show file tree
Hide file tree
Showing 4 changed files with 109 additions and 12 deletions.
9 changes: 5 additions & 4 deletions hw/m68k/q800.c
Expand Up @@ -533,10 +533,11 @@ static void q800_init(MachineState *machine)

sysbus = SYS_BUS_DEVICE(dev);
sysbus_realize_and_unref(sysbus, &error_fatal);
sysbus_connect_irq(sysbus, 0, qdev_get_gpio_in(via2_dev,
VIA2_IRQ_SCSI_BIT));
sysbus_connect_irq(sysbus, 1, qdev_get_gpio_in(via2_dev,
VIA2_IRQ_SCSI_DATA_BIT));
/* SCSI and SCSI data IRQs are negative edge triggered */
sysbus_connect_irq(sysbus, 0, qemu_irq_invert(qdev_get_gpio_in(via2_dev,
VIA2_IRQ_SCSI_BIT)));
sysbus_connect_irq(sysbus, 1, qemu_irq_invert(qdev_get_gpio_in(via2_dev,
VIA2_IRQ_SCSI_DATA_BIT)));
sysbus_mmio_map(sysbus, 0, ESP_BASE);
sysbus_mmio_map(sysbus, 1, ESP_PDMA);

Expand Down
15 changes: 11 additions & 4 deletions hw/misc/mac_via.c
Expand Up @@ -327,7 +327,9 @@ static void via1_sixty_hz(void *opaque)
MOS6522State *s = MOS6522(v1s);
qemu_irq irq = qdev_get_gpio_in(DEVICE(s), VIA1_IRQ_60HZ_BIT);

qemu_set_irq(irq, 1);
/* Negative edge trigger */
qemu_irq_lower(irq);
qemu_irq_raise(irq);

via1_sixty_hz_update(v1s);
}
Expand All @@ -338,7 +340,9 @@ static void via1_one_second(void *opaque)
MOS6522State *s = MOS6522(v1s);
qemu_irq irq = qdev_get_gpio_in(DEVICE(s), VIA1_IRQ_ONE_SECOND_BIT);

qemu_set_irq(irq, 1);
/* Negative edge trigger */
qemu_irq_lower(irq);
qemu_irq_raise(irq);

via1_one_second_update(v1s);
}
Expand Down Expand Up @@ -917,9 +921,11 @@ static uint64_t mos6522_q800_via2_read(void *opaque, hwaddr addr, unsigned size)
* On a Q800 an emulated VIA2 is integrated into the onboard logic. The
* expectation of most OSs is that the DRQ bit is live, rather than
* latched as it would be on a real VIA so do the same here.
*
* Note: DRQ is negative edge triggered
*/
val &= ~VIA2_IRQ_SCSI_DATA;
val |= (ms->last_irq_levels & VIA2_IRQ_SCSI_DATA);
val |= (~ms->last_irq_levels & VIA2_IRQ_SCSI_DATA);
break;
}

Expand Down Expand Up @@ -1146,7 +1152,8 @@ static void via2_nubus_irq_request(void *opaque, int n, int level)
s->a |= (1 << n);
}

qemu_set_irq(irq, level);
/* Negative edge trigger */
qemu_set_irq(irq, !level);
}

static void mos6522_q800_via2_init(Object *obj)
Expand Down
82 changes: 78 additions & 4 deletions hw/misc/mos6522.c
Expand Up @@ -64,14 +64,62 @@ static void mos6522_update_irq(MOS6522State *s)
static void mos6522_set_irq(void *opaque, int n, int level)
{
MOS6522State *s = MOS6522(opaque);
int last_level = !!(s->last_irq_levels & (1 << n));
uint8_t last_ifr = s->ifr;
bool positive_edge = true;
int ctrl;

/*
* SR_INT is managed by mos6522 instances and cleared upon SR
* read. It is only the external CA1/2 and CB1/2 lines that
* are edge-triggered and latched in IFR
*/
if (n != SR_INT_BIT && level == last_level) {
return;
}

if (level) {
/* Detect negative edge trigger */
if (last_level == 1 && level == 0) {
positive_edge = false;
}

switch (n) {
case CA2_INT_BIT:
ctrl = (s->pcr & CA2_CTRL_MASK) >> CA2_CTRL_SHIFT;
if ((positive_edge && (ctrl & C2_POS)) ||
(!positive_edge && !(ctrl & C2_POS))) {
s->ifr |= 1 << n;
}
break;
case CA1_INT_BIT:
ctrl = (s->pcr & CA1_CTRL_MASK) >> CA1_CTRL_SHIFT;
if ((positive_edge && (ctrl & C1_POS)) ||
(!positive_edge && !(ctrl & C1_POS))) {
s->ifr |= 1 << n;
}
break;
case SR_INT_BIT:
s->ifr |= 1 << n;
} else {
s->ifr &= ~(1 << n);
break;
case CB2_INT_BIT:
ctrl = (s->pcr & CB2_CTRL_MASK) >> CB2_CTRL_SHIFT;
if ((positive_edge && (ctrl & C2_POS)) ||
(!positive_edge && !(ctrl & C2_POS))) {
s->ifr |= 1 << n;
}
break;
case CB1_INT_BIT:
ctrl = (s->pcr & CB1_CTRL_MASK) >> CB1_CTRL_SHIFT;
if ((positive_edge && (ctrl & C1_POS)) ||
(!positive_edge && !(ctrl & C1_POS))) {
s->ifr |= 1 << n;
}
break;
}

mos6522_update_irq(s);
if (s->ifr != last_ifr) {
mos6522_update_irq(s);
}

if (level) {
s->last_irq_levels |= 1 << n;
Expand Down Expand Up @@ -250,6 +298,7 @@ uint64_t mos6522_read(void *opaque, hwaddr addr, unsigned size)
{
MOS6522State *s = opaque;
uint32_t val;
int ctrl;
int64_t now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);

if (now >= s->timers[0].next_irq_time) {
Expand All @@ -263,12 +312,24 @@ uint64_t mos6522_read(void *opaque, hwaddr addr, unsigned size)
switch (addr) {
case VIA_REG_B:
val = s->b;
ctrl = (s->pcr & CB2_CTRL_MASK) >> CB2_CTRL_SHIFT;
if (!(ctrl & C2_IND)) {
s->ifr &= ~CB2_INT;
}
s->ifr &= ~CB1_INT;
mos6522_update_irq(s);
break;
case VIA_REG_A:
qemu_log_mask(LOG_UNIMP, "Read access to register A with handshake");
/* fall through */
case VIA_REG_ANH:
val = s->a;
ctrl = (s->pcr & CA2_CTRL_MASK) >> CA2_CTRL_SHIFT;
if (!(ctrl & C2_IND)) {
s->ifr &= ~CA2_INT;
}
s->ifr &= ~CA1_INT;
mos6522_update_irq(s);
break;
case VIA_REG_DIRB:
val = s->dirb;
Expand Down Expand Up @@ -335,20 +396,33 @@ void mos6522_write(void *opaque, hwaddr addr, uint64_t val, unsigned size)
{
MOS6522State *s = opaque;
MOS6522DeviceClass *mdc = MOS6522_GET_CLASS(s);
int ctrl;

trace_mos6522_write(addr, mos6522_reg_names[addr], val);

switch (addr) {
case VIA_REG_B:
s->b = (s->b & ~s->dirb) | (val & s->dirb);
mdc->portB_write(s);
ctrl = (s->pcr & CB2_CTRL_MASK) >> CB2_CTRL_SHIFT;
if (!(ctrl & C2_IND)) {
s->ifr &= ~CB2_INT;
}
s->ifr &= ~CB1_INT;
mos6522_update_irq(s);
break;
case VIA_REG_A:
qemu_log_mask(LOG_UNIMP, "Write access to register A with handshake");
/* fall through */
case VIA_REG_ANH:
s->a = (s->a & ~s->dira) | (val & s->dira);
mdc->portA_write(s);
ctrl = (s->pcr & CA2_CTRL_MASK) >> CA2_CTRL_SHIFT;
if (!(ctrl & C2_IND)) {
s->ifr &= ~CA2_INT;
}
s->ifr &= ~CA1_INT;
mos6522_update_irq(s);
break;
case VIA_REG_DIRB:
s->dirb = val;
Expand Down
15 changes: 15 additions & 0 deletions include/hw/misc/mos6522.h
Expand Up @@ -65,6 +65,21 @@
#define T1MODE 0xc0 /* Timer 1 mode */
#define T1MODE_CONT 0x40 /* continuous interrupts */

/* Bits in PCR */
#define CB2_CTRL_MASK 0xe0
#define CB2_CTRL_SHIFT 5
#define CB1_CTRL_MASK 0x10
#define CB1_CTRL_SHIFT 4
#define CA2_CTRL_MASK 0x0e
#define CA2_CTRL_SHIFT 1
#define CA1_CTRL_MASK 0x1
#define CA1_CTRL_SHIFT 0

#define C2_POS 0x2
#define C2_IND 0x1

#define C1_POS 0x1

/* VIA registers */
#define VIA_REG_B 0x00
#define VIA_REG_A 0x01
Expand Down

0 comments on commit b793b4e

Please sign in to comment.