# nerves_system_surface_rt

A [Nerves](https://nerves-project.org/) 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

- **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.reg` from 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](https://open-rt.gitbook.io/open-surfacert/common/windows-rt/recovery-images)
page. Some of the links there are dead. These images are unofficial.

To make the recovery USB on macOS:

```sh
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](https://jwa4.gitbook.io/).

```sh
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](https://jwa4.gitbook.io/), which replaced the Open Surface RT
jailbreak USB page.

### Jailbreak references

- [Open Surface RT guide](https://open-rt.gitbook.io/open-surfacert/)
- [Open Surface RT: Jailbreak Exploits](https://open-rt.gitbook.io/open-surfacert/common/windows-rt/jailbreak-exploits)
- [Open Surface RT: Yahallo, Disable Secure Boot](https://open-rt.gitbook.io/open-surfacert/common/boot-sequence/uefi/secure-boot/disable-secureboot)
- [Open Surface RT: Recovery Images](https://open-rt.gitbook.io/open-surfacert/common/windows-rt/recovery-images)
- [jwa4 guide](https://jwa4.gitbook.io/) (current jailbreak USB instructions)
- [Ubuntu wiki: ARM/SurfaceRT](https://wiki.ubuntu.com/ARM/SurfaceRT)
- Open Surface RT Discord: <https://discord.gg/VW75GmWa95>

## Part 2: Porting Nerves

### What's needed

1. **Nerves system.** Build on
   [`nerves_system_br`](https://github.com/nerves-project/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`](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`](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`](https://github.com/nerves-project/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`:

```elixir
{: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](https://github.com/CoderDennis/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".

```sh
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 RT
      DTB, 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](#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:

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:
   ```elixir
   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:
   ```sh
   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

- [Nerves docs: Systems](https://hexdocs.pm/nerves/systems.html)
- [`nerves_system_br` README](https://github.com/nerves-project/nerves_system_br) (porting guide)
- [Porting Nerves to the e-RT3](https://dev.to/pojiro/elixir-on-industrial-hardware-porting-nerves-to-e-rt3-7k1),
  an example port to new hardware
- [U-Boot docs: Surface RT](https://docs.u-boot.org/en/latest/board/microsoft/surface-rt.html)
- [Linux Tegra device trees](https://github.com/torvalds/linux/tree/master/arch/arm/boot/dts/nvidia)

## License

This project follows the [REUSE](https://reuse.software/) 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](https://open-rt.gitbook.io/open-surfacert/) community kernel
and keeps its upstream SPDX headers.
