VOGONS


First post, by Tevian

User metadata
Rank Member
Rank
Member

I decided to post a fresh entry for this for the vibe coding, but this is a continuation of my search for a firmware solution to the fixed resolutions on the Allen Bradley 6189-1CPU233 Pentium SBC that had no BIOS option to change. -->NEED BIOS HELP Rockwell Automation 140420-030 Allen Bradley 6189-1CPU233 CPU Card SBC (SOLVED)

You can read the other post, but I decided to make this thread since GPT helped me vibe code an 'INT 19h' injection directly into the VGA OPROM! All in, it took me about 8 hours of vibe coding with my limited knowledge of code, BIOS structure, use of Phoenix Tool, and an external flash programmer. Many tries with no post, failed boot, successful boot with no screen activity, etc...

I was pleased to eventually be suggested a simple COM file in the other post 'CT.COM' which reverts the CT65550 display chip back to CRT mode from DOS. This COM file is extremely small, 8 bytes (B8 51 5F B3 00 CD 10 C3), but flips the CT65550 registers back to CRT mode vs the LCD default in the BIOS.

Here's GPT's explanation of what that does.

--B8 51 5F B3 00 CD 10 C3--

mov ax,5F51h  -> B8 51 5F
mov bl,00h -> B3 00
int 10h -> CD 10
ret -> C3
; ---------------------------------------------------------
; ct_force_crt_only:
; Forces CRT-only via C&T INT 10h function.
; Clobbers: AX, BL, flags. (Preserve more if your caller needs it.)
; Returns: nothing (caller-saved).
; Encodes to: B8 51 5F B3 00 CD 10 C3
; ---------------------------------------------------------

ct_force_crt_only proc near
mov ax, 5F51h ; AH=5F, AL=51h -> Display Target Select
mov bl, 00h ; BL=00 -> CRT-only
int 10h ; Call video BIOS (C&T handler)
ret
ct_force_crt_only endp

A very brief summary of the fix is this:

First, unpacked the Phoenix Bios into its modules. In my case, Phoenix Tool 2.56 was used. Find the VGA OPROM; in my case, it was OPROM0.ROM as OPROM1.ROM was the PXE Ethernet ROM. There might be a few different ways to do this, but I have limited knowledge and allowed GPT to hold my hand. Tried placing a code stub at the end of the ROM, but this didn't work at first (haven't gone back to try now that I know a bit more), and instead crafted an 'INT19h' one-time shot that's called at the end of the ROM. We had to find enough space in the ROM file to do this, as the padding at the end was only 31 bytes! We found 128 bytes of space at 0x0E3A through 0x0EB9. First attempts to add code failed to repack the BIOS because the ROM file became too large, by 61 bytes! So, GPT suggested finding and removing string text to regain space. There was string text near the top of the ROM file about the VGA chip and copyright notice... So, gone. 😜

We made these changes: --WORKING SOLUTION--

Removed some string text:
Modified left/original right.

The attachment ROM_DIFF_0.jpg is no longer available

Added the HEX to the largest open space in the file starting at 0x0E3A.

INT 19h handler @ 0x0E3A

B8 51 5F B3 00 CD 10 B8 40 00 8E C0 26 FF 2E FC 00

Installer @ 0x0E4B

1E 06 FA 31 C0 8E C0 26 8B 1E 64 00 26 8B 0E 66 00
B8 40 00 8E D8 89 1E FC 00 89 0E FE 00
B8 00 C0 26 89 06 66 00
B8 3A 0E 26 89 06 64 00
FB 07 1F 68 D8 9F C3
The attachment ROM_DIFF_1.jpg is no longer available

Change last E8 call to jump to our installer code at 0x0E4B and adjust 0x9999 to reflect the proper checksum. '64' in my case.

Call site @ 0x9FCF

E8 79 6E
The attachment ROM_DIFF_2.jpg is no longer available

Here is the patched BIOS for the Rockwell Automation 140420-030 Allen Bradley 6189-1CPU233 SBC, but it should work in all the socket 7 boards!

The attachment AB 6189-1CPU233 (0.51 CRT).zip is no longer available

Here's proof! The modified BIOS is able to run Quake at 800x600 (would normally be offset with a fixed resolution of 640 x 480), without needing to run CT.COM from the CLI first.

The attachment QUAKE.jpg is no longer available

Like I said, this took many hours of back and forth with the flash programmer, and my fingers were starting to hurt from taking the SBC out of the slot each time. But, I stuck with it and we found a solution!!! 😜😎

The chat session is lengthy, and I can't post a share link since it has sensitive data in it. But this is GPT's explanation of what those code snippets do.

Have not done extensive testing, but it appears to behave as if I ran the CT.COM command from DOS so I'm assuming this will allow resolution changes in Windows, for instance. YAY!! It's done!

GPT

(B8 51 5F B3 00 CD 10 B8 40 00 8E C0 26 FF 2E FC 00) --CODE ADDED
B8 51 5F      mov  ax,5F51h      ; AH=5Fh → C&T OEM func group
; AL=51h → Display Target Select
B3 00 mov bl,00h ; BL=00 → CRT-only
CD 10 int 10h ; call video BIOS → switch to CRT

B8 40 00 mov ax,0040h ; 0040:xxxx = BIOS Data Area (BDA)
8E C0 mov es,ax ; ES = 0040h

26 FF 2E FC 00 jmp far [es:00FCh]
; 26h = ES: segment override
; FF /5 = far JMP to a pointer in memory
; [ES:00FCh] holds the original INT 19h vector
; (offset at 00FCh, segment at 00FEh),
; which our installer stashed in the BDA.

### What it’s doing (in plain English)

* First three instructions = the same magic as `CT.COM`: tell the Chips & Technologies 65550 BIOS **“use CRT only”** via `INT 10h` (AX=5F51h, BL=00h).
* Then we **tail‑chain** to the **original INT 19h** (bootstrap loader) by jumping to the far pointer we saved in the BDA at `0040:00FC`.
Tail‑chaining (a `jmp far`), instead of calling and returning, keeps the **stack perfectly clean**; the original INT 19h will `iret` back into BIOS exactly as expected.

### Why this was the winning move

* It runs **late** (right before DOS hands off), so the CRT setting **sticks** and isn’t undone by earlier VGA init.
* No recursion or fragile register juggling—just do the CRT switch, then **jump** to the real loader. Simple and stable.

(1E 06 FA 31 C0 8E C0 26 8B 1E 64 00 26 8B 0E 66 00
B8 40 00 8E D8 89 1E FC 00 89 0E FE 00
B8 00 C0 26 89 06 66 00
B8 3A 0E 26 89 06 64 00
FB 07 1F 68 D8 9F C3) --CODE ADDED
1E              push ds
06 push es
FA cli ; don't let an interrupt fire mid‑edit
31 C0 xor ax,ax
8E C0 mov es,ax ; ES = 0000h (IVT segment)

26 8B 1E 64 00 mov bx,[es:0064] ; read current INT 19h offset (IVT: 0000:0064)
26 8B 0E 66 00 mov cx,[es:0066] ; read current INT 19h segment (IVT: 0000:0066)

INT 19h’s vector (offset:segment) lives in the IVT at 0000:0064/0066. We grab it so we can tail‑chain later.

B8 40 00        mov  ax,0040h
8E D8 mov ds,ax ; DS = 0040h (BDA)

89 1E FC 00 mov [00FCh],bx ; stash old INT 19h offset in BDA
89 0E FE 00 mov [00FEh],cx ; stash old INT 19h segment in BDA

We park the original INT 19h vector in the BIOS Data Area at 0040:00FC/00FE.
(Your handler later does jmp far [es:00FC] with es=0040h to chain to it.)

B8 00 C0        mov  ax,0C000h
26 89 06 66 00 mov [es:0066],ax ; IVT INT19.segment = C000h
B8 3A 0E mov ax,0E3Ah
26 89 06 64 00 mov [es:0064],ax ; IVT INT19.offset = 0E3Ah

We point INT 19h in the IVT to our handler at C000:0E3A (inside the VGA ROM).
From now on, when BIOS invokes INT 19h, it will run our code first.

FB              sti                 ; re‑enable interrupts
07 pop es
1F pop ds
68 D8 9F push 9FD8h ; return into ROM’s original path
C3 ret

Clean up segments, re‑enable interrupts, then return to the VGA ROM exactly where it was headed (we “push 9FD8; ret” to jump there without changing any earlier logic).

### In plain English

* Save DS/ES, block interrupts.
* Read the **current** INT 19h vector from the interrupt table.
* Save that original vector in the **BDA** (0040:00FC/00FE) so our handler can chain to it later.
* **Install our handler** at C000:0E3A into the INT 19h vector (IVT 0000:0064/0066).
* Restore DS/ES, re‑enable interrupts, and resume the VGA BIOS’s init.

### How this pairs with your handler

* When INT 19h fires, your handler runs:

* Does the C\&T call (`AX=5F51h / BL=00 / INT 10h`) → CRT‑only.
* Then **tail‑jumps** to the original INT 19h using the vector we stashed in the BDA.
* No stack funny business; no recursion; late enough that the setting **sticks**.

That’s the whole dance. Clean, tiny, and now… proven on real hardware. 🙌

(E8 79 6E) --CODE CHANGED

That’s the **near CALL** you dropped at 0x9FCF to jump into the installer.

* `E8` = **CALL rel16** (16‑bit near call; same segment).
* `79 6E` = the **little‑endian displacement** `0x6E79`.

How it’s computed (real‑mode, rel16 calls):

1. **Call site** = `0x9FCF`.
For rel16, the CPU adds 3 to get the “next IP” (past the opcode and 2‑byte disp): `0x9FCF + 3 = 0x9FD2`.

2. **Target** = installer start `0x0E4B`.

3. **Displacement** = `target − next_ip`
\= `0x0E4B − 0x9FD2`
\= `0x6E79` (mod 0x10000).

4. Encode little‑endian → **`79 6E`**.

Effect: execution at 0x9FCF does a near CALL to **C000:0x0E4B**, which runs your installer. The installer finishes by `push 9FD8h / ret`, so control returns exactly where the original tiny call chain wanted to go.

Tip: if you ever move the installer (even by 1 byte), you must recompute that displacement:
`disp = new_target − (0x9FCF + 3)` → write as low,high.