A Nerves system for the original Microsoft Surface RT (2012, NVIDIA Tegra 3).
Status: Nerves runs from the internal eMMC and boots through GRUB, with A/B firmware updates over Wi-Fi (
mix upload) that fall back if new firmware doesn't validate. All four CPU cores and the 1 MB L2 cache are on. The console is on the built-in screen, with the Touch/Type Cover or a USB keyboard. The touchscreen (multitouch), speakers, battery status and USB storage work. As far as we know, nobody has run Nerves on a Surface RT before.
Hardware
| Item | Details |
|---|---|
| SoC | NVIDIA Tegra 3 (T30), 4× Cortex-A9 (ARMv7-A, NEON) |
| RAM | 2 GB |
| Storage | 32/64 GB internal eMMC, microSD slot |
| Wi-Fi/BT | Marvell 88W8797 (SDIO, Linux driver mwifiex_sdio) |
| USB | 1× USB 2.0 Type-A, host only (no gadget/OTG) |
| Input | Touchscreen (Atmel maXTouch, HID over I2C), Touch/Type Cover |
| Firmware | UEFI with Secure Boot locked on (must be jailbroken, see below) |
This targets the Surface RT only. The Surface 2 uses a Tegra 4 and boots Linux differently.
Part 1: Jailbreak the Surface RT
The Surface RT only boots Microsoft-signed code. Two exploits get around this. They're steps of the same jailbreak, and they're done in this order:
- Golden Keys (Secure Boot Debug Policy, CVE-2016-3287 / CVE-2016-3320). A leaked Microsoft-signed debug policy that turns on test signing in the Windows boot manager. Windows updates from November 2016 onwards block it (MS16-094, MS16-100, MS16-140). A device with those updates must be reset to an older Windows image first.
- Yahallo (2020, Bingxing Wang, GPLv2). Exploits the Tegra 3/4 UEFI
variable services to take over TrustZone, then permanently turns off
Secure Boot. It needs Golden Keys installed first. Microsoft and NVIDIA
declined to fix it.
YahalloUndo.efireverses it.
After Yahallo, the UEFI firmware boots any EFI binary, which is what lets Linux boot.
Before you start
- Back up or give up the data on the device. Disabling Secure Boot can
lock out the firmware TPM, which makes BitLocker-encrypted data unreadable.
Either turn off device encryption from Windows (
manage-bde -off C:) or save the recovery key (manage-bde -protectors -get C:, or https://account.microsoft.com/devices/recoverykey). - Don't install Windows updates before jailbreaking. Use a local
account, stay off Wi-Fi, or apply
WindowsUpdateManualMode.regfrom the jailbreak USB. - Keep the tablet plugged in.
- Only flash firmware that matches your device. The jailbreak USB holds firmware for several models.
Resetting the device (if needed)
If Windows has post-2016 updates, or you can't sign in, reset the tablet with a Windows RT 8.0 recovery image. Microsoft no longer offers one for the Surface RT at https://support.microsoft.com/surface-recovery-image. Use the community images listed on the Open Surface RT Recovery Images page. Some of the links there are dead. These images are unofficial.
To make the recovery USB on macOS:
diskutil list external physical # find the USB drive, e.g. /dev/disk4
diskutil eraseDisk FAT32 RECOVERY MBRFormat /dev/diskN
unzip -q "Surface RT 8.0 North America.zip" -d /Volumes/RECOVERY
sources/Install.wim is about 3.6 GB, under FAT32's 4 GB per-file limit.
Boot from it with Volume Down + Power, then choose
Troubleshoot → Reset your PC.
Making the jailbreak USB on macOS
The jailbreak USB package (Tegra_Jailbreak_USB.zip) comes from the
jwa4 guide.
diskutil list external physical # double-check the disk identifier!
diskutil eraseDisk FAT32 SURFACE MBRFormat /dev/diskN
mkdir jailbreak_usb && unzip -q Tegra_Jailbreak_USB.zip -d jailbreak_usb
COPYFILE_DISABLE=1 cp -R jailbreak_usb/ /Volumes/SURFACE/
dot_clean -m /Volumes/SURFACE # remove macOS ._ metadata files
diskutil eject /dev/diskN
eraseDisk erases the whole disk without asking for confirmation.
What's on the USB drive:
| Path | Purpose |
|---|---|
efi/boot/bootarm.efi, efi/microsoft/boot/bcd | Windows boot manager the Surface boots from |
Jailbreak_USB_Menu.cmd | Menu for running the jailbreak |
SecureBootDebugPolicy.p7b, Jailbreak/SecureBootDebugPolicy/ | Golden Keys |
Jailbreak/Yahallo/ | Yahallo.efi, YahalloUndo.efi, README |
Firmware/ | UEFI firmware for Surface RT, Surface 2, TF600T, SKU1 |
Boot from USB with Volume Down + Power. Let go of Volume Down when the Surface logo appears. Then follow the jwa4 guide, which replaced the Open Surface RT jailbreak USB page.
Jailbreak references
- Open Surface RT guide
- Open Surface RT: Jailbreak Exploits
- Open Surface RT: Yahallo, Disable Secure Boot
- Open Surface RT: Recovery Images
- jwa4 guide (current jailbreak USB instructions)
- Ubuntu wiki: ARM/SurfaceRT
- Open Surface RT Discord: https://discord.gg/VW75GmWa95
Part 2: Porting Nerves
What's needed
- Nerves system. Build on
nerves_system_br, using an existing ARMv7 system as a template. The Cortex-A9 works with the existingnerves_toolchain_armv7_nerves_linux_gnueabihftoolchain. - Kernel and device tree. The Surface RT device tree is not in
mainline Linux. This system builds Linux 6.18.36 with a patch that adds
it, ported from the Open Surface RT community kernel. See
dts/README.mdfor the source, the changes and the known limitations (one CPU speed). The battery and audio drivers are backported from later kernels (linux/0008-0010). The kernel config istegra_defconfigpluslinux/surface-rt.fragment. - Bootloader. After Yahallo, UEFI boots EFI binaries directly. Use
GRUB (arm-efi) for A/B switching, following the GRUB + grubenv pattern
in
nerves_system_x86_64. Avoid U-Boot: it supports the Surface RT, but has to be loaded over USB from another computer (APX/RCM mode) on every boot and only uses one core. fwupconfig. Partition layout: EFI FAT boot partition, rootfs A, rootfs B, application data. Develop on microSD first, then move to the eMMC.- Development access. The USB port is host-only, so Nerves' USB gadget
networking won't work. Use Wi-Fi (VintageNet) or a USB Ethernet adapter,
with
nerves_sshandmix upload. - Display (optional). Nerves runs headless by default. Options are a framebuffer console, Scenic on DRM, or a kiosk browser (heavier).
Using the system
Add it to a Nerves project's mix.exs with a target named surface_rt:
{:nerves_system_surface_rt,
github: "CoderDennis/nerves_system_surface_rt",
tag: "v0.2.1",
runtime: false,
targets: :surface_rt}Releases have a prebuilt system artifact, so mix firmware doesn't need to
build the system. Without one it's built from source, as below.
nerves_surface_rt_hello
is an example app.
Building
The system uses nerves_system_br 1.35.2, which builds Erlang/OTP 29.1.2
for the target. The host's OTP major version must match, so this repo pins
Erlang 29.1.2 and Elixir 1.20.4-otp-29 in .tool-versions.
Don't go below OTP 29.1.1 on the target. Its SSH daemon (ssh 6.0.5)
rejects subsystem requests that follow an env request, and macOS's ssh
sends LANG and LC_* by default, so mix upload and sftp fail with
"subsystem request failed on channel 0".
mix archive.install hex nerves_bootstrap
mix deps.get
mix compile # on macOS, Buildroot runs in Docker (Docker must be running)
If your shell doesn't switch versions automatically, prefix the commands
with mise exec -- (or the asdf equivalent).
Nerves writes the Buildroot output to build.log in this directory. Don't
redirect mix compile to that same file, or the two outputs overwrite
each other. The built system ends up in
~/.nerves/artifacts/nerves_system_surface_rt-portable-<version>/.
Plan
- [x] Finish the jailbreak (Golden Keys, then Yahallo)
- [x] Boot an existing community Surface RT Linux image to confirm the
kernel, device tree and boot path work - [x] Nerves system skeleton with
nerves_system_br, GRUB (arm-efi) and a`fwup` config with A/B updates (fwup configs tested with placeholder files; full build not run yet) - [x] Surface RT device tree ported to Linux 6.18 (compiles with
dtc) - [x] Full system build (
mix compile): kernel with EFI stub, Surface RTDTB, GRUB `bootarm.efi`, rootfs and a firmware image all build - [x] Boot Nerves to an IEx prompt (kernel and DTB loaded from the UEFI
Shell on a USB stick, root filesystem on microSD) - [x] Boot through GRUB from the eMMC, with A/B updates. The UEFI firmware
can't see the microSD slot, so Nerves can't boot from microSD. - [x] Wi-Fi with VintageNet and SSH access
- [x] Display output (Tegra DRM console on the built-in screen)
- [x] Install to eMMC (see Installing)
- [ ] An easier first install (today it needs a second, temporary boot
setup, see below). Now that USB flash drives work, Nerves could probably run from a USB stick instead of the microSD card, possibly just `mix burn` to a stick and boot it with Volume Down + Power. The stick has the same partition IDs as the eMMC install, so it would have to come out afterwards. Not tried yet. - [x] Fall back to the previous firmware if new firmware isn't validated
- [x] Use all four CPU cores (ACPI parking protocol, see
linux/0005) - [x] Turn on the L2 cache (through the secure monitor, see
linux/0006) - [x] USB flash drives (fixes a mainline ChipIdea bug on Tegra, see
`linux/0007`) - [x] Battery status and audio (drivers backported from Linux 7.x)
- [x] Touch/Type Cover, with hotplug (
linux/0011,linux/0012) - [x] Touchscreen (multitouch, HID over I2C)
- [x] Audio playback through the speakers (fixes a mainline WM8962 bug,
see `linux/0013`). The speaker volume starts at 0; turn it up with `amixer -c 0 sset Speaker 110`. - [x] Software reboot (restarts through the PMIC, see
`linux/0003-mfd-tps65910-Restart-through-the-PMIC.patch`) - [x] Hardware watchdog: the PMIC's watchdog power-cycles the Surface if
the system hangs (`linux/0014`). The Tegra watchdog's reset doesn't bring it back. - [ ] Research the front and rear cameras. They don't seem to be USB:
probably camera sensors on the Tegra 3's MIPI CSI interface. Start by finding the sensors in the firmware's ACPI tables. The community kernels don't support them either.
Installing
The UEFI firmware can't boot from microSD, so Nerves is installed on the internal eMMC. This erases the eMMC, including Windows. The first install currently needs a second, temporary boot setup:
mix burnthe firmware to a microSD card.- On a FAT-formatted USB stick, put a UEFI Shell as
EFI/BOOT/bootarm.efi, plus the system'szImageandtegra30-microsoft-surface-rt-efi.dtb, and astartup.nshlike:
The Open Surface RT Raspberry Pi OS image's boot partition has such a UEFI Shell.fs0: zImage dtb=tegra30-microsoft-surface-rt-efi.dtb root=PARTUUID=04030201-02 rootwait console=tty1 cpuidle.off=1 clk_ignore_unused pd_ignore_unused reset -s - Boot the Surface from the stick, with the microSD card in, and connect
to Wi-Fi with
VintageNetWiFi.quick_configure/2. - Copy the
.fwto the Surface (sftp nerves.local,putit in/root), then write it to the eMMC at the IEx prompt:
This only writes the eMMC's main area. Its boot partitions, which hold the UEFI firmware, are read-only in Linux.System.cmd("fwup", ["-a", "-i", "/root/<name>.fw", "-d", "/dev/mmcblk0", "-t", "complete", "-U"]) - Power off, remove the microSD card and the USB stick, and press Power. The microSD card uses the same partition IDs, so leave it out.
After that, update with mix upload. GRUB switches between partitions A
and B on its own, and the Surface restarts into the new firmware by itself.
New firmware boots once as a trial. Nerves.Runtime's StartupGuard
validates it once all applications have started. If it doesn't, the next
boot falls back to the previous firmware: if new firmware hangs, hold
Power for 10 seconds and turn the Surface back on.
The kernel arguments (LINUX_ARGS in post-build.sh) are in each root
filesystem, so they ship with mix upload and are covered by the
fallback. mix upload doesn't update the boot partition's grub.cfg,
which only picks the partition and handles the fallback. Only a full
install writes it. When it changes, copy it to /mnt/boot/EFI/BOOT/ on
the device (remount /mnt/boot read-write first).
Untested alternative: install from Raspberry Pi OS
This would avoid the microSD card and the hand-made UEFI Shell setup. It only needs one USB stick, but it hasn't been tried yet.
- Write the Open Surface RT Raspberry Pi OS image to a USB stick, boot the Surface from it (Volume Down + Power) and connect to Wi-Fi. Its kernel can use USB storage.
- On the Mac, turn the
.fwinto a raw disk image:fwup -a -i <name>.fw -t complete -d surface.img - Copy
surface.imgto the Surface (e.g. withscp) and write it to the eMMC:sudo dd if=surface.img of=/dev/mmcblk0 bs=4M conv=fsync. Check withlsblkthatmmcblk0is the 32/64 GB eMMC first. - Power off, remove the USB stick, and press Power.
Written as a raw image, the application data partition probably stays at
the 256 MiB set in fwup.conf. fwup can only grow it to fill the disk
when it writes the disk directly. Installing the armhf fwup package on
Raspberry Pi OS and running fwup -a -i <name>.fw -t complete -d /dev/mmcblk0 there would avoid that.
Nerves references
- Nerves docs: Systems
nerves_system_brREADME (porting guide)- Porting Nerves to the e-RT3, an example port to new hardware
- U-Boot docs: Surface RT
- Linux Tegra device trees
License
This project follows the REUSE specification.
REUSE.toml maps each file to its license, and the license texts are in
LICENSES/. Like the official Nerves systems:
| Files | License |
|---|---|
linux/ (kernel config fragment and Surface RT device tree patch) | GPL-2.0-only |
Config.in, nerves_defconfig, post-build.sh, post-createfs.sh (Buildroot derived) | GPL-2.0-or-later |
fwup.conf, fwup-ops.conf, fwup_include/, grub.cfg, rootfs_overlay/, mix.exs and other small config files | CC0-1.0 |
README.md, dts/README.md | CC-BY-4.0 |
The device tree comes from the Open Surface RT community kernel and keeps its upstream SPDX headers.