Sixth in a series on building a firmware-free VideoBIOS for RISC-V. In Part 5 a cold GK208 woke with no x86 anywhere in the machine — live chip ID, memory controller, privilege ring — and the monitor still showed black. This post is the modeset I wrote to light it, and the ordering that finally locked the pixel clock.
The story so far. I abandoned the card's x86 firmware and replayed a captured cold-init sequence from C on the RISC-V CPU. The silicon came up firmware-free. The screen did not, because waking the chip and lighting the display are different jobs, and the second one had killed the old road too.
One bit, again
The black screen from Part 4 had a single cause, and it had not changed: the pixel
clock never locked. VPLL0's control register at 0x614140 read
0x02010002 on my card and 0x02030002 on a working one —
bit 17, the lock bit, clear. On the x86 road I could blame the firmware for it, since
it sat on a timed gate waiting for a warm machine mine was not. Now there was no
firmware. The sequence going into the display engine was mine, and so was the unlit bit
— days into the June 2026 rewrite, with the cold card freshly awake, nothing
stood between it and a lit screen but code I had written.
VGA registers light nothing
I started where the capture pointed: program the legacy CRTC and attribute registers for mode 3, set the raster geometry, upload the 8×16 font. On these cards that scans out nothing. The VGA path Kepler carries is vestigial — present for compatibility, wired to nothing that reaches the DAC on its own. The display is driven by EVO, the class-based display channel: you allocate a core channel and push methods into it, and no amount of poking CRTC registers coaxes a raster to life. I had been aiming at the wrong altitude.
A modeset in the open
So I read nouveau again, the same way as before — as a specification, not code to ship. An EVO modeset is a sequence of methods on the core channel: bind a context DMA for the framebuffer, configure the head with the raster timings and the 28.322 MHz pixel clock, attach an output resource, then issue one UPDATE that makes the whole assembled state take at once. I wrote that in my own C, against the golden register values I had read off the working card. The live output on this board is DAC-1, not DAC-0 — a fact that had already cost me an evening in Part 4 — and this time I attached the right one from the start.
The cargo cult I left behind
In Part 4 I had tried to move the display supervisor by faking the signals it waits on, driving the advance myself so the firmware would go and program its clock. That is cargo-cult modesetting: reproducing the motions the state machine expects without establishing the state those motions are supposed to leave behind. It went through some of the phases and never locked. The coherent EVO sequence is the opposite. It puts the engine into the exact state the supervisor is checking for, so the supervisor advances because the modeset is real, not because I mimed the parts it could see.
The thing that locked the clock
Even with the method sequence right, the VPLL stayed unlocked until one more thing happened: the output was attached to the head. The lock is not something I could set by writing the PLL register directly — it follows from binding the DAC to the head that drives it. My lock check after programming the coefficients is deliberately short, because I learned it will never pass this early, and the comment says why:
/* on this board the VPLL does NOT lock until the DAC is attached
* later in the supervisor handshake, so a long wait here always caps. */
while (!(rd(0x614140) & 0x00020000u) && ++spins < 400)
usleep(5);
That 0x00020000 is bit 17, the same lock bit that read clear in Part 4.
Attach the DAC, and it sets. The ordering has the shape of a rain dance, an arcane
sequence performed in a fixed order to placate the hardware, and for a while I treated
it as one. It is not voodoo. The clock locks because the output path the PLL feeds is
finally whole, and calling that magic would only mean I had not yet understood it.
First light
I ran the cold init from Part 5, then the modeset, on a GK208 that had never been
POSTed in its life. 0x614140 read 0x02030002 — the lock
bit set, the same value the working card shows at a login screen. The monitor recognized
the signal and, in its own on-screen menu, read the mode back to me: 720×400 at
70 Hz, analog — the exact timing I had programmed. There was no x86 running
anywhere, no emulator in the binary, no firmware underneath: a RISC-V CPU had taken a
stone-cold card to a live raster on its own.
A signal, not a picture
What lit was the pipe, not a picture I had chosen — the display engine scanning out a framebuffer whose contents I did not yet control. That is the honest size of it: the gate that had stopped both roads was open, and the clock the firmware could never lock on my machine locked on the first coherent modeset of my own. Putting content I choose into that raster — text, from a character generator of my own, with no VGA text mode to lean on — is a different problem, and it is the next post.
Next: First Pixel — Content I Control, and the Character Generator.
No comments:
Post a Comment