WonderMCA: Gone before we looked, the Model 57’s early /CD-SETUP and the POS register that never answered


The card that vanished from setup

WonderMCA had been enumerating cleanly across the PS/2 matrix for weeks. POST passed, the option ROM checksummed, the adapter ID showed up in the reference-diskette setup. Then I sat down at the IBM PS/2 Model 57 — the 8557, a 386SX desktop — booted it, and the card was simply not there. No adapter, no slot, nothing. POST didn’t even complain; from the chassis’s point of view there was no WonderMCA in the bus at all.

That’s a particular kind of frustrating. A crash gives you a code. A disappearance gives you nothing. The card was clearly alive — the screenpad lit up, Core 0 was running, the UART banner printed — but the one thing it had to do during the first second of POST, answer the POS scan, it wasn’t doing.

What the card is supposed to do

A quick refresher on Micro Channel’s Programmable Option Select. When a PS/2 powers on, the planar walks the slots one at a time. For each slot it asserts that slot’s dedicated /CD SETUP line and reads the eight POS bytes at I/O 0x1000x107. Adapter ID, enable bit, ROM base — all of it comes back over those eight reads, but only while /CD SETUP is asserted to that slot. Outside setup, 0x1000x107 belongs to nobody and reads back as float.

So WonderMCA’s job is dead simple in principle: when the chassis reads 0x1000x107 and our /CD SETUP is asserted, put the right POS byte on the data bus. Miss that, and the chassis decides the slot is empty.

On WonderMCA the bus is handled entirely by Core 1 of the RP2350B, running a tight hand-written loop out of SCRATCH_X. /CD SETUP arrives on GPIO 45, buffered (and inverted) by a U8 SN74LVC240, so at the pin it reads active HIGH = asserted. The POS gate was, on the face of it, trivial:

if (io_device == DEV_MCA_POS) {
    if (gpio_get(PIN_CD_SETUP))            <em>// asserted?</em>
        dev_mca_pos_ior(addr_lo, &MCA_Data);
    else
        MCA_Read = 0;                      <em>// not our setup cycle</em>
}

Read the pin. If high, answer. What could go wrong?

The first wrong turn: blame the UART

The very first thing I noticed looked damning. On WonderMCA, GPIO 45 does double duty: it’s PIN_CD_SETUP, but the build also had it declared as PICO_DEFAULT_UART_RX_PIN=45. The SDK’s stdio_uart requires a real UART-RX-capable pin, and there’s no spare GPIO on this board, so the console RX had been parked on the CD-SETUP pin.

That smelled like the whole bug. stdio_init_all() configures the default UART pins — if it grabbed GPIO 45 as UART RX, gpio_get() on the POS gate would be reading a pin owned by the wrong peripheral. And worse, stdio_init_all() was being called three times, one of them after multicore_launch_core1() — i.e. after Core 1 had already reclaimed GPIO 45 as a plain input.

I fixed all that, and it needed fixing: stdio now initialises exactly once, before the launch, Core 1 reclaims GPIO 45 as SIO with a pull-down, and the redundant post-launch stdio_init_all() is skipped on WonderMCA. I even left a rule in CMakeLists.txt: never add a post-launch stdio_init_all() on this board.

And the card still wasn’t detected.

That’s the part worth dwelling on, because it’s a trap I fall into constantly: the first plausible cause that is genuinely wrong and does deserve a fix is not necessarily the cause. The UART collision was real. It just wasn’t why the Model 57 couldn’t see the card.

Instrumenting the gate

When a theory dies, stop guessing and count things. I added three counters to the POS path and a rate-limited heartbeat from Core 0:

  • pos_ior_count — POS reads we actually served
  • pos_iow_count — POS writes served
  • pos_cd_low_count — 0x1000x107 cycles that reached Core 1 but were gated off because the CD-SETUP read came back LOW

The whole point was to split three failure modes that look identical from the outside:

  • ior > 0 → POST is reading our POS regs, gate open, we’re fine.
  • cd_low > 0 with ior = 0 → cycles reach us, but the gate is shut.
  • all zero → the cycle never even arrives — a decode/CPLD problem, not the gate.

I flashed it, booted the Model 57, and watched the serial console:

[mca_pos] ior=1 iow=0 cd_low=345 cd_setup=0
[mca_pos] ior=1 iow=0 cd_low=365 cd_setup=0
[mca_pos] ior=1 iow=0 cd_low=380 cd_setup=0
[mca_pos] ior=1 iow=0 cd_low=407 cd_setup=0

There it was, in three numbers.

The cycles were arriving — cd_low climbing into the hundreds means the chassis was hammering 0x1000x107 and the address decode was routing every one of them to our POS handler. So the decode was fine. But gpio_get(PIN_CD_SETUP) read LOW on every single one. We served exactly one POS read, ever (ior=1, almost certainly a fluke), and rejected the other 345+.

The gate was slamming the door on every POS read the Model 57 sent us.

“It’s inverted” — and then the real timing

My first reaction was polarity. If the buffer wasn’t inverting the way I thought, asserted would read LOW and the gate would be backwards. I nearly flipped it. But the comments, the pull-down, and a quick check all said the same thing: U8 does invert, asserted is HIGH at GPIO 45, and the gate sense was correct.

So I put the logic analyzer on the actual /CD SETUP line and the GPIO, and that’s when the Model 57 showed its hand:

