Windows on ARM, also known as Windows RT (commercially) is a ‘mobile’ operating system that isn’t necessarily a mobile operating system. Technically, it comes in two commercial flavors, both Windows Phone and Windows RT. Both of these ‘operating systems’ use the NT kernel and underlying foundation underneath for platform initialization and startup.
This blog post goes in depth on how I managed to port Windows RT 8.1 to run on a generic ARM Cortex-A15/Cortex-A9 based system. (This procedure will work for Windows Phone 8.x too, but I never tested that configuration.)
Windows RT has several core system requirements, namely (these don’t necessarily follow the official guidelines, but are instead things I’ve found out through manual research):
Anything else is really considered superflous and is not entirely necessary to boot Windows RT 8.1/8.0 successfully to the user interface.
The firmware must implement the following ACPI tables:
Without these ports (and without a HAL extension), the system will fail to boot successfully.
DBG2 tables used for a ‘successful’ Windows RT boot deviate from ACPI 5.0
specification. The structure for mine closely followed those of the Surface RT, which would mean that
its device firmware violates ACPI spec…?)
System Timer + Interrupt Controller
Common ARM operating systems need a system timer to drive them along with an interrupt controller to dispatch interrupts across SMP/UP platforms in an orderly manner. There was really no standard way to drive all interrupt controllers on ARM until the GIC came around and was put into use with the Cortex-A9 processor.
Cortex-A15 also adds architected timer, removing one other potentially proprietary component from the system that Cortex-A9 and other processors still rely on for core system functionality.
Initial Windows RT devices did ship with Cortex-A9 processors, so how did they solve the issue? Microsoft
implemented a feature called “HAL Extensions”. The Tegra3 family of devices, for example, ships with a library known
HalExtTegra2.dll. This dynamic library controls arming and syncing of the system timer and plugs in to
the rest of the system through a function dispatch table.
For reference, the header
nthalext.h in the Windows Driver Kit 8.1 can be referenced if you wish to make
your own HAL extension. The following structure comes directly from the WDK:
Cortex-A15 systems and potentially others can use the system timer defined in the ARMv7 instruction set architecture (64-bit cp15 register c14, etc), however the issue of finding out which IRQ the timer is tied to is still a problem.
This issue is solved by adding a
GTDT ACPI table to the system, which details the specific IRQ number
the timer lies on.
(Please note: the names of the fields here follow the ARM64/ACPI 5.1 specification, I just used a newer
version of the
iasl compiler from the ACPICA.)
An ARM system can have multiple debug ports, but UART locations aren’t really standardized across all ARM
systems. Enter the
DBG2 ACPI table.
DBG2 ACPI table defines the name path, port type and port address of a
DBGP compatible system UART
or USB peripheral that can be attached to the WinDbg system debugger. Windows RT systems may not have a
table, only a
The namepath for the system UART must also match the one in the
Device Descriptor Information
Instead of using flattened device trees like most ARM based operating systems nowaday, Microsoft chose to support only ACPI for their systems. This creates a barrier that separates Windows RT systems from standard ARM Linux/BSD machines, even if they both use UEFI.
The HAL uses the ACPI tables installed by the UEFI firmware (retrieved from the EFI system table) in order
to determine which addresses are which and where to map system peripherals. In addition, the
ACPI.SYS driver is also
used in parallel with the plug and play system for device enumeration.
While a FDT device may have a path such as
/uart0@0x10006000 on a Linux system, an ACPI device on Windows RT has
the following path convention:
ACPI\VEND000A, and so on. (Additional path schemes are available, but are not
documented here for simplicity.)
Bringup on qemu-system-arm -M virt-rt
‘Virt-rt’ is a system based on qemu-system-arm’s -M ‘virt’, with some simple modifications, namely the addition of a PL310
L2 cache controller, a NS16550 serial UART, 1GB of RAM PL111 LCD controller, extra storage and some modifications to the processor’s
MIDR in order to boot Windows without complaint (Cortex-A15 => Cortex-A9). The system can boot without the PL310 controller
being on the SoC and in the DSDT, however one needs to use the
/DISABLEEXTCACHE flag in the system load options to avoid
an unnecessary bugcheck.
This system has little in common with the Tegra chipsets that most Windows RT devices have, except that it also implements an ARM Generic Interrupt Controller.
The system timer is architected, there is no direct timer peripheral like there is on Tegra or other platforms, instead, a GTDT table is present in the system firmware. The only ACPI tables are the ones needed, otherwise, it is a barren platform with barely anything installed.
Once the system boots to EFI, we can start Windows RT from the EFI shell:
And once we have access to the Windows Boot Manager and have the kernel running, we can attach WinDbg to the host and break into the system’s kernel debugger:
Some minor instruction patching (mainly to disable PatchGuard, nt!KiVerifyScopesExecute), and we’re able to get far into system initialization, far enough to get to a graphical user interface:
(Currently the system has very little functionality in terms of usage, there is no proper input driver nor storage. The framebuffer provided to the system is the exact same one EFI sets up. Once additional functionality is implemented, the system can become usable as a desktop Windows instance.)
Oh yeah, speed isn’t too great either without KVM, but hey, it works for what it’s worth.
But what about Secure Boot and TPMs?
That’s really optional. (And no, I don’t think you’ll ever get past Windows Logo testing either.)
Does this mean I can break Secure Boot on my Surface RT?
tl;dr what do I need to do???
Make a new UEFI target, add ACPI support to it, add a BlockIo driver, add EFI GOP support, ARM support, write ACPI descriptor tables, debug the system, ensure that the proper peripherals are in place, get a Windows image, master it properly for your system, test, debug, test, debug, test, debug, lose a couple nights of sleep and then finally marvel at your creation.
It’s a lot of work.Share