Files
vt1200/xie/GHIDRA_XIE.md
T
arti d8d54bb990 feat(xie): a client-chosen XIE image is displayed on the VT1200
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).
2026-11-01 23:22:25 +02:00

29 KiB
Raw Blame History

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 / ProcQueryImageExtension family — built on the same AGE-Logic/X-Consortium code that survives at 5.0 in xie/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-argument AddExtension with no MinorOpcodeProc, which is the root of the minor_op = 0 artefact (§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) and 0x18D9A6 (swapped), and it is installed into main RAM at bit 0xD3F90 / 0xD4550.
  • Why a 5.0 client dies: the 2.3 request lengths and the request set differ. Every handler length-checks stuff->length in its first six instructions and returns BadLength (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 -showtechs dies because it asks for QueryTechniques (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 gets BadLength/minor 0 and at one word it hangs waiting for a reply that is never sent.
  • There is no QueryTechniques and no technique-capability reply anywhere in the 2.3 table. xieperf -showtechs cannot 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 in InitExtensions [C] (same registration, different dynamic base is possible on a differently-populated server — the count is what the ROM proves).
  • NumErrors = 0 is consistent with the wire: the only XIE failures seen are the core errors BadRequest (1), BadIDChoice (14) and BadLength (16). No extension-private error code is ever produced [C].
  • The reproduction of opcode 129 is not decidable from the ROM — dix assigns major_opcode dynamically from last_opcode. What the ROM does prove is that XIE is the only extension registered besides ServerManagementExtension, and that it is registered with the standard dix AddExtension path, 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 bit 0xD4B10) 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 at 0x18D9A6 (RAM bit 0xD4550), as stated in §3. (The dump in ghidra-evidence/dispatch_table.txt labels the 0x18DA64 columns "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 (QueryImageExtension equivalent), 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 (GetClientData in 5.0) is ProcNotImplemented here.
  • slot 6 takes no argument where 5.0's QueryColorList carries a colorList id.

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 emits BadIDChoice (14). That is why the wire shows BadIDChoice for 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 0x1AD920 when it initialises client state; slot 2 decrements it and returns Success with no reply. So slot 2 behaves like a per-client teardown/reset request [I]. (0x1AD920 is also zeroed by XieExtensionInit at 0x64B18.)

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, decrements 0x1AD920, returns Success, 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 WriteToClient
    

    That is why the wire shows TIMEOUT at one word (the client waits for a reply that never comes) and BadLength at every other length.

  • Consequently the answer to "what would xieperf -showtechs legitimately 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" at 0x649B0…0x64A80, plus "error allocating photoflo" 0x68962, "error allocating Roi" 0x689BC, "error allocating photomap" 0x6D0B0, "unsupported pipe element" 0x1C71D). But I did not locate a MakeTable-equivalent array: a scan for runs of ≥18 consecutive ROM pointers found nothing of 5.0's 38-entry MakeTable shape (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)

  1. AddExtension("Xie", 3, 0, XieProc@0x64B98, XieSProc@0x64BE0, XieCloseDown@0x64C28).
  2. Dispatcher indexes a 46-entry pointer table at RAM bit 0xD3F90 with stuff->opcode, rejecting >= 46; the ROM images of that table and the swapped one are at 0x18D8E8 / 0x18D9A6 (46 entries each).
  3. 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.
  4. Slot 1 is QueryImageExtension: 2-word request, 8-word reply, major 2, minor 3, serviceClass from the client record, reply.length = 8.
  5. BadLength is returned as A8 = 16 and arrives at the client with minor_op = 0; BadRequest from ProcNotImplemented and BadIDChoice from LookupPhotos are sent by XIE with stuff->opcode as the minor and arrive with the true minor.
  6. Slots 11 and 24 take 3 and 4 words and read their id from request byte 4; slot 6 takes 1 word.
  7. There is no QueryTechniques in 2.3.

Inferred

  1. Opcode 129 is just "second extension registered"; the ROM fixes the counts (3 events, 0 errors), not the dynamic base.
  2. dix's generic error path cannot name a minor because no MinorOpcodeProc was supplied (6-argument AddExtension) — the outcome is proven, the dix internals were not decompiled.
  3. Slot 2 is a per-client teardown/reset request (decrements the counter slot 1 increments; no reply).
  4. The {2,3} comparison in slot 1 is a client-declared protocol version that gates per-client state initialisation.
  5. Slots 19/20/43 are photoflo/element-list requests (only large ones; slot 20 has a 25-element notion).

Unknown

  1. 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, LookupPhotos use) was recovered.
  2. Which techniques/elements 2.3 can execute, and whether a MakeTable equivalent exists (no 38-entry pointer array was found).
  3. The contents/role of the third 46-entry table at RAM bit 0xD4B10.
  4. What Device Init (0xE7F944) validates.
  5. 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.

  1. 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 PC 0x3F200040 maps to Ghidra 0x8. Ghidra still does not follow CALLs, so the listings in xie/ghidra-evidence/ were produced with explicit DisassembleCommand(AddressSet(start, start+0x5ff), wholeROM, followFlow=true).

  2. Find XieExtensionInit. Search the 32-bit immediates for the bit address of the string Xie (0x3F5263B0 = file 0x64C76) or of "XieExtensionInit: AddExtension failed" (0x3F5263D0 = file 0x64C7A):

    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 a jpype.JArray.)

  3. 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))
    
  4. Dump a handler prologue — DisassembleCommand from the handler address for 0x60 bytes and walk getInstructionAt(); the length check is always within the first six instructions.

  5. Re-check the wire side (read-only probes, 46 connections):

    cd /tmp/opencode && python3 xie_len_sweep.py    # uses xie/evidence/xie_raw.py
    

    Expected: the 25 implemented minors answer BadLength (or, for 2 and 6, run), the 21 ProcNotImplemented slots answer BadRequest with their own minor.

  6. 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).