nerves_system_surface_rt

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:

  1. 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.
  2. 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.efi reverses it.

After Yahallo, the UEFI firmware boots any EFI binary, which is what lets Linux boot.

Before you start

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

Part 2: Porting Nerves

What's needed

  1. Nerves system. Build on nerves_system_br, using an existing ARMv7 system as a template. The Cortex-A9 works with the existing nerves_toolchain_armv7_nerves_linux_gnueabihf toolchain.
  2. 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.md for 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 is tegra_defconfig plus linux/surface-rt.fragment.
  3. 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.
  4. fwup config. Partition layout: EFI FAT boot partition, rootfs A, rootfs B, application data. Develop on microSD first, then move to the eMMC.
  5. 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_ssh and mix upload.
  6. 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

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:

  1. mix burn the firmware to a microSD card.
  2. On a FAT-formatted USB stick, put a UEFI Shell as EFI/BOOT/bootarm.efi, plus the system's zImage and tegra30-microsoft-surface-rt-efi.dtb, and a startup.nsh like:
    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
    The Open Surface RT Raspberry Pi OS image's boot partition has such a UEFI Shell.
  3. Boot the Surface from the stick, with the microSD card in, and connect to Wi-Fi with VintageNetWiFi.quick_configure/2.
  4. Copy the .fw to the Surface (sftp nerves.local, put it in /root), then write it to the eMMC at the IEx prompt:
    System.cmd("fwup", ["-a", "-i", "/root/<name>.fw", "-d", "/dev/mmcblk0", "-t", "complete", "-U"])
    This only writes the eMMC's main area. Its boot partitions, which hold the UEFI firmware, are read-only in Linux.
  5. 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.

  1. 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.
  2. On the Mac, turn the .fw into a raw disk image:
    fwup -a -i <name>.fw -t complete -d surface.img
  3. Copy surface.img to the Surface (e.g. with scp) and write it to the eMMC: sudo dd if=surface.img of=/dev/mmcblk0 bs=4M conv=fsync. Check with lsblk that mmcblk0 is the 32/64 GB eMMC first.
  4. 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

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.