NetBSD-Bugs archive
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index][Old Index]
kern/60693: ae(4) does not work with Kinetics EtherPort and Dayna DaynaPORT NuBus cards: register byte lane is hardcoded
>Number: 60693
>Category: kern
>Synopsis: ae(4) does not work with Kinetics EtherPort and Dayna DaynaPORT NuBus cards: register byte lane is hardcoded
>Confidential: no
>Severity: serious
>Priority: medium
>Responsible: kern-bug-people
>State: open
>Class: sw-bug
>Submitter-Id: net
>Arrival-Date: Tue Sep 08 10:25:00 +0000 2026
>Originator: Ray Tran
>Release: NetBSD 11.0; the patch applies unchanged to -current (11.99.8)
>Organization:
Diamond Creek Digital
>Environment:
Hardware: Apple Macintosh Centris 650 w/ 68MB RAM and Macintosh IIcx w/ 20MB RAM
uname -a:
NetBSD 11.0 (Q650WSMUX) #22: Mon Sep 7 10:03:36 UTC 2026
>Description:
ae(4) cannot drive Kinetics-derived NuBus ethernet cards (Kinetics EtherPort and
the Dayna DaynaPORT E/II, which uses the same design). Attachment either fails
outright with "unsupported Dayna hardware", or the card attaches and then passes
no traffic.
There are two distinct problems.
1. Register byte lane is hardcoded.
if_ae_nubus.c assumes the DP8390 registers appear on the byte lane given by
KE_REG_OFFSET. That offset describes the EtherPort SE/30. On the EtherPort IIN
and on the DaynaPORT E/II the registers appear on a different lane, and on
different host bridges the same card can present on a different lane again.
With the wrong lane the driver reads and writes what it believes are DP8390
registers but which are in fact unrelated addresses. The chip is never
configured, so the card is silent.
Observed directly: the same DaynaPORT E/II card, moved from a Macintosh IIcx to
a Centris 650, presents its registers on a different lane in each machine.
2. Packet buffer access is interleaved.
Both card families interleave buffer memory rather than presenting it linearly.
The existing linear accessors corrupt packets in both directions.
>How-To-Repeat:
Fit a Dayna DaynaPORT E/II or Kinetics EtherPort to any NuBus Macintosh running
NetBSD/mac68k and boot a kernel with "ae* at nubus?". The card is either
rejected at attach or attaches without passing traffic.
>Fix:
The attached patch:
* Probes all four byte lanes at attach time and selects the lane that responds
with plausible DP8390 command and interrupt-status register values, instead of
trusting a compile-time constant. If no lane responds it falls back to the old
behaviour and says so, so the change cannot make a previously working card
worse.
* Adds interleaved buffer accessors (ae_il_write_mbuf, ae_il_read_hdr,
ae_il_ring_copy) and selects them for Kinetics-derived cards.
Diagnostic output follows existing conventions: the selected lane is reported
with aprint_verbose_dev(), the full four-lane probe table with aprint_debug_dev()
so it only appears under boot -x, and failure to find any responding lane with
aprint_error_dev() so it is always visible.
The runtime probe is what makes this portable. The patch was developed on a IIcx
and moved to a Centris 650 with no modification; the probe selected the correct,
different lane on each machine without intervention.
Verified on both machines: card attaches, reports its MAC address, and passes
traffic. Sustained HTTP transfer over the DaynaPORT E/II on the Centris 650
measured 218 KiB/s, flat across the transfer with no stalls or retries.
The Centris 650 runs it under NetBSD 11.0. The patch applies to
-current with no fuzz and builds there.
Applies with "patch -p1" from the top of usr/src; verified with -F0
(no fuzz) against NetBSD-current 11.99.8 (20260830003849Z) and 11.0.
--- a/sys/arch/mac68k/dev/if_ae.c
+++ b/sys/arch/mac68k/dev/if_ae.c
@@ -183,3 +183,159 @@
}
return (totlen);
}
+
+/*
+ * Interleaved buffer access for Kinetics EtherPort and Dayna DaynaPORT
+ * NuBus cards. Each 16-bit word of packet buffer is followed by 16
+ * bits that are not driven, so logical byte N lies at offset N << 1.
+ * Byte-wide writes are rejected, so partial words need
+ * read-modify-write.
+ *
+ * bus_space_{read,write}_2() do not swap on mac68k, so the high half of
+ * each word holds the earlier byte on the wire.
+ */
+static void
+ae_il_readmem(struct dp8390_softc *sc, int from, uint8_t *to, int count)
+{
+ bus_space_tag_t t = sc->sc_buft;
+ bus_space_handle_t h = sc->sc_bufh;
+ bus_size_t off = (bus_size_t)from << 1;
+ uint16_t w;
+
+ if (off & 2) { /* odd logical start */
+ w = bus_space_read_2(t, h, off - 2);
+ *to++ = (uint8_t)w;
+ off += 2;
+ count--;
+ }
+ while (count >= 2) {
+ w = bus_space_read_2(t, h, off);
+ *to++ = (uint8_t)(w >> 8);
+ *to++ = (uint8_t)w;
+ off += 4; /* skip the dead word */
+ count -= 2;
+ }
+ if (count > 0) {
+ w = bus_space_read_2(t, h, off);
+ *to = (uint8_t)(w >> 8);
+ }
+}
+
+static void
+ae_il_writemem(struct dp8390_softc *sc, const uint8_t *from, int to, int count)
+{
+ bus_space_tag_t t = sc->sc_buft;
+ bus_space_handle_t h = sc->sc_bufh;
+ bus_size_t off = (bus_size_t)to << 1;
+ uint16_t w;
+
+ if (off & 2) { /* odd logical start */
+ w = bus_space_read_2(t, h, off - 2);
+ w = (w & 0xff00) | *from++;
+ bus_space_write_2(t, h, off - 2, w);
+ off += 2;
+ count--;
+ }
+ while (count >= 2) {
+ w = ((uint16_t)from[0] << 8) | from[1];
+ bus_space_write_2(t, h, off, w);
+ from += 2;
+ off += 4;
+ count -= 2;
+ }
+ if (count > 0) { /* no byte writes: read-modify-write */
+ w = bus_space_read_2(t, h, off);
+ w = (w & 0x00ff) | ((uint16_t)*from << 8);
+ bus_space_write_2(t, h, off, w);
+ }
+}
+
+/*
+ * Zero the buffer and check that it reads back clear, as ae_test_mem()
+ * does for the linear cards. Covers the whole buffer, so the
+ * interleaved accessors are exercised over their full range.
+ */
+int
+ae_il_test_mem(struct dp8390_softc *sc)
+{
+ uint8_t zero[64], back[64];
+ int i, n;
+
+ memset(zero, 0, sizeof(zero));
+
+ for (i = 0; i < sc->mem_size; i += n) {
+ n = sc->mem_size - i;
+ if (n > (int)sizeof(zero))
+ n = (int)sizeof(zero);
+ ae_il_writemem(sc, zero, sc->mem_start + i, n);
+ }
+
+ for (i = 0; i < sc->mem_size; i += n) {
+ n = sc->mem_size - i;
+ if (n > (int)sizeof(back))
+ n = (int)sizeof(back);
+ ae_il_readmem(sc, sc->mem_start + i, back, n);
+ if (memcmp(zero, back, n) != 0) {
+ aprint_error_dev(sc->sc_dev,
+ "failed to clear NIC buffer at offset %x\n",
+ sc->mem_start + i);
+ return 1;
+ }
+ }
+ return 0;
+}
+
+void
+ae_il_read_hdr(struct dp8390_softc *sc, int src, struct dp8390_ring *hdrp)
+{
+ uint8_t hdr[4];
+
+ ae_il_readmem(sc, src, hdr, sizeof(hdr));
+ hdrp->rsr = hdr[0];
+ hdrp->next_packet = hdr[1];
+ hdrp->count = hdr[2] | (hdr[3] << 8);
+}
+
+int
+ae_il_ring_copy(struct dp8390_softc *sc, int src, void *dst, u_short amount)
+{
+ u_short tmp;
+
+ if (src + amount > sc->mem_end) { /* wraps in the ring */
+ tmp = sc->mem_end - src;
+ ae_il_readmem(sc, src, dst, tmp);
+ amount -= tmp;
+ src = sc->mem_ring;
+ dst = (char *)dst + tmp;
+ }
+ ae_il_readmem(sc, src, dst, amount);
+ return src + amount;
+}
+
+int
+ae_il_write_mbuf(struct dp8390_softc *sc, struct mbuf *m, int buf)
+{
+ int totlen = 0;
+
+ for (; m != NULL; m = m->m_next) {
+ if (m->m_len == 0)
+ continue;
+ ae_il_writemem(sc, mtod(m, uint8_t *), buf, m->m_len);
+ buf += m->m_len;
+ totlen += m->m_len;
+ }
+
+ /*
+ * Pad runts to the minimum frame size, as dp8390_write_mbuf() and
+ * ae_write_mbuf() do. ARP requests are 42 bytes and go unanswered
+ * without this.
+ */
+ if (totlen < ETHER_MIN_LEN - ETHER_CRC_LEN) {
+ static const uint8_t pad[ETHER_MIN_LEN - ETHER_CRC_LEN];
+
+ ae_il_writemem(sc, pad, buf,
+ ETHER_MIN_LEN - ETHER_CRC_LEN - totlen);
+ totlen = ETHER_MIN_LEN - ETHER_CRC_LEN;
+ }
+ return totlen;
+}
--- a/sys/arch/mac68k/dev/if_aevar.h
+++ b/sys/arch/mac68k/dev/if_aevar.h
@@ -16,3 +16,9 @@
int ae_size_card_memory(bus_space_tag_t, bus_space_handle_t, int);
int ae_test_mem(struct dp8390_softc *);
int ae_write_mbuf(struct dp8390_softc *, struct mbuf *, int);
+
+/* Interleaved buffer access for Kinetics EtherPort and Dayna DaynaPORT */
+int ae_il_test_mem(struct dp8390_softc *);
+void ae_il_read_hdr(struct dp8390_softc *, int, struct dp8390_ring *);
+int ae_il_ring_copy(struct dp8390_softc *, int, void *, u_short);
+int ae_il_write_mbuf(struct dp8390_softc *, struct mbuf *, int);
--- a/sys/arch/mac68k/nubus/if_ae_nubus.c
+++ b/sys/arch/mac68k/nubus/if_ae_nubus.c
@@ -74,8 +74,17 @@
#endif
void ae_nubus_intr(void *);
+static void ae_il_watchdog(struct ifnet *);
-CFATTACH_DECL_NEW(ae_nubus, sizeof(struct dp8390_softc),
+struct ae_nubus_softc {
+ struct dp8390_softc sc_dp; /* must be first */
+ int sc_slot; /* NuBus slot, to detach the intr */
+ int sc_lane; /* register byte lane the probe chose */
+ uint8_t sc_probe[4][4]; /* per-lane probe results, diagnosis */
+ u_int sc_quiet; /* consecutive idle interrupts */
+};
+
+CFATTACH_DECL_NEW(ae_nubus, sizeof(struct ae_nubus_softc),
ae_nubus_match, ae_nubus_attach, NULL, NULL);
static int
@@ -121,14 +130,15 @@
static void
ae_nubus_attach(device_t parent, device_t self, void *aux)
{
- struct dp8390_softc *sc = device_private(self);
+ struct ae_nubus_softc *nsc = device_private(self);
+ struct dp8390_softc *sc = &nsc->sc_dp;
struct nubus_attach_args *na = aux;
#ifdef DEBUG
struct ifnet *ifp = &sc->sc_ec.ec_if;
#endif
bus_space_tag_t bst;
bus_space_handle_t bsh;
- int i, success;
+ int i, success, vendor, mem_span, reg_lane;
const char *cardtype;
sc->sc_dev = self;
@@ -148,10 +158,12 @@
sc->mem_start = 0;
sc->mem_size = 0;
+ reg_lane = -1;
success = 0;
- switch (ae_nb_card_vendor(bst, bsh, na)) {
+ vendor = ae_nb_card_vendor(bst, bsh, na);
+ switch (vendor) {
case DP8390_VENDOR_APPLE: /* Apple-compatible cards */
case DP8390_VENDOR_ASANTE:
/* Map register offsets */
@@ -298,21 +310,126 @@
for (i = 0; i < 16; i++) /* normal order, longword aligned */
sc->sc_reg_map[i] = i << 2;
- if (bus_space_subregion(bst, bsh,
+ for (i = 0; i < 4; i++) {
+ const uint8_t c0 = ED_CR_PAGE_0 | ED_CR_RD2 | ED_CR_STP;
+ const uint8_t c1 = ED_CR_PAGE_1 | ED_CR_RD2 | ED_CR_STP;
+ uint8_t r0, r1, x0, x1;
+
+ if (bus_space_subregion(bst, bsh,
+ (KE_REG_OFFSET & ~3) + i, AE_REG_SIZE,
+ &sc->sc_regh))
+ continue;
+
+ bus_space_write_1(bst, sc->sc_regh, 0, c0);
+ DELAY(100);
+ r0 = bus_space_read_1(bst, sc->sc_regh, 0);
+ x0 = bus_space_read_1(bst, sc->sc_regh,
+ sc->sc_reg_map[ED_P0_ISR]);
+
+ bus_space_write_1(bst, sc->sc_regh, 0, c1);
+ DELAY(100);
+ r1 = bus_space_read_1(bst, sc->sc_regh, 0);
+ x1 = bus_space_read_1(bst, sc->sc_regh,
+ sc->sc_reg_map[ED_P0_ISR]);
+
+ nsc->sc_probe[i][0] = r0;
+ nsc->sc_probe[i][1] = x0;
+ nsc->sc_probe[i][2] = r1;
+ nsc->sc_probe[i][3] = x1;
+
+ if ((r0 & (ED_CR_PS1 | ED_CR_PS0)) == ED_CR_PAGE_0 &&
+ (r1 & (ED_CR_PS1 | ED_CR_PS0)) == ED_CR_PAGE_1 &&
+ (r0 & (ED_CR_RD2 | ED_CR_STP)) ==
+ (ED_CR_RD2 | ED_CR_STP)) {
+ reg_lane = i;
+ break;
+ }
+ }
+ if (reg_lane >= 0) {
+ bus_space_write_1(bst, sc->sc_regh, 0,
+ ED_CR_PAGE_0 | ED_CR_RD2 | ED_CR_STP);
+ DELAY(100);
+ } else if (bus_space_subregion(bst, bsh,
KE_REG_OFFSET, AE_REG_SIZE, &sc->sc_regh)) {
aprint_error(": failed to map register space\n");
break;
}
- if ((sc->mem_size = ae_size_card_memory(bst, bsh,
+
+ /*
+ * Stop the chip before touching buffer memory. mac68k enters
+ * through the Mac OS Booter, so NuBus cards are not reset on
+ * the way in. A card left running by the Mac OS driver still
+ * owns its local bus, and the first host write to shared RAM
+ * below does not complete. Mask and acknowledge interrupts
+ * once it is stopped.
+ */
+ NIC_BARRIER(sc->sc_regt, sc->sc_regh);
+ NIC_PUT(sc->sc_regt, sc->sc_regh, ED_P0_CR,
+ ED_CR_PAGE_0 | ED_CR_RD2 | ED_CR_STP);
+ NIC_BARRIER(sc->sc_regt, sc->sc_regh);
+ for (i = 5000; i > 0; i--) {
+ if (NIC_GET(sc->sc_regt, sc->sc_regh, ED_P0_ISR) &
+ ED_ISR_RST)
+ break;
+ DELAY(1);
+ }
+ NIC_PUT(sc->sc_regt, sc->sc_regh, ED_P0_IMR, 0);
+ NIC_PUT(sc->sc_regt, sc->sc_regh, ED_P0_ISR, 0xff);
+ NIC_BARRIER(sc->sc_regt, sc->sc_regh);
+
+ if ((mem_span = ae_size_card_memory(bst, bsh,
KE_DATA_OFFSET)) == 0) {
aprint_error(": failed to determine size of RAM.\n");
break;
}
+ /*
+ * ae_size_card_memory() writes at fixed address strides, so
+ * on an interleaved card it measures address space rather
+ * than RAM, reporting 16KB for an 8KB buffer.
+ *
+ * Map the whole span, which is what the accessors walk, but
+ * give dp8390 the real size. dp8390_config() derives
+ * rec_page_stop from mem_size, and a doubled value puts the
+ * receive ring past the end of physical memory.
+ */
+ sc->mem_size = mem_span / 2;
if (bus_space_subregion(bst, bsh,
- KE_DATA_OFFSET, sc->mem_size, &sc->sc_bufh)) {
+ KE_DATA_OFFSET, mem_span, &sc->sc_bufh)) {
aprint_error(": failed to map register space\n");
break;
}
+
if (ae_nb_get_enaddr(bst, bsh, na, sc->sc_enaddr)) {
aprint_error(": can't find MAC address\n");
break;
@@ -362,6 +479,18 @@
*/
sc->test_mem = ae_test_mem;
sc->write_mbuf = ae_write_mbuf;
+
+ /*
+ * Kinetics EtherPort and Dayna DaynaPORT interleave the packet
+ * buffer, one live 16-bit word per four bytes, and reject
+ * byte-wide writes. The linear defaults cannot drive that.
+ */
+ if (vendor == DP8390_VENDOR_KINETICS) {
+ sc->test_mem = ae_il_test_mem;
+ sc->write_mbuf = ae_il_write_mbuf;
+ sc->read_hdr = ae_il_read_hdr;
+ sc->ring_copy = ae_il_ring_copy;
+ }
#ifdef DEBUG
ifp->if_watchdog = ae_nb_watchdog; /* Override watchdog */
#endif
@@ -372,21 +501,116 @@
aprint_normal(": %s, %dKB memory\n", cardtype, sc->mem_size / 1024);
+ if (vendor == DP8390_VENDOR_KINETICS) {
+ if (reg_lane >= 0)
+ aprint_verbose_dev(self, "registers on byte lane %d\n",
+ reg_lane);
+ else
+ aprint_error_dev(self, "no register lane responded;"
+ " falling back to lane %d\n", KE_REG_OFFSET & 3);
+ /*
+ * The full probe table is only of interest when the lane
+ * choice itself is in question, so keep it behind boot -x.
+ */
+ for (i = 0; i < 4; i++)
+ aprint_debug_dev(self, " lane %d: cr %02x/%02x"
+ " isr %02x/%02x%s\n", i,
+ nsc->sc_probe[i][0], nsc->sc_probe[i][2],
+ nsc->sc_probe[i][1], nsc->sc_probe[i][3],
+ i == reg_lane ? " <- chosen" : "");
+ }
+
if (dp8390_config(sc)) {
bus_space_unmap(bst, bsh, NBMEMSIZE);
return;
}
+ /* dp8390_config() fills in if_watchdog only when it is NULL. */
+ if (vendor == DP8390_VENDOR_KINETICS)
+ sc->sc_ec.ec_if.if_watchdog = ae_il_watchdog;
+
/* make sure interrupts are vectored to us */
- add_nubus_intr(na->slot, ae_nubus_intr, sc);
+ nsc->sc_lane = reg_lane;
+ nsc->sc_slot = na->slot;
+ nsc->sc_quiet = 0;
+ add_nubus_intr(na->slot, ae_nubus_intr, nsc);
+}
+
+/*
+ * As dp8390_watchdog(), but report the chip state with the timeout. A
+ * clear TSR means the chip never transmitted; ED_TSR_PTX means it did
+ * and the completion interrupt was lost.
+ */
+static void
+ae_il_watchdog(struct ifnet *ifp)
+{
+ struct dp8390_softc *sc = ifp->if_softc;
+ uint8_t cr, isr, tsr;
+ int st;
+
+ NIC_BARRIER(sc->sc_regt, sc->sc_regh);
+ cr = NIC_GET(sc->sc_regt, sc->sc_regh, ED_P0_CR);
+ isr = NIC_GET(sc->sc_regt, sc->sc_regh, ED_P0_ISR);
+ tsr = NIC_GET(sc->sc_regt, sc->sc_regh, ED_P0_TSR);
+
+ /*
+ * A transmit that completed (ED_TSR_PTX) whose interrupt is still
+ * pending in ISR means the slot interrupt was lost, not that the
+ * chip failed. The VIA code is known to drop them; see the kludge
+ * in ae_nb_watchdog(). Run the handler by hand and carry on rather
+ * than resetting a working card.
+ */
+ if ((tsr & ED_TSR_PTX) != 0 &&
+ (isr & (ED_ISR_PTX | ED_ISR_RXE | ED_ISR_PRX)) != 0) {
+ log(LOG_ERR, "%s: lost interrupt: cr %02x isr %02x tsr %02x\n",
+ device_xname(sc->sc_dev), cr, isr, tsr);
+ st = splnet();
+ (void)dp8390_intr(sc);
+ splx(st);
+ return;
+ }
+
+ log(LOG_ERR, "%s: device timeout: cr %02x isr %02x tsr %02x\n",
+ device_xname(sc->sc_dev), cr, isr, tsr);
+ if_statinc(ifp, if_oerrors);
+
+ dp8390_reset(sc);
}
void
ae_nubus_intr(void *arg)
{
- struct dp8390_softc *sc = arg;
+ struct ae_nubus_softc *nsc = arg;
+ struct dp8390_softc *sc = &nsc->sc_dp;
+
+ if (dp8390_intr(sc) != 0) {
+ nsc->sc_quiet = 0;
+ return;
+ }
+
+ /*
+ * dp8390_intr() found nothing to do, but the slot is still
+ * asserting. via2_nubus_intr() polls vBufA until every slot in
+ * nubus_intr_mask drops its line, so it calls us again at once and
+ * the machine looks dead while it spins here.
+ *
+ * Only consecutive unproductive interrupts count: a card doing real
+ * work generates a great many of them legitimately, and counting
+ * those would unhook a perfectly healthy interface.
+ */
+ if (++nsc->sc_quiet < 1000)
+ return;
- (void)dp8390_intr(sc);
+ aprint_error_dev(sc->sc_dev,
+ "%u interrupts with nothing to service (isr %02x cr %02x);"
+ " lane %d, detaching slot %d\n", nsc->sc_quiet,
+ NIC_GET(sc->sc_regt, sc->sc_regh, ED_P0_ISR),
+ NIC_GET(sc->sc_regt, sc->sc_regh, ED_P0_CR),
+ nsc->sc_lane, nsc->sc_slot);
+
+ NIC_PUT(sc->sc_regt, sc->sc_regh, ED_P0_IMR, 0);
+ add_nubus_intr(nsc->sc_slot, NULL, NULL);
+ nsc->sc_quiet = 0;
}
static int
Home |
Main Index |
Thread Index |
Old Index