On the Model 57, /CD SETUP asserts very early — before /ADL — and deasserts in the middle of /CMD.

Read that again, because it’s the whole bug. /CD SETUP is not held for the duration of the command phase on this chassis. It goes active before the address strobe and is already gone halfway through the command.

Now look at when our gate read it. The Core 1 loop polls for /ADL, latches the address the instant /ADL fires (call that T=0), then runs through a FIFO wait, the IO_Index_T lookup, the A_AB mux restore, and finally branches into the C-level IOR handler — where gpio_get(PIN_CD_SETUP) lived. That read happens somewhere around T+20 cycles and beyond, deep into /CMD.

On a well-behaved chassis that holds /CD SETUP flat across the whole cycle, reading it at T+20 is fine. On the Model 57, by T+20 it had already deasserted. We were reading the pin a clean ~70 ns after the signal we cared about had vanished. The card wasn’t broken, the gate wasn’t backwards, the polarity was right. We were just looking too late.

The fix: catch it on the way past

The address is already captured early — the ASM grabs it right at /ADL. The answer was to grab /CD SETUP at the same moment, while it’s still asserted, instead of re-reading the live pin 70 ns later.

GPIO 45 sits in the RP2350’s high GPIO bank (32–47), which you read in one shot with mrc p0, #0, Rd, c0, c9. GPIO 45 is bit 13 of that word. So right in the I/O dispatch path, at roughly T+8 — early in /CMD, while /CD SETUP is still solidly asserted — I take a single high-bank snapshot and carry it out of the ASM:

"5:"
"   mrc  p0, #0, r0,      c0, c8   "   // address (low bank)      T+7
"   mrc  p0, #0, %[iohi], c0, c9   "   // RAW high-bank snapshot   T+8  <-- /CD SETUP still asserted
"   ubfx r0, r0, #DBASE, #12       "   // addr_lo

The C handler then just decodes the bit out of the frozen snapshot instead of touching the pin:

bool io_cd_setup = (io_hi >> (PIN_CD_SETUP - 32)) & 1u;  <em>// latched at T+8, not re-read mid-/CMD</em>
if (io_device == DEV_MCA_POS && io_cd_setup)
    dev_mca_pos_ior(addr_lo, &MCA_Data);

It’s a couple of instructions on the I/O path — which is CHRDY-extended, so there’s budget to spare — and it changes nothing about the address or data timing. It just samples the signal when it’s there.

Confirmation

Same console, next boot:

[mca_pos] ior=28 iow=9  cd_low=520 cd_setup=0
[mca_pos] ior=34 iow=11 cd_low=566 cd_setup=0
[mca_pos] ior=42 iow=38 cd_low=600 cd_setup=0

ior climbing in steps as the chassis walks our eight POS bytes — and iow climbing too, which is even better: that’s the BIOS not just reading our adapter ID but writing the POS option bytes, enabling the card and programming its ROM base. The Model 57 saw WonderMCA in the slot, configured it, and moved on. (cd_low keeps rising because it counts every 0x100 cycle for the other slots, where our /CD SETUP is correctly deasserted — that’s expected, not an error. And cd_setup=0 in the print is just the Core 0 heartbeat sampling the pin asynchronously, nowhere near our brief selection window — the ior/iow counters are the real measure.)

Card detected.

The same trap, two more signals

Once you’ve been bitten by “the signal was gone before the C code looked,” you start seeing it everywhere — and rightly so. Two more bus qualifiers live in that exact same high-bank snapshot, and both had the same latent risk:

  • /TC (terminal count, end-of-DMA) on GPIO 39 = bit 7. On the scope it asserts +10 ns after /ADL — so our T+8-ish snapshot catches it cleanly, while the old gpio_get(PIN_R_TC) deep in the DMA consume path was reading it ~90 ns later, right at its deassert edge. That late read was almost certainly part of why the real /TC EOT had always been flaky enough that we’d fallen back to a byte-count.
  • DMA grant on GPIO 38 = bit 6. Grant can drop as the transfer ends, so a late read risks missing the capture on exactly the last (/TC) byte — the one that matters most.

Both now come from the same mrc … c9 word, sampled the instant they’re valid. One register read, three signals, all frozen at the moment the cycle begins instead of whenever the C handler happens to get around to them.

What I took away

  • A signal you can read is not a signal that’s still there. On a bus, when you sample is as much a part of correctness as what you sample. The Model 57 holds /CD SETUP for a narrower window than the chassis I’d been developing against, and 70 ns of handler latency was the whole difference between a card and an empty slot.
  • Latch at the edge, decode at leisure. The address was already captured early; the qualifiers should have been too. Grabbing one high-bank GPIO word the moment /ADL fires, and pulling the bits out in C later, costs almost nothing and removes an entire class of “deasserted-before-we-looked” bugs.
  • A necessary fix is not always the fix. The UART-on-GPIO-45 collision was real and got cleaned up — but it wasn’t the reason. Counting beats guessing: three counters turned an invisible disappearance into “the cycle arrives, the pin reads low, every time,” and that sentence wrote the fix.

Different MCA chassis assert the same signals with surprisingly different timing, all of it legal under the IBM spec. WonderMCA now samples the bus qualifiers when they’re guaranteed valid, not when it’s convenient — and the Model 57, at last, knows the card is there.

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top