The XIE path now runs end to end: XieCreateImage -> XieCreatePhoto ->
XiePutTile -> XieExport(photomap -> depth-1 pixmap) -> XCopyArea(pixmap ->
window), and the client's tile appears on screen at the client's own window
position, pixel-exact.
TWO FAULTS BEHIND THE LONG DEAD END
1. Device model (harness/mame/vt1200.cpp, opt-in VT1200_ACCEL=1). The
accelerator's output port 0x5600200 returns the pixel stream the firmware
pushed into the FIFO -- the firmware then stores it into the drawable itself
(file 0x125528: MOVE *B6,*B1 ; ADDK 0x10,B1). The FIFO read pointer was
never reset between transfers, so consecutive exports read stale words and
the same fill produced 759 then 516 set pixels. Reset m_fifo_in/out on the
data-register write; the export is now deterministic.
2. Measurement, not the device.
- dwt/screenshot.py without --real reads 1024x1024 at a 128-byte row stride,
but the VR319 default programs 1280x1024 at a 2048-bit (256-byte) pitch.
That is the "shifted" picture; --real is the correct read.
- The earlier "0 differing bytes over 128 KiB" VRAM comparison stepped peek
by 1024 *bit* addresses, so it scanned rows 0..63 only -- never the window
at rows 200..263. Over the real raster the white/black diff is 576 bytes
spanning exactly those rows.
- New instrument: decclient does a server-side XGetImage readback of the
pixmap and window and counts set pixels. That separated "the server never
drew it" from "the framebuffer view is wrong".
EVIDENCE (reproduced twice, the second time from a fresh machine)
no export vs 0xFF : 4096 / 4096 pixels differ
no export vs 0x00 : 0 / 4096 (the window background is black)
0xFF vs 0x00 : 4096 / 4096
0xAA vs 0x0F : 2048 / 4096 (the two stripe phases)
Tile byte 0xAA reproduces as 1-pixel vertical stripes, 0x0F as 4-pixel, with
no shear -- so the placement is right and the pixels are the client's.
Contents: xie/ holds DEC XIE 2.3 headers, 84 man pages and complete
client+server source; a libXie.a and decclient built from DEC's own code; the
XIE probes; and evidence/ with every measurement, including explicit
retractions of the earlier wrong readings (VRAM_IDENTICAL_FOR_BOTH_FILLS and
friends). xie/evidence/XIE_IMAGE_DISPLAYED.md is the report; frames are in
xie/evidence/png/.
The accelerator model stays opt-in: it answers the status poll and relays the
pixel stream, but performs no compositing, so it is a stand-in and the verified
default configuration is unchanged (a no-env run still parks in the level-0
diagnostics menu at PC=3FDCE400).
29 KiB
XIE (X Image Extension) in the VT1200 firmware — ROM analysis
Static analysis of vt1200_v22.bin (1 835 008 bytes, md5 b46a3e442ccb86134d2356bd70d22fd2)
under Ghidra, cross-checked against a live wire probe of 192.168.99.2:0 and
against the vendored XIE 5.0 server source in xie/xc/.
Nothing in the ROM was modified. Everything here is reproducible from
xie/ghidra-evidence/ plus the steps in §12.
1. Executive summary
- The VT1200 X server contains a complete, older XIE server — DEC's
XieExtensionInit/XieProc/ProcQueryImageExtensionfamily — built on the same AGE-Logic/X-Consortium code that survives at 5.0 inxie/xc/programs/Xserver/XIE/. It is XIE 2.3,serviceClass = xieValFull, 8 constraint levels. - It registers itself with
AddExtension("Xie", NumEvents = 3, NumErrors = 0, XieProc, XieSProc, XieCloseDown)— a six-argumentAddExtensionwith noMinorOpcodeProc, which is the root of theminor_op = 0artefact (§7). - Its request space is 46 slots (minor 0…45), not 26. Slot 0 and 21 slots beyond
it are a shared "not implemented" stub; 25 slots are implemented. The table is
a 46-entry array of function pointers, in ROM at
0x18D8E8(main) and0x18D9A6(swapped), and it is installed into main RAM at bit0xD3F90/0xD4550. - Why a 5.0 client dies: the 2.3 request lengths and the request set differ.
Every handler length-checks
stuff->lengthin its first six instructions and returnsBadLength(16) on mismatch — and that returned error is emitted by dix's generic path with minor 0, so the client is told only "opcode 129, minor 0, BadLength".xieperf -showtechsdies because it asks forQueryTechniques(5.0 minor 2) in a 2-word request: in 2.3 slot 2 accepts exactly one word and produces no reply at all, so at two words it getsBadLength/minor 0 and at one word it hangs waiting for a reply that is never sent. - There is no
QueryTechniquesand no technique-capability reply anywhere in the 2.3 table.xieperf -showtechscannot be made to work against this server without a client that speaks 2.3.
Confidence markers: [C] confirmed by two independent angles (ROM code plus wire or source), [I] inferred from one angle, [U] unknown.
2. Address model used throughout
ghidra_byte = (mame_bit - 0x3F200000) / 8 (ROM is mapped at bit 0x3F200000)
mame_bit = 0x3F200000 + ghidra_byte * 8
Instructions are 16-bit little-endian words. A 32-bit immediate in the code stream
is the bit address of its target (MOVI 0x3F5263B0 → file offset 0x64C76).
CALLA prints a byte address in the CPU's byte space, whose base is 0x7E40000:
file_offset = operand - 0x7E40000 (e.g. CALLA 0x07eff36e → 0xBF36E).
3. Address table
| Ghidra | MAME bit | name | what it does |
|---|---|---|---|
0x64A9C |
0x3F5254E0 |
XieExtensionInit |
calls AddExtension("Xie",…), caches the ExtensionEntry, then runs device init |
0x64B98 |
0x3F525CC0 |
XieProc |
request dispatcher for major opcode 129 (unswapped clients) |
0x64BE0 |
0x3F525F00 |
XieSProc |
same for byte-swapped clients; indexes the swapped table |
0x64C28 |
0x3F526140 |
XieCloseDown |
CloseDownProc passed to AddExtension |
0x64C3C |
0x3F5261E0 |
ProcNotImplemented |
shared stub in 21 slots; sends BadRequest itself |
0x68A4C |
0x3F545260 |
slot 1 handler | QueryImageExtension (2-word request, 8-word reply) |
0x18D8E8 |
0x3FE6C740 |
ProcTable[] image |
46 × CARD32 bit address, installed at RAM bit 0xD3F90 |
0x18D9A6 |
0x3FE6CD30 |
SProcTable[] image |
46 × CARD32, installed at RAM bit 0xD4550 |
0x18DA66 |
0x3FE6D330 |
third 46-entry table image | installed at RAM bit 0xD4B10; used from inside slot 1 |
0x0BF36E |
0x3F7F9B70 |
AddExtension |
validates 3 proc pointers and the two counts |
0x0BFA5E |
0x3F7FD2F0 |
SendErrorToClient-like |
builds a 32-byte core error from (client, major, minor, badValue, code) |
0x66FA0 |
0x3F533D00 |
LookupPhotos |
generic photo-resource lookup; emits BadIDChoice (14) |
The table images are preceded in ROM by their own init records
(dispatch_table.txt):
0x18D8E2: CARD16 0x005C CARD32 0x000D3F90 <- main table destination
0x18D9A0: CARD16 0x005C CARD32 0x000D4550 <- swapped table destination
0x18DA60: CARD16 0x005C CARD32 0x000D4B10 <- third table destination
which is what makes the ADDI 0xd3f90 / ADDI 0xd4550 in the dispatcher resolvable
to concrete ROM data.
4. Registration [C]
XieExtensionInit at 0x64A9C. Arguments are pushed onto the callee's argument
block and consumed right-to-left, so the pushed order (closeDown, swapped, main,
0, 3, "Xie") is the C order (name, NumEvents, NumErrors, main, swapped, closeDown):
rom:00064aa8 MOVI 0x3f526140,A11 ; closeDownProc = 0x64C28
rom:00064ab0 MOVI 0x3f525f00,A9 ; swappedMainProc= 0x64BE0
rom:00064ab8 MOVI 0x3f525cc0,A9 ; mainProc = 0x64B98
rom:00064ac0 SUB A9,A9 ; NumErrors = 0
rom:00064ac4 MOVK 0x3,A9 ; NumEvents = 3
rom:00064ac8 MOVI 0x3f5263b0,A8 ; "Xie" (file 0x64C76)
rom:00064ad0 CALLA 0x07eff36e ; AddExtension (file 0x0BF36E)
Independent check — AddExtension itself (0xBF36E) requires three non-NULL proc
pointers and bounds the two counts against the event window (128) and error window
(255):
rom:000bf374 MOVE *A13(0xff80),A11,0x1 ; arg3 mainProc -> JREQ fail if NULL
rom:000bf37a MOVE *A13(0xff60),A11,0x1 ; arg4 swapped -> JREQ fail if NULL
rom:000bf380 MOVE *A13(0xff40),A11,0x1 ; arg5 closeDown -> must be non-NULL
rom:000bf38e MOVE @0x21a620,A11,0x1 ; running event base
rom:000bf394 MOVE *A13(0xffc0),A9,0x1 ; arg1 = NumEvents
rom:000bf39a CMPI 0x80,A9 ; 128
rom:000bf3a0 MOVE @0x21a640,A9,0x1 ; running error base
rom:000bf3a6 MOVE *A13(0xffa0),A11,0x1 ; arg2 = NumErrors
rom:000bf3ac CMPI 0xff,A11 ; 255
Numbers. NumEvents = 3, NumErrors = 0.
- The wire agrees on the events:
XQueryExtension("Xie")returns present, opcode 129, first_event 64, which is what dix assigns when it hands out a three-event block at that point inInitExtensions[C] (same registration, different dynamic base is possible on a differently-populated server — the count is what the ROM proves). NumErrors = 0is consistent with the wire: the only XIE failures seen are the core errorsBadRequest(1),BadIDChoice(14) andBadLength(16). No extension-private error code is ever produced [C].- The reproduction of opcode 129 is not decidable from the ROM — dix assigns
major_opcodedynamically fromlast_opcode. What the ROM does prove is that XIE is the only extension registered besidesServerManagementExtension, and that it is registered with the standard dixAddExtensionpath, so 129 is simply "second extension registered on this server" [I].
XieExtensionInit then caches fields of the returned ExtensionEntry:
rom:00064b30 MOVE *A11(0x60),A11,0x1
rom:00064b34 MOVE A11,@0x1ad960,0x1 ; XIE major opcode, used in every error
0x1AD960 is the value later fed to SendErrorToClient as the major opcode — the
wire's major_op=129 for every error confirms the field [C].
Finally it calls a device-init routine at 0x3F944 (CALLA 0x07e7f944 at 0x64B64)
and prints "XieExtensionInit: Device Init failed\n" if it returns non-zero. That is
the only "Device Init" in the ROM; its body was not analysed [U].
5. The dispatcher [C]
XieProc, 0x64B98 / bit 0x3F525CC0:
rom:00064b9e MOVE *A13(0xffe0),A11,0x1 ; arg0 = ClientPtr
rom:00064ba2 MOVE *A11(0x40),A9,0x1 ; A9 = client->requestBuffer
rom:00064ba8 MOVE *A9(0x8),A11,0x0 ; stuff->opcode = request byte 1
rom:00064bac CMPI 0x2e,A11 ; 46
rom:00064bb0 JRNC 0x00064bca ; >= 46 -> dix protocol error
rom:00064bbc SLL 0x5,A11 ; * 32 bits = 4-byte stride
rom:00064bbe ADDI 0xd3f90,A11 ; proc_table at RAM bit 0xD3F90
rom:00064bc4 MOVE *A11,A11,0x1
rom:00064bc6 CALL A11
rom:00064bd6 MOVE *SP(0x80),A14,0x1
rom:00064bda MMFM SP,0x2a00 ; handler's A8 (an X error code) -> dix
XieSProc (0x64BE0) is byte-for-byte the same with ADDI 0xd4550 — the swapped
table. This is exactly the 5.0 macro
#define ExecProc(client,opcode) ((client_table[client->index].proc_table[opcode])(client))
#define CallProc(client) (ExecProc(client,stuff->opcode))
(xie/xc/programs/Xserver/XIE/include/macro.h), except that 2.3 indexes a RAM
copy installed from the ROM image at 0x18D8E8.
There is no per-request work in the dispatcher other than the < 46 bound —
so any per-request length check happens inside the handler, which is what the wire
shows (§7).
6. The request table [C]
See
GHIDRA_XIE_HANDLERS.md(second pass) for what each slot does, the corrected reading of the third pointer table, and a verified session-level client. Two corrections to what follows:
- The array at
0x18DA64(RAM bit0xD4B10) is the reply-swap table, not the swapped-proc table, and only slots 1, 3, 6, 10 and 24 have an entry — those are the slots that produce a reply at all. The real swapped-proc table is at0x18D9A6(RAM bit0xD4550), as stated in §3. (The dump inghidra-evidence/dispatch_table.txtlabels the0x18DA64columns "swapped"; they are reply-swap entries.)- Slots 1–4 are DEC's four Session Services (
INIT_SESSION,TERM_SESSION,QUERY_OP_DEFAULTS,SET_OP_DEFAULTS); slot 1's name in §6.1/§7 stands (QueryImageExtensionequivalent), slots 2–4 were unnamed there. Slots 7, 8 and 10 turn out to be multi-operation classes, not single requests.
46 slots; 0x64C3C (ProcNotImplemented) fills 21 of them. Length column is
stuff->length in 4-byte words, read from the code (see
xie/ghidra-evidence/listings.txt for the instruction addresses). The last column
is what the live server did when sent a 1-word request for that minor (my own
sweep, xie/ghidra-evidence/length_sweep_wire.txt): BadLength means "slot is
implemented and rejects 1 word".
| minor | handler (Ghidra) | MAME bit | length check in ROM | 1-word wire result |
|---|---|---|---|---|
| 0 | 0x64C3C |
0x3F5261E0 |
none (NotImplemented) | BadRequest, minor_op 0 |
| 1 | 0x68A4C |
0x3F545260 |
length == 2 |
BadLength |
| 2 | 0x68DA6 |
0x3F546D30 |
length == 1 |
TIMEOUT (success, no reply) |
| 3 | 0x3FD78 |
0x3F3FEBC0 |
length == 2 |
BadLength |
| 4 | 0x3FE06 |
0x3F3FF030 |
length == 2 |
BadLength |
| 5 | 0x68DDC |
0x3F546EE0 |
length == 5, then byte[4] == 2 |
BadLength |
| 6 | 0x68EC6 |
0x3F547630 |
length == 1 |
REPLY len 0 |
| 7 | 0x66866 |
0x3F534330 |
length == 4 |
BadLength |
| 8 | 0x668E4 |
0x3F534720 |
length >= 3 |
BadLength |
| 9 | 0x6698E |
0x3F534C70 |
length == 3 |
BadLength |
| 10 | 0x66B64 |
0x3F535B20 |
length == 3 |
BadLength |
| 11 | 0x667B6 |
0x3F533DB0 |
length == 3 |
BadLength |
| 12 | 0x66738 |
0x3F5339C0 |
length == 2 |
BadLength |
| 13 | 0x66A8E |
0x3F535470 |
length == 2 |
BadLength |
| 14 | 0x66C34 |
0x3F5361A0 |
length == 4 |
BadLength |
| 15 | 0x6A536 |
0x3F5529B0 |
length == 3 |
BadLength |
| 16 | 0x64C3C |
0x3F5261E0 |
none (NotImplemented) | BadRequest, minor_op 16 |
| 17 | 0x64C3C |
0x3F5261E0 |
none | BadRequest, minor_op 17 |
| 18 | 0x6A5A4 |
0x3F552D20 |
length >= 4 |
BadLength |
| 19 | 0x6A76E |
0x3F553B70 |
length >= 8 |
BadLength |
| 20 | 0x6AA54 |
0x3F5552A0 |
length == 0x23 (35 words) |
BadLength |
| 21 | 0x62D40 |
0x3F516A00 |
length == 16 |
BadLength |
| 22 | 0x630F4 |
0x3F5187A0 |
length == 2 |
BadLength |
| 23 | 0x64C3C |
0x3F5261E0 |
none | BadRequest, minor_op 23 |
| 24 | 0x6315E |
0x3F518AF0 |
length == 4 |
BadLength |
| 25–32 | 0x64C3C |
0x3F5261E0 |
none | BadRequest, minor_op = N |
| 33 | 0x64CC8 |
0x3F526640 |
length == 4 |
BadLength |
| 34–39 | 0x64C3C |
0x3F5261E0 |
none | BadRequest, minor_op = N |
| 40 | 0x65262 |
0x3F529310 |
length == 4 |
BadLength |
| 41,42 | 0x64C3C |
0x3F5261E0 |
none | BadRequest, minor_op = N |
| 43 | 0x65600 |
0x3F52B000 |
length == 10 |
BadLength |
| 44 | 0x65CAA |
0x3F52E550 |
length == 5 |
BadLength |
| 45 | 0x64C3C |
0x3F5261E0 |
none | BadRequest, minor_op 45 |
The 46-request sweep matches the table exactly: every slot the ROM maps to
0x64C3C answers BadRequest and every other slot answers BadLength (or, for 2
and 6, runs). That is a complete, independent confirmation of the table contents —
25 implemented slots at 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,18,19,20,21,22,24,33,40,43,44.
6.1 What can and cannot be named [I]/[U]
Only slot 1 is identified with certainty (see §7.1). The rest cannot be given 5.0 names by number, because the numbering demonstrably is not 5.0's. Hard counter-examples:
- slot 22 takes 2 words; 5.0's smallest slot-22 candidate (
PutClientData,sz_xiePutClientDataReq = 20) is 5 words. - slot 2 takes one word and returns no reply; no 5.0 query behaves that way.
- slot 23 (
GetClientDatain 5.0) isProcNotImplementedhere. - slot 6 takes no argument where 5.0's
QueryColorListcarries acolorListid.
Structural facts that are established for the unnamed slots:
- Photo-resource requests — slots 11, 12, 13, 14, 21, 22, 24, 33, 40, 43, 44 all
call
LookupPhotos(0x66FA0), the same helper that emitsBadIDChoice(14). That is why the wire showsBadIDChoicefor minor 11 at 3 words and minor 24 at 4 words: they were dispatched and their id argument did not resolve. - Id field position — in both slot 11 and slot 24 the id is the CARD32 at
request byte offset 4 (
MOVE *A9(0x20),…), i.e. word 1, same offset as 5.0, but the request is longer overall (extra words follow the id). - Element-list requests — slots 19 (≥8 words), 20 (35 words) and 43 (10 words)
are the only large ones; they are the closest thing to photoflo/element-list
requests. Slot 20 additionally seeds a local with
MOVK 0x19(25) before the length test, i.e. it has a 25-element notion of its own [I]. - The restart/teardown pair — slot 1 increments the global at
0x1AD920when it initialises client state; slot 2 decrements it and returnsSuccesswith no reply. So slot 2 behaves like a per-client teardown/reset request [I]. (0x1AD920is also zeroed byXieExtensionInitat0x64B18.)
7. QueryImageExtension and the BadLength / minor-0 mechanism
7.1 Slot 1 = QueryImageExtension [C]
Handler 0x68A4C, length == 2 (0x68A60/0x68A64/0x68A66). It builds an
8-word reply in its frame and hands it to the generic reply writer
CALLA 0x07e6a422 (0x68D62). The load-bearing stores:
rom:00068c88 MOVE *A13(0xffe0),A5,0x1 ; client
rom:00068c8c MOVE *A5(0xe0),*A13(0x130),0x0 ; serviceClass <- client field
rom:00068c92 MOVK 0x8,A5
rom:00068c94 MOVE A5,*A13(0x140),0x1 ; reply.length = 8
rom:00068c98 MOVK 0x2,A7
rom:00068c9a MOVE A7,*A13(0x160),0x0 ; majorVersion = 2
rom:00068c9e MOVK 0x3,A5
rom:00068ca0 MOVE A5,*A13(0x170),0x0 ; minorVersion = 3
and the live reply confirms every field:
bytes 8.. of the reply: 02 00 | 03 00 | 20 00 | 00 00 | e0 56 41 00 ...
major minor class align first levels
reply length field = 8
reply.length = 8 matches the 5.0 source exactly — reply.length = sizeof(Preferred_levels)>>2 and sz_xieQueryImageExtensionReply = 32
(xie/xc/.../dixie/request/session.c, XIEproto.h), and serviceClass 0x20 is
xieValFull under XIE_FULL. The 2.3 request is 8 bytes, identical to 5.0's,
which is why xieperf gets this far.
One 2.3-only detail: the handler also reads the request's second word and compares its halves with 2 and 3 —
rom:00068c6e MOVE *A9(0x20),A5,0x0 ; request word 1, low half
rom:00068c72 SUBK 0x2,A5 ; == 2 ?
rom:00068c76 MOVE *A9(0x30),A5,0x0 ; request word 1, high half
rom:00068c7a SUBK 0x3,A5 ; == 3 ?
rom:00068c84 MOVB *A5,*A13(0x128) ; -> per-client "declared 2.3" flag
That flag gates the per-client state initialisation at 0x68CAC…0x68D16
(it is not used to build the reply — a client that sends zeros still receives
major 2 / minor 3). Interpreted as a client-declared version pair {2,3}; [I],
the exact consequence of the flag is not traced.
7.2 Why BadLength reports minor 0 [C] for the behaviour, [I] for the dix side
Two different error paths exist, and only one of them carries a minor opcode.
(a) Errors the XIE code sends itself carry the real minor. LookupPhotos
(0x66FA0) on a failed resource lookup:
rom:00066fd8 MOVK 0xe,A11 ; arg = 14 = BadIDChoice
rom:00066fda MOVE A11,*A14+,0x1
rom:00066fdc MOVE *A13(0xff80),*A14+,0x1 ; the offending id
rom:00067110 MOVE A8,*A14+,0x1 ; stuff->opcode (the real minor)
rom:00067112 MOVE 0x1ad960,*A14+,0x1 ; XIE major opcode
rom:00067118 MOVE *A13(0xffe0),*A14+,0x1 ; client
rom:0006711c CALLA 0x07effa5e ; SendErrorToClient(...)
and ProcNotImplemented (0x64C3C) does the same with code 1:
rom:00064c4c MOVK 0x1,A7 ; arg = 1 = BadRequest
rom:00064c56 MOVE *A9(0x8),A8,0x0 ; stuff->opcode
rom:00064c5c MOVE 0x1ad960,*A14+,0x1 ; major opcode
rom:00064c64 CALLA 0x07effa5e ; SendErrorToClient(...)
Wire: minor 23 → code 1, minor_op 23, major_op 129; minor 16,17,25…45 likewise
report their own number. The real minor is transmitted.
(b) The length check does not send anything — it returns 16. Every handler's prologue is:
rom:00068eda MOVE *A9(0x10),A11,0x0 ; stuff->length
rom:00068ede DEC A11
rom:00068ee0 JREQ ok
rom:00068ee2 MOVK 0x10,A8 ; 16 = BadLength
rom:00068ee4 JR epilogue ; A8 is the function's return value
XieProc returns that A8 to dix (0x64BDA MMFM restores and returns). dix's
generic "extension returned an error" path then emits the error — and it has no
minor opcode to put in it: the extension was registered by a six-argument
AddExtension with no MinorOpcodeProc argument (§4), and XieProc keeps no
minor in any client field. Result: BadLength always arrives with
minor_op = 0, while errors sent by the XIE code itself carry the true minor.
This is exactly the split the wire shows, for every implemented slot:
minor=1 words=1: ERROR code=16 ... minor_op=0 major_op=129
minor=23 words=1: ERROR code= 1 ... minor_op=23 major_op=129
Consequence for xieperf. Its failing request is one whose 5.0 length does not
match the 2.3 expectation, so the client learns only "major 129, minor 0,
BadLength" — the minor is structurally unavailable. The only robust fix on the
client side is to send 2.3 lengths; the server cannot be made to name the failing
request without patching dix's error path.
7.3 The observed sizes for minors 6, 11 and 24 [C]
| minor | 2.3 length | 5.0 length (sz_*Req) |
consequence |
|---|---|---|---|
| 6 | 1 word (bare header) | 2 words (colorList id) |
5.0's 2-word request → BadLength; 1 word → reply with length = 0 (an empty list) |
| 11 | 3 words | 2 words (photomap id) |
id at byte 4 resolves through LookupPhotos; a 5.0 2-word request is rejected before the lookup |
| 24 | 4 words | 3 words (nameSpace + floID) |
id at byte 4; a 5.0 3-word request is rejected |
So the 2.3 requests are consistently longer than 5.0's for id-carrying requests, and shorter (argless) for the list query at minor 6. Both directions break a 5.0 client.
8. 2.3 vs 5.0 — concrete wire differences
| aspect | XIE 5.0 (xie/xc/...) |
ROM 2.3 |
|---|---|---|
| request space | 26 (xieNumProtoReq 26, ProcTable5_00[27]) |
46 (CMPI 0x2e, 46-entry table) |
| implemented requests | 25 in XIE_FULL |
25 of 46; 21 slots are ProcNotImplemented |
| dispatch | client_table[].proc_table[opcode] |
same, but table lives in RAM at bit 0xD3F90 / 0xD4550, loaded from ROM 0x18D8E8 / 0x18D9A6 |
minorOpcodeProc |
separate AddExtension argument |
absent — 6-argument AddExtension; BadLength loses its minor |
QueryTechniques |
ProcQueryTechniques, 2-word request, 32-byte reply |
does not exist: slot 2 is a 1-word, reply-less request |
GetClientData |
implemented (slot 23, 5 words) | ProcNotImplemented → BadRequest |
QueryColorList |
2 words, carries a colorList id |
1 word, no argument |
QueryPhotomap |
2 words | 3 words |
QueryPhotoflo |
3 words | 4 words |
| events | 6+ event types in XIEproto.h (PhotofloDone, Flo, ColorAlloc, DecodeNotify, ExportAvailable, ImportObscured) |
3 registered events |
| error codes | XIE-private error classes in xieFlo*Err |
none: only core 1 / 14 / 16 are produced (NumErrors = 0) |
| version reply | major 5, minor 00 | major 2, minor 3, serviceClass = 0x20 (xieValFull), 8 levels |
9. Techniques and elements [C] for the absence, [U] for a capability list
-
There is no QueryTechniques handler and no technique-capability reply in the 2.3 table. Slot 2 — 5.0's
QueryTechniques— accepts exactly one word, decrements0x1AD920, returnsSuccess, and writes nothing to the client:rom:00068dc2 MOVE @0x1ad920,A11,0x1 rom:00068dc8 DEC A11 rom:00068dca MOVE A11,@0x1ad920,0x1 rom:00068dcc SUB A8,A8 ; Success, no WriteToClientThat is why the wire shows
TIMEOUTat one word (the client waits for a reply that never comes) andBadLengthat every other length. -
Consequently the answer to "what would
xieperf -showtechslegitimately list" is: nothing, from this server. There is no table it can ask for. -
The element side does exist — the ROM contains the AGE-Logic element machinery (
"error allocating export function block","error allocating export context","error allocating export histogram space","error allocating export pixel list","error allocating export RGB list","error allocating export map list"at0x649B0…0x64A80, plus"error allocating photoflo"0x68962,"error allocating Roi"0x689BC,"error allocating photomap"0x6D0B0,"unsupported pipe element"0x1C71D). But I did not locate aMakeTable-equivalent array: a scan for runs of ≥18 consecutive ROM pointers found nothing of 5.0's 38-entryMakeTableshape (largest runs: 192, 144, 128, 120, 70, 64, 63, 63 entries, all in the ordinary-data region). Which techniques 2.3 implements is therefore UNKNOWN — the code is present, the enumeration is not identified.
10. Other XIE entry points
| Ghidra | name/role | evidence |
|---|---|---|
0x66FA0 |
LookupPhotos — generic photo-resource lookup, emits BadIDChoice |
string "LookupPhotos: BadIDChoice coming!" @0x68910 referenced at 0x67128; id comes from request byte 4 |
0x0BFA5E |
error sender used by all XIE code that reports a named minor | argument order (client, major@0x1AD960, minor=stuff->opcode, badValue, code) matches the wire |
0x64C28 |
CloseDownProc, passed to AddExtension |
6th argument; small wrapper that calls 0x3F99E |
0x3F944 |
"Device Init" hook called at the end of XieExtensionInit |
CALLA 0x07e7f944 at 0x64B64; JREQ + the "Device Init failed" string @0x64CA1 |
0x67490, 0x67A44 |
xref "error allocating photoflo" — photoflo allocation |
string refs |
0x67C50 |
xref "error allocating Roi" |
string ref |
0x6C820, 0x6CAA8 |
xref "error allocating photomap" |
string refs |
0x68F38 |
helper called from slot 1 after the version check; walks a per-client table at 0x1ADA70 |
ADDI 0x1ada70 at 0x68F4E |
0x1AD920, 0x1AD940, 0x1AD960, 0x1AD980, 0x1AD990, 0x1AD9A0 |
XIE globals: client counter, two cached ExtensionEntry fields, three cached lookup/queue proc pointers, major opcode |
XieExtensionInit 0x64B18…0x64B5E |
A third 46-entry table is installed at RAM bit 0xD4B10 and indexed the same way
from inside slot 1 (0x68D44 SLL 0x5 / 0x68D46 ADDI 0xd4b10 / 0x68D4E CALL A5).
Its contents were not dumped; it is presumably the per-request "device dependent"
override [I].
11. Confirmed / Inferred / Unknown
Confirmed (ROM code + wire, or ROM code + 5.0 source)
AddExtension("Xie", 3, 0, XieProc@0x64B98, XieSProc@0x64BE0, XieCloseDown@0x64C28).- Dispatcher indexes a 46-entry pointer table at RAM bit
0xD3F90withstuff->opcode, rejecting>= 46; the ROM images of that table and the swapped one are at0x18D8E8/0x18D9A6(46 entries each). - The 25 implemented slots and the exact per-slot length check in the table of §6. Verified end-to-end by the 46-request wire sweep.
- Slot 1 is
QueryImageExtension: 2-word request, 8-word reply, major 2, minor 3,serviceClassfrom the client record,reply.length = 8. BadLengthis returned as A8 = 16 and arrives at the client withminor_op = 0;BadRequestfromProcNotImplementedandBadIDChoicefromLookupPhotosare sent by XIE withstuff->opcodeas the minor and arrive with the true minor.- Slots 11 and 24 take 3 and 4 words and read their id from request byte 4; slot 6 takes 1 word.
- There is no
QueryTechniquesin 2.3.
Inferred
- Opcode 129 is just "second extension registered"; the ROM fixes the counts (3 events, 0 errors), not the dynamic base.
- dix's generic error path cannot name a minor because no
MinorOpcodeProcwas supplied (6-argumentAddExtension) — the outcome is proven, the dix internals were not decompiled. - Slot 2 is a per-client teardown/reset request (decrements the counter slot 1 increments; no reply).
- The
{2,3}comparison in slot 1 is a client-declared protocol version that gates per-client state initialisation. - Slots 19/20/43 are photoflo/element-list requests (only large ones; slot 20 has a 25-element notion).
Unknown
- Names of slots 3–5, 7–15, 18–22, 24, 33, 40, 43, 44 — the numbering is not 5.0's,
and only the shape (length, id offset,
LookupPhotosuse) was recovered. - Which techniques/elements 2.3 can execute, and whether a
MakeTableequivalent exists (no 38-entry pointer array was found). - The contents/role of the third 46-entry table at RAM bit
0xD4B10. - What
Device Init(0xE7F944) validates. - Why slot 20 wants exactly 35 words.
12. Reproduction
Everything below assumes the Ghidra session from the task
(program_list_open → session id), language tms34010:LE:32:default, program
/home/arti/code/k-space/vt1200/vt1200_v22.bin, base rom:00000000.
-
Make the session writable and analyse the reset path.
program_mode_set(read_only=false);analysis_update_and_wait. Note: auto-analysis finds 0 functions on this raw image (no entry point). Add the reset vector as an entry point and re-run:program.getSymbolTable().addExternalEntryPoint(addr(0x8))— the reset PC0x3F200040maps to Ghidra0x8. Ghidra still does not followCALLs, so the listings inxie/ghidra-evidence/were produced with explicitDisassembleCommand(AddressSet(start, start+0x5ff), wholeROM, followFlow=true). -
Find
XieExtensionInit. Search the 32-bit immediates for the bit address of the stringXie(0x3F5263B0= file0x64C76) or of"XieExtensionInit: AddExtension failed"(0x3F5263D0= file0x64C7A):import jpype, struct mem = program.getMemory() a = jpype.JArray(jpype.JByte)(0x1c0000); mem.getBytes(program.getMinAddress(), a) data = bytes((b & 0xff) for b in a) data.find(struct.pack("<I", 0x3F5263D0)) # -> 0x64ae2(
mem.getBytes(addr, bytearray)silently does nothing in this PyGhidra build — use ajpype.JArray.) -
Dump the tables
for base in (0x18d8e8, 0x18d9a6): for i in range(46): v = struct.unpack_from("<I", data, base + 4*i)[0] print(i, hex(v), hex((v - 0x3F200000) >> 3)) -
Dump a handler prologue —
DisassembleCommandfrom the handler address for 0x60 bytes and walkgetInstructionAt(); the length check is always within the first six instructions. -
Re-check the wire side (read-only probes, 46 connections):
cd /tmp/opencode && python3 xie_len_sweep.py # uses xie/evidence/xie_raw.pyExpected: the 25 implemented minors answer
BadLength(or, for 2 and 6, run), the 21ProcNotImplementedslots answerBadRequestwith their own minor. -
Re-check the error-path split:
probe(23, 1)→code=1, minor_op=23;probe(1, 1)→code=16, minor_op=0.
Evidence files: xie/ghidra-evidence/dispatch_table.txt (the 46×2 table with raw
bit addresses), xie/ghidra-evidence/listings.txt (every quoted instruction),
xie/ghidra-evidence/length_sweep_wire.txt (the 46-request probe transcript).