# Ring LOAD_TA works in `full_system`; the failure is RAS-specific Enabled by fixing the response readback (`Ring.response()` — the firmware writes the status into the ALIAS copy of the command buffer). With real statuses visible, `LOAD_TA` over the host ring can finally be evaluated. ## Measured | image submitted | status | session | | --- | --- | --- | | RAS (`ras_full.bin`) | `0xffff000b` | 0 | | **HDCP (`hdcp_full.bin`)** | **`0x00000000`** | 0 | | RAS with 0x40 bytes of body corrupted | `0x34` (52) | 0 | | all-zero image | `0x2d` (45) | 0 | Four distinct outcomes, so the path discriminates properly: * **`LOAD_TA` over the ring works.** HDCP loads with status 0. The earlier reading that "LOAD_TA registers no session" was an artefact of the stale readback, and the follow-on "LOAD_TA fails" was true only for RAS. * `0x34` is signature-verification failure — the same code `load_asd.py` reports for a bit-flipped image, so the verification path is live and correct. * `0x2d` for an all-zero image is a distinct, earlier rejection. * **RAS alone returns `0xffff000b`**, and it is not "already loaded": HDCP is equally loaded at boot and succeeds. ## Where it fails `tee_cmd_dispatch` reaches cmd 1 through `tbb [pc, r6]` at 0x202922 (table at 0x202926; cmd 1 -> 0x2029AC). The handler calls **`tee_load_ta`** at 0x202C24 and takes its return as the status — so `0xffff000b` originates inside `tee_load_ta`, not in the dispatcher. The sOS trace during the failing load runs through TA manifest parsing — `ta_prop_get`, `ta_prop_get_heap_pages`, `ta_prop_get_appid`, `bl_rpc_probe`, `sysdrv_rpc_3104/3105/1005` — which points at a **resource or property condition specific to RAS's manifest** (heap pages being the obvious candidate) rather than at image validity, since the image passes signature checks that a corrupted one fails. ## What this unblocks HDCP loads and returns a session over the ring, so the **`INVOKE_CMD` path can now be exercised end to end in `full_system`** against a TA that loads. That was the thing blocking command-coverage work, and it no longer depends on solving RAS first. The RAS-specific `0xffff000b` remains open, with a concrete next step: instrument `tee_load_ta` and find which property check rejects RAS but not HDCP. --- # Narrowing RAS's `0xffff000b`: it is not the image loader Differentially traced HDCP (loads, status 0) against RAS (`0xffff000b`), each in a **fresh build**, filtering the idle spin loop that otherwise swamps the trace (343k blocks of `bl_main`/`bl_finalize_boot` phase drift). ## Two hypotheses, both refuted by measurement **"RAS is already loaded at boot."** Refuted in the same run that raised it: HDCP is equally loaded at boot and returns status 0. **"RAS trips the size guard at `0x2041F2`."** `bl_load_ta_image` has exactly one site setting the error code 0xb — `movs r4, #0xb` at `0x2041F2`, reached from a page-count check (`> 0x400`) and an available-memory check. Hooking both guards shows **RAS never reaches it**, and the two TAs pass identically: HDCP: combined field 0x6 (limit 0x400), need 0xf000, avail 0x3e0000 -> FITS RAS: combined field 0x6 (limit 0x400), need 0xf000, avail 0x3e0000 -> FITS So the failure is not a memory-size rejection, which was the natural reading of the `ta_prop_get_heap_pages` trace. ## Where the paths actually part The two runs are block-identical for 186 blocks and diverge at **`0x2047F0`**, `bl_load_ta_image`'s continuation test: 002047e0 cbz r4, #0x2047f0 ; r4 == 0 -> SUCCESS so far, BOTH take this 002047f0 ldr r0, [pc, #0x9c] ; lit = 0x002060ec 002047f4 ldr.w r0, [r0, #0x824] 002047f8 cbz r0, #0x20480e 002047fa ldr r0, [sp, #0x178] 002047fc cbz r0, #0x20480e `r4 == 0` for **both** TAs, so `bl_load_ta_image` succeeds for RAS too. HDCP then loops back to `bl_load_ta_image+0x9c` to process a further segment; RAS falls out to `0x20480E` and returns. Back in `tee_load_ta`, `movs r7, r0` / `bne` at `0x202CCE` is **not** taken for RAS — confirming a zero return — and RAS proceeds through the `sl == 2` branch to `0x202CF2`. ## Conclusion and next step `0xffff000b` is produced **after** the image loader, on `tee_load_ta`'s path through `0x202CF2`, with `sl == 2` selecting a different arm than HDCP's `sl == 1`. The image loads fine; something downstream rejects RAS. Next step is narrow and concrete: instrument `tee_load_ta` from `0x202CF2` onward and find the site that sets 0xb, with `sl` as the discriminator. --- # Chain complete: the rejection is the TA-side init handshake Continuing past `0x202CF2`, `tee_load_ta`'s `sl == 2` arm reaches: 00202d14 sub.w r1, r0, #0x4100 ; typeword must be 0x4154 00202d18 subs r1, #0x54 00202d1a bne #0x202d7e ... 00202d46 movs r2, #3 ; command 3 = init/create 00202d4c bl #0x201848 -> ta_send_command 00202d50 movs r7, r0 ; <- THIS becomes the ring status 00202d52 beq #0x202d62 Measured for both TAs: | | typeword @0x202D14 | `ta_send_command` | ring status | | --- | --- | --- | --- | | HDCP | `0x4154` (ok) | called, returns **`0x0`** | `0x00000000` | | RAS | `0x4154` (ok) | called, returns **`0xffff000b`** | `0xffff000b` | Both pass the typeword gate, both reach the send. The status the host sees is `ta_send_command`'s return, verbatim. ## What this means The full chain is now traced end to end: ring LOAD_TA -> tee_cmd_dispatch, tbb [pc,r6] @0x202922, cmd 1 -> 0x2029AC -> tee_load_ta @0x202C24 -> bl_load_ta_image SUCCEEDS for both (r4 == 0) -> ta_send_command(cmd 3) HDCP 0x0, RAS 0xffff000b <== origin So **RAS's image loads and verifies fine**; what fails is the init/create handshake with the loaded TA. That is consistent with the independent observation in this same harness that RAS's init gate at VA `0x4060E0` reads `0x00` (closed) while HDCP's reads `0x90` — the RAS TA is not completing its init in this environment. This reframes the remaining question. It is not "why does LOAD_TA reject RAS" but "why does the RAS TA fail its init handshake", which is a TA-side question about RAS's environment (its RPC dependencies, `sysdrv_rpc_3104/3105/1005` appear in the load trace) rather than a loader question. Note also that `0xffff000b` may be a timeout rather than a TA-authored code: `ta_send_command` returning it does not prove the TA replied with it. Deciding between "RAS replied with an error" and "RAS never replied" is the next discriminator, and it is cheap — check whether RAS's command handler executes during the send. --- # RAS authors the error itself, and the single fix-point is located ## It is not a timeout `ta_send_command` returning `0xffff000b` did not prove the TA replied. Checked: **both TAs execute during the send** — HDCP 9 blocks, RAS 13 — so RAS is running its handler and answering, not timing out. RAS's 13 blocks, with the error appearing on return: 0x4045f0 0x404608 0x404618 0x404620 0x40466c 0x402c64 0x402ed0 0x402f0c 0x402c70 0x402c94 <- r0 becomes 0xffff000b 0x404670 0x404674 0x4045e8 ## The mechanism 00402c64 push {r4,r5,r6,lr} ; RAS init 00402c6c bl #0x402ed0 00402c70 cbz r0, #0x402c94 ; callee returned 0 -> error 00402c94 ldr r0, [pc, #0x30] ; lit = 0xffff000b 00402c96 pop {r4,r5,r6,pc} `0x402ed0` scans its argument nibble-by-nibble for the first nonzero nibble, stores that index, and requires the nibble to be 5, 6 or 7. RAS receives **r0 = 0** as its first argument — passed in, not read from memory (measured: `r0=0x0, r1=0x412010, r2=0x412008`) — so the callee returns 0 immediately. ## Controlled experiment, with the control that mattered Forcing r0 at the function entry changed nothing, which would have refuted the whole story. But **the experiment had to be validated before being believed**, and it failed validation: | intervention | ring status | | --- | --- | | baseline | `0xffff000b` | | force r0 **after** the call, at `0x402C70` | **`0x00000000`** (success) | | force r0 at entry, `0x402C64` | `0xffff000b` (no effect) | The middle row is the positive control: `reg_write` from a code hook *does* affect this TA, and supplying a nonzero value at the decision point makes the whole `LOAD_TA` return **success**. So the null result at the entry site is an artefact of writing a register at a translation-block boundary, not evidence about the firmware. Without that control the natural reading would have been "hypothesis refuted", and it would have been wrong. ## Conclusion RAS's image loads and verifies; RAS's init then declines because a single input word reaches it as 0, and `0xffff000b` is RAS's own literal for that. Forcing the decision point to nonzero yields a clean `LOAD_TA` success, so this is the only thing standing between `full_system` and a working RAS session. **This is an emulator environment gap, not a firmware defect, and it is deliberately NOT patched.** A forced register would be exactly the "stub returning 0 is a silent lie" failure this project avoids — the value is a real input that hardware supplies and our environment does not. The remaining work is to find what legitimately produces it; the card runs RAS 27.0.2.5 successfully, so the value is observable there. ## Attempt to find the legitimate source: inconclusive (recorded so it is not repeated) The obvious next move is to find what *should* produce that word. sOS builds the init parameter block just before the send: 00202d24 ldrb r0, [r4, #1] 00202d26 cmp r0, #0xff 00202d28 beq #0x202d36 ; 0xff -> skip filling the params 00202d2a ldr r0, [r4, #4] ; -> sp+0x20 00202d2e ldr r0, [r5, #0x30] ; -> sp+0x24 00202d32 movs r0, #7 ; -> sp+0x1c Tried to read `[r4+1]`, `[r4+4]`, `[r5+0x30]` and the assembled parameter block from inside a code hook, for both HDCP and RAS. **All of the reads fail** with a Unicorn error: those are sOS virtual addresses, and `uc.mem_read` in that hook context does not resolve them. So this says nothing about the firmware — no conclusion should be drawn from it in either direction. The next attempt needs to resolve the sOS stack/descriptor VA to its physical address first (the harnesses already do this elsewhere for TA spaces, e.g. gating attribute PAs by trampoline VA), and read through that.