Unmaintained research; experimental hardware changes; no support commitment. Released for others to inspect, reuse, and continue. Historical notes are not current installation instructions. Scripts can change boot configuration, firmware loading, regulators, and mounted disks; review them before running.
Original research prose, probes, and tooling use the scoped MIT grant unless a file declares another license. BSD-3-Clause tools retain their BSD-3-Clause license. Third-party patches, quoted source, driver excerpts, OEM ACPI tables, and vendor-derived register data retain their existing rights and provenance. The MIT license does not grant redistribution rights over vendor material. Complete proprietary drivers and firmware are not supplied.
Matching October 2026 source handoff releases:
research
and kernel,
both tagged research-handoff-2026-10. These are experimental source snapshots,
not prebuilt kernel, ISO, firmware, or support releases.
- Front camera state and measured fixes: IMX681 capture, desktop/libcamera integration, C-PHY bring-up, and mode timing.
- Kernel source on default
main: latest released front-camera driver and associated C-PHY/CAMSS work together. - Upstream handoff and remaining limits: audio/ADSP, USB-C/DisplayPort, USB4, and camera/codec findings.
- libcamera and FFmpeg patches; libaperture findings: userspace fixes and unpatched recording-bug notes, with their own evidence and scope.
- Exported build and UKI tools: reviewed source-only ISO/EFI tooling and the ADSP test builder, with public external prerequisites instead of a dependency on the unpublished distro checkout.
- Excluded the private-memory handoff archive and two stray command-output files.
- Removed private-memory copying from both handoff generators.
- Limited handoff line-ending validation to Linux executables; PowerShell files intentionally retain CRLF.
- Removed references that required unpublished assistant notes.
- Added this maintenance notice and scoped original-material licensing.
- Made the latest preserved camera tree the kernel's default
main, with no additional experiment merges or new hardware qualification. - Exported missing original ISO/EFI/ADSP source tools with provenance; excluded private disk/account installers, firmware, compiled outputs, and Git history.
- Removed the obsolete private Git/Ghidra bundle generator. Use the tagged GitHub source archives; both public handoff generators include the new tools and their license.
Investigating external GPU support on a Surface Pro 11 (Snapdragon X Elite X1E80100, Windows 11 ARM64) — and, along the way, keeping a decent platform reference for this fairly niche machine.
No eGPU hardware on hand yet. Everything so far is derived from the device itself: ACPI tables, driver binaries, PnP state.
New here? Start with docs/orientation.md — the vocabulary and mental model behind everything else (ACPI vs device tree, what CCI/CSIPHY/NHI/UCSI/HUTRR87 actually are, and how the pieces connect). Then design/state-and-unblock-plan.md for where things stand and what happens next, and design/kernel-dev-strategy.md for how the kernel work is done and verified.
The platform is not the blocker, and as of July 2026 the driver may not be either.
On-device analysis established that PCIe tunneling over USB4 is implemented inbox on ARM64, firmware reserves 3.75 GB of 64-bit MMIO per USB4 port for exactly this, and DMA goes through a coherent SMMUv3. All the things that usually kill eGPU on an ARM machine are fine here.
Then prior-art search turned up two things these docs originally predated: NVIDIA shipped an ARM64 Windows GPU driver (GeForce 616.00, developer preview), and someone ran an RTX 4060 as an eGPU on a Snapdragon X Elite laptop with it at playable frame rates. Separately, a Surface Pro 11 user's Razer Core X already enumerates in Device Manager — the transport chain on this exact model is empirically proven.
So there are now two paths, and they want different hardware:
| Goal | Path | Vendor |
|---|---|---|
| Actually use a GPU here, soon | Install NVIDIA's 616.00 ARM64 preview | NVIDIA |
| Write a GPU driver as the project | Custom KMDF + Mesa RADV, no WDDM | AMD |
The custom-driver work is no longer the shortest route to a working GPU. It stays interesting on its own terms — a Vulkan stack over a self-written KMD would be novel on Windows on Arm, and it doesn't depend on a preview driver continuing to exist or to accept unlisted hardware — but it should be chosen deliberately, not adopted as a necessity.
Read in order:
| Doc | What's in it |
|---|---|
design/three-pillars-loop.md |
The work plan. Iteration recipe for the three unsolved problems: multi-touch, camera, USB4 |
docs/rtx4070-verdict.md |
Start here for the practical question. Will an RTX 4070 eGPU work on this machine, and will it be genuinely usable |
docs/prior-art.md |
Read first. NVIDIA's ARM64 driver, the working Snapdragon eGPU build, and what it does to this project |
docs/nvidia-arm64-driver.md |
Teardown of the 616.00 package — full API inventory, chip support, the subsystem-locked INF, and what it means for Blender/Premiere |
docs/native-blender-arm64.md |
What actually gates a native ARM64 Blender with CUDA/OptiX, and how to build one |
docs/feasibility.md |
The four walls, what's cleared, what isn't, and next steps |
docs/architecture.md |
Proposed design — no WDDM, RADV, user-mode fence polling |
docs/linux-support.md |
Per-peripheral Linux status for this exact machine. Pen and touchscreen do not work, and USB4 doesn't either — which rules out the Linux eGPU path |
docs/linux-install-options.md |
Custom ISO vs VM — WSL2 is a build environment, not a test environment, and why |
docs/linux-prior-art.md |
Who already solved what. Pen, audio, suspend and sensors are working today in someone else's repo; USB4 and cameras are not |
docs/linux-gap-analysis.md |
What it would take to fix each Linux gap, and whether any of it needs hardware RE (it doesn't) |
docs/device-profile.md |
General platform reference for this machine, not eGPU-specific |
docs/usb4-driver-notes.md |
RE notes on Usb4HostRouter.sys — tunnel classes, registry tunables, and the absence of any security gate on PCIe tunnels |
Get an eGPU enclosure and an NVIDIA card, install the 616.00 ARM64 developer preview via Device Manager (overriding the compatibility warning), and see whether it comes up. Ada is proven to work; Blackwell is what the driver actually targets. That answers the whole question in an evening.
A cheap TB3/TB4 NVMe enclosure (~$40) is still a reasonable sanity check on this particular unit first — a third party confirmed enumeration on a Surface Pro 11, but not on yours. It proves the transport chain end to end: retimer, host router, PCIe tunnel, host bridge appearance, SMMU, and MMIO window assignment.
# before plugging anything in
powershell -ExecutionPolicy Bypass -File probes\probe-platform.ps1 -Tag baseline
# with the enclosure attached
powershell -ExecutionPolicy Bypass -File probes\probe-platform.ps1 -Tag with-enclosure
Compare-Object (gc data\probe-baseline.txt) (gc data\probe-with-enclosure.txt)What to look for:
ACPI\PNP0A08\0or\1flipping fromPresent: FalsetoOK— transport works.- A new device with problem code 12 (insufficient resources) — the tunnel works but MMIO assignment doesn't, which would be the one genuinely fatal outcome.
- Nothing changes at all — tunneling isn't being negotiated; check retimer firmware and whether the enclosure is genuinely USB4/TB rather than plain USB-C.
| Script | Purpose |
|---|---|
probes/probe-platform.ps1 |
Snapshot PnP/PCI/USB4/security state. Designed for before/after diffing. |
probes/dump-acpi.ps1 |
Dump all ACPI tables via GetSystemFirmwareTable, including the DSDT (which is not enumerated and needs fetching by signature). |
probes/parse_acpi.py |
Parse MCFG (PCIe segments), DSDT (host bridge MMIO windows), IORT (SMMU topology, DMA limits). Not a full AML interpreter — scans for raw resource descriptors, which is enough for recon. |
probes/strings_pe.py |
ASCII + UTF-16 strings and PE import table from a driver. Fast triage without involving Ghidra at all. |
probes/pe_arch.py |
PE machine-architecture inventory over a directory tree. Written to answer whether a driver ships ARM64, ARM64EC, or x64 user-mode components. |
probes/Check-CrossAdapter.ps1 |
Query every D3D12 adapter for the cross-adapter / hybrid-presentation capabilities an eGPU driving the internal display depends on. |
probes/acpi_i2c_map.py |
Recover I2C/SPI/UART slave devices and their controllers from the Windows ACPI DSDT — for identifying hardware Linux hasn't named yet. |
probes/pe_hybrid.py |
Distinguish ARM64EC / ARM64X hybrid images from genuine x64 via the CHPE metadata pointer. PE machine type cannot tell them apart. |
probes/ghidra_api.py |
Minimal client for a GhidraMCP headless server's HTTP API, so you can talk to a specific port when several instances are running. |
Regenerate the ACPI analysis:
powershell -ExecutionPolicy Bypass -File probes\dump-acpi.ps1
python probes\parse_acpi.py > data\acpi-report.txtdocs/ findings and design
probes/ re-runnable investigation scripts
data/ generated output, OEM ACPI tables, and decoded reports
- Proprietary system drivers used for offline analysis must be obtained from your
own Windows DriverStore.
data/bin/is gitignored and is not part of this release.
There are two modes, and the difference decides whether your work survives.
Throwaway triage — start-server-for-file.bat passes --file, which imports
into a transient program:
C:\Tools\ghidra-mcp\start-server-for-file.bat "...\Usb4HostRouter.sys" 8090
curl -X POST "http://127.0.0.1:8090/run_analysis?program=Usb4HostRouter.sys"
python probes\ghidra_api.py 8090 /search_strings search_term=PCIeTunnel
A --file program can never be saved — save_program returns
Location does not exist for a save operation!, and adding --project does not
help, because --file still imports transiently. Renames and comments made this
way are lost when the server stops.
Durable work — import into a real project first, then serve that program. This is the only way renames, prototypes, structs and comments persist:
:: 1. import + analyze + save into the project (creates it if needed)
C:\Tools\ghidra\ghidra_12.1.2_PUBLIC\support\analyzeHeadless.bat ^
C:\Tools\ghidra-proj SurfaceCam -import "...\rearsensor.sys" -overwrite
:: 2. serve the program that now lives in the project -- --program, not --file
C:\Tools\ghidra\ghidra_12.1.2_PUBLIC\support\launch.bat fg jdk GhidraMCPHeadless 4G "" ^
com.xebyte.headless.GhidraMCPHeadlessServer ^
--project C:\Tools\ghidra-proj\SurfaceCam.gpr --program /rearsensor.sys --port 8094
Then save_program works, and should be called after each batch of edits.
Gotchas:
-
The loader does not auto-analyze when launched with
--file.analysis_statusreportsanalyzed: falseand queries come back nearly empty untilrun_analysishas been POSTed. (analyzeHeadlessanalyses as it imports, so the--programpath is already analysed.) -
Reads are GET with query params; writes are POST with a JSON body. Sending a mutation as a query string returns a misleading
"Function address or name is required"even when the parameter is present.probes/ghidra_api.pyonly does GET, so useInvoke-RestMethod -Method Post -Body <json> -ContentType application/jsonfor anything that changes the database. -
create_projecttakesparentDir+name(notpath). -
The MCP bridge only ever sees the one auto-discovered instance —
list_instancesshows just port 8089 no matter how many per-binary servers are running, andconnect_instancematches on project name among those. So MCP tools cannot drive a per-binary server; use HTTP. -
Port 8089 is the long-running Ableton instance. Calling
create_projecton the connected bridge switches that server's open project out from under it. Calling it over HTTP against your own port is fine. -
In PowerShell, don't name a variable
$args— it is an automatic variable and the assignment silently yields nothing. -
A kernel build after
git checkoutcan report success without building what you asked for. Switching to a branch that introduces a new Kconfig symbol leaves.configstale; the implicitsyncconfigthen tries to prompt for the new symbol, fails withError in reading or end of filebecause stdin is closed, and the build still exits 0. The log looks like a menu dump. Set the symbol and runolddefconfignon-interactively on every branch before building, and check the symbol is really in.configbefore trusting the result:./scripts/config --file .config -m CONFIG_VIDEO_IMX681 make olddefconfig </dev/null grep -E '^CONFIG_VIDEO_IMX681=' .config # or the build proved nothing -
analyzeHeadlessis throttled by default and it looks like a hang. Its launcher setsMAXMEM_DEFAULT=2Gplus-XX:ParallelGCThreads=2 -XX:CICompilerCount=2, explicitly so people can run many instances in parallel. On a 9.3 MB DLL that produced 36 minutes of CPU, RSS pinned at ~2 GB, and no program — which reads as "too big to analyse" but is really GC thrash against a 2 GB ceiling. Raise it before blaming the binary:set GHIDRA_HEADLESS_MAXMEM=12G set GHIDRA_HEADLESS_JAVA_OPTIONS=-XX:ParallelGCThreads=8 -XX:CICompilerCount=8The server launcher (
start-server-for-file.bat) takes its heap as an argument instead and is not subject to this default, which is why the small drivers analysed fine through it. -
A running server holds the project lock, so
analyzeHeadlesscannot import into it. The failure is immediate and explicit —LockException: Unable to lock project!— but easy to cause, because the natural workflow is to serve a program and then import the next one. Stop the server first; if it was killed rather than shut down, delete the stale<Project>.lockand.lock~.
Current projects: C:\Tools\ghidra-proj\SurfaceCam.gpr holds eight programs,
all imported with analyzeHeadless and therefore already analysed. Enumerate them
by reading NAME out of SurfaceCam.rep/idata/**/*.prp — do not go from memory,
which is how this list was wrong once already:
| program | annotations |
|---|---|
qccammipicsi8380.sys |
the CSIPHY/CSID driver — C-PHY settle table, lane masks and per-frequency register tables, extracted to data/csiphy-cphy-x1e80100.txt |
QcDeviceMFT8380.dll |
CamX_ImageSensorData_CreateCSIPHYConfig, CamX_SensorNode_AcquireResources, CamX_IFENode_SetupCSIPHYInputResource |
surfacecamfrontsensor8380.sys |
CameraSensorDriver_SendCSLPacket + a plate comment holding the recovered opcode map and the 24-byte CSIPhyInfo payload layout |
surfacecamrearsensor8380.sys |
camera-probe annotations, see docs/camera-sensors.md |
surfacecamauxsensor8380.sys |
none yet — the IR sensor, imported for comparison work |
qccamplatform8380.sys |
camera platform driver, imported 2026-08-02 |
qccamisp8380.sys |
the CSID/IFE driver -- imported 2026-08-07. The Windows counterpart of drivers/media/platform/qcom/camss; its strings name CamZ\Core\IFEDriverV3\csid\src\csid_full_hal.c. Has the RDI0-RDI4 path enables, the ipp/rdi/rx/top/bufDone ISR, IFE Overflow IRQ, and the full acquire structure (lane type dphy/cphy, VC, data type, VCDT count, input height, binning config, HBI count). Serve with start-isp-server.bat on 8099. Found by searching the DriverStore for CsidRxTotalPktsRcvd, which qccammipicsi8380.sys does not contain. data/bin/ is gitignored, so copy it back from C:\Windows\System32\DriverStore\FileRepository\qccamisp8380.inf_arm64_*\qccamisp8380.sys before re-importing. |
QcUsb4Bus8380.sys, TouchPenProcessor0C83.dll |
earlier USB4 and touch work |
Launchers, one per program because only one server can hold the project lock:
start-camx-server.bat (8096), start-frontsensor-server.bat (8097),
start-ableton-server.bat (8089, a different project). Stop the running one
before starting another or before any analyzeHeadless import, and delete the
stale .lock/.lock~ if it was killed rather than shut down — there is no
/shutdown endpoint, so killing it is the normal exit and the lock will be
stale.
The findings those annotations record are in
design/camera-bringup-20260806.md; the database is a convenience, the
document is the record.
Start here: design/three-pillars-loop.md.