So you told me you want to run Windows on a Calculator
Running Windows on a consumer calculator was a dream until the appearance of HP Prime G2 Graphing Calculator. This calculator has the most superior hardware specification among the market so far. And more importantly, HP switched to ARMv7-A on the calculator (!)
HP Prime G2 SoC Specification. The NXP i.MX 6ULL (Ultra Lite) SoC features a uni-core Cortex A7 processor.
Previous work
My friend Wenting Zhang (Zephray) has done the fundamental reverse engineering about GPIO assignments and technical information regarding this calculator. In addition, he managed to port a functional U-Boot and Linux on it.
According to TI-Planet, the calculator comes with a lot of test pins including SD/MMC, JTAG and UART.
Sarah (winocm) explored the possibility of running Windows RT on Qemu platform. Some information has been changed as of 2019, but the post is still worth reading.
Microsoft also partnered with NXP to enable Windows 10 IoT on iMX SoCs. Given these facts, this calculator should be technically capable of running Windows 10 IoT (or generically saying Windows on ARM.)
Pushing the limit of Windows System Requirements
Microsoft Docs site outlined the system requirements for the Windows 10 family. To run the small footprint (still large compared to Linux), the system should have a x86/x64/ARMv7/AArch64 processor, at least 256MB memory and 2GB storage. The device should also has a functional UEFI firmware and full ACPI & SMBIOS tables.
There isn’t sufficient onboard storage in this case (and Windows doesn’t support raw SLC NAND flash either), but USB device can be attached to the calculator and acts as a boot device.
Sarah has summarized ARMv7 architectural requirements for running Windows. The requirements have changed a bit across years, so here is my new summary on this:
System processor: ARMv7-A compatible processor with VFPv3+VFP HalfPrec, VFPv4 is also okay
Memory: At least 256MB of RAM (can probably do less)
Peripherals:
- System timer (either architectural or vendor-specific)
- ARM Generic Interrupt Controller (GIC) or Broadcom Interrupt Controller on BCM2835/2836/2837, both CPU and Distributor Interface. GICv2 or higher is required if GIC is present and declared.
- Framebuffer
- Supported UART, either NS16550, BCM283x or some vendor-specific IP (in the case of Qualcomm and NXP)
- Firmware: UEFI 2.3 or higher
In this case the calculator meets those hard requirements. However the SoC runs in 396MHz by default, significantly slower than the 400MHz/1GHz baseline. Also I only report about 250MB memory to the system while the minimum requirement is 512MB with user interface. However, it boots!
The SoC runs at 396MHz according to WinDbg’s cpuinfo
I think we can probably further push the limit. If I recall correctly, Windows Server Nano requires about 150MB memory to boot on amd64 systems.
UEFI and ACPI
I’ve talked about UEFI implementations several times in the past so there isn’t anything new. Basically the UEFI consists a set of device drivers and core components from TianoCore. ACPI tables are copied from the Windows IoT iMX Project Mu Repo and stripped down.
I chainload the UEFI from U-Boot. iMX does not enable unaligned memory access by default and this causes a lot of troubles in UEFI DXE and BDS phases. You should enable it as soon as possible.
mrc p15, 0, r0, c1, c0, 0
bic r0, r0, #2
mcr p15, 0, r0, c1, c0, 0
Using a compiler with hardware float support is also helpful for speed up the boot speeed (30s -> 4s.) Also Windows mandates VFPv3+ support on ARMv7 platforms.
The screen initialization as well as some I/O mux assignments are completed in U-Boot. The current U-Boot does not support 24/32bpp Serial RGB LCD interface operation, so I added the Serial RGB support by referring the implementation of iMX FrameBuffer in Linux Kernel. The code has not yet been upstreamed to the master but I should do this later.
https://twitter.com/imbushuo/status/1190149993182576640
Demo of Serial RGB LCD Interface’s operation in U-Boot
UEFI just picks up the FrameBuffer allocated by U-Boot and registers the Graphics Output Protocol. Unfortunately the screen resolution cannot satisfy the minimum 80*25 console requirements, so I patched Console DXE components by adding a new 40*12 mode.
https://twitter.com/imbushuo/status/1190482299940245505
EDK2 component asserts because the screen resolution cannot meet 80*25 minimum console requirements.
Prior to the hack in console components I did some trick by reporting 640*480 output in the UEFI GOP protocol. The trick also helped me getting diagnostics information from Windows Boot Manager because 320*240 isn’t sufficient to display the error code. I periodically run block-transfer (BLT) from FrameBuffer to the filesystem and saved BMP files on a USB drive.
https://twitter.com/imbushuo/status/1190511363161935872
Hack that makes 80*25 mode available with some graphical artifacts
https://twitter.com/imbushuo/status/1191278476461891585
The 40*12 mode
https://twitter.com/imbushuo/status/1192181323282038784
Windows Boot Manager’s behavior in 320*240
https://twitter.com/imbushuo/status/1192715303840387074
Block-transfer (BLT) on the FrameBuffer
As for USB support, fortunately the USB IP in iMX6 is fully standard-compliant and in-box support is included in recent Windows versions. Therefore once I configured the OTG controller into host mode and pass the MMIO region to UEFI and Windows, it works magically without problems. However, Wenting and me have not yet figured out the VBus source on the OTG port (we assume some DC-DC circuit that controlled by SoC or PMIC but this hypothesis has not yet been verified) so the port has to be powered using some external voltage source for proper functionalities.
SMBIOS
Make sure you properly report memory size and address regions in SMBIOS, or Windows will do some incredibly weird things (TM).
Windows debugger fails to read memory due improper SMBIOS configuration.
Timer and HAL Extensions
https://twitter.com/imbushuo/status/1196937237021118464?s=20
Windows will access to some assumed and invalid memory address if no usable system timer is found.
Windows will fail to boot if no Interrupt Controller or System Timer is found. A system time can be registered using either GTDT table (ARM architectural table) or HAL extensions via CSRT table.
iMX6ULL comes with three timer complexes: iMX GPT (Generic Purpose Timer), EPIT (Enhanced Periodic Timer) and ARM Architectural Timer. ARM Architectural Timer is new to iMX6UL/ULL since earlier iMX6 SoCs use cores such as Cortex A15 which lacks proper Architectural Timer support. Microsoft has implemented a EPIT HAL extension for the system timer, but it was not loaded at first due to a hardware ID mismatch in CSRT table and system image (I downloaded an earlier FFU for iMX6 Solo.) So in the case when no timer is available, Windows will desperately attempt to initialize an assumed timer and the system crashes due to invalid memory access.
The iMX6ULL datasheet does mention the presence of architectural timer but doesn’t say much about the initialization procedure. The EDK2 code can initialize the timer counter but GIC PPI (Per Processor Interrupt) does not work properly. However, Windows successfully initializes and utilizes the timer via GTDT table.
At this moment the UEFI utilizes EPIT timer and turns it off upon the hand-off to Windows. Windows picks up the generic timer and initializes it then. At some later point the UEFI might switch to generic timer too, with the proper initialization procedure.
A few peripherals needs the Smart DMA (SDMA) controller HAL extension and it isn’t too hard to load it.
Show me that in action!
[Security] 3rd party image[0] can be loaded after EndOfDxe: VenHw(0D51905B-B77E-452A-A2C0-ECA0CC8D514A,004118020000000000)/USB(0x0,0x0)/USB(0x2,0x0)/HD(1,GPT,F9ADAEA9-E8DE-4291-B191-5DABA6DC1215,0x800,0x100000)/\efi\boot\bootarm.efi.
InstallProtocolInterface: 5B1B31A1-9562-11D2-8E3F-00A0C969723B 8F257028
ConvertPages: failed to find range 10000000 - 100EEFFF
Loading driver at 0x0008E9D6000 EntryPoint=0x0008E9E7511 bootmgfw.efi
InstallProtocolInterface: BC62157E-3E33-4FEC-9920-2D3B36D750DF 8F28EB10
ProtectUefiImageCommon - 0x8F257028
0x000000008E9D6000 - 0x00000000000EF000
InstallProtocolInterface: 752F3136-4E16-4FDC-A22A-E5F46812F4CA 8FBFF88C
ConvertPages: failed to find range 102000 - 102FFF
Disabling EPIT timer on ExitBootServicesEventSetUefiImageMemoryAttributes - 0x000000008F78A000 - 0x0000000000003000 (0x0000000000000008)
SetUefiImageMemoryAttributes - 0x000000008F787000 - 0x0000000000003000 (0x0000000000000008)
SetUefiImageMemoryAttributes - 0x000000008F784000 - 0x0000000000003000 (0x0000000000000008)
SetUefiImageMemoryAttributes - 0x000000008F77F000 - 0x0000000000005000 (0x0000000000000008)
SetUefiImageMemoryAttributes - 0x000000008F77C000 - 0x0000000000003000 (0x0000000000000008)
SetUefiImageMemoryAttributes - 0x000000008F779000 - 0x0000000000003000 (0x0000000000000008)
https://twitter.com/imbushuo/status/1197088581963010049?s=20
A video that demonstrates Windows 10 IoT on a calculator.
Where’s Secure Boot and TPM?
There are not really necessary. However, since OP-TEE has iMX6/7/8 support, you can run a secure monitor in TrustZone (TZ) and provide those services via Secure Monitor Calls from EL1/PL1.
In fact the official iMX Windows IoT implementation comes with OP-TEE bundled. I skipped it due to the concern of memory utilization.
But what about drivers?
Windows 10 IoT BSP repository have a lot of available iMX6/7/8 driver sources available. USB works out of box as mentioned. The calculator touchscreen and keypad drivers need to be implemented.
The touchscreen and keypad drivers are available in the Linux kernel tree so it should not be too hard to port them on Windows.
Can it boot Windows RT 8.1?
Maybe. Update: Windows RT 8.1 won’t boot, but later versions do. Windows PE won’t boot in ramdisk mode because 256MB memory isn’t sufficient. I haven’t got flat boot mode work, it enters some loop with no further initialization once devices are enumerated (including the USB drive I used.)
https://twitter.com/imbushuo/status/1198156294231736321
HP Prime G2 calculator runs Windows, running calculator application.
https://twitter.com/imbushuo/status/1198156505997930497
Notepad running on the calculator
But I want to boot Linux too!
You have two options:
- Just use U-Boot to load zImage, device tree and initrd
- Move FD, MpPark and FrameBuffer memory regions to the top of the system memory region and leave the lower 128MB memory unoccupied
Load Linux via either GRUB or directly from EFISTUB. It boots and here’s a snippet of the boot log:
EFI stub: Exiting boot services and installing virtual address map…
Disabling EPIT timer on ExitBootServicesEventSetUefiImageMemoryAttributes - 0x000000008F97B000 - 0x0000000000003000 (0x0000000000000008)
SetUefiImageMemoryAttributes - 0x000000008F978000 - 0x0000000000003000 (0x0000000000000008)
SetUefiImageMemoryAttributes - 0x000000008F973000 - 0x0000000000005000 (0x0000000000000008)
SetUefiImageMemoryAttributes - 0x000000008F970000 - 0x0000000000003000 (0x0000000000000008)
SetUefiImageMemoryAttributes - 0x000000008F96D000 - 0x0000000000003000 (0x0000000000000008)
SetUefiImageMemoryAttributes - 0x000000008F96A000 - 0x0000000000003000 (0x0000000000000008)
Booting Linux on physical CPU 0x0
Linux version 4.14.98-g371433a62906-dirty (imbushuo@bc-macbookpro) (gcc version 7.4.1 20181213 [linaro-7.4-2019.02 revision 56ec6f6b99cc167ff0c2f8e1a2eed33b1edc85d4] (Linaro GCC 7.4-2019.02)) #2 PREEMPT Thu Nov 14 03:10:29 EST 2019
CPU: ARMv7 Processor [410fc075] revision 5 (ARMv7), cr=10c53c7d
CPU: div instructions available: patching division code
CPU: PIPT / VIPT nonaliasing data cache, VIPT aliasing instruction cache
OF: fdt: Machine model: HP Prime G2 Calculator
Memory policy: Data cache writeback
efi: Getting EFI parameters from FDT:
efi: EFI v2.70 by EDK II
efi: ACPI 2.0=0x8f49b000 SMBIOS=0x8f9a8000 SMBIOS 3.0=0x8f9a6000
OF: reserved mem: failed to allocate memory for node 'linux,cma'
CPU: All CPU(s) started in SVC mode.
Built 1 zonelists, mobility grouping on. Total pages: 64516
Kernel command line: zImage.efi root=/dev/ram0 rw initrd=/rootfs.cpio.gz dtb=/imx6ull-14x14-prime.dtb
If you are using the NXP Linux configuration, it will eventually panic because it reads initrd memory address from device tree or some predefined configuration settings and of course the initrd is loaded as somewhere else in UEFI. You need to remove those settings and make the configuration more generic.
But why?
People talk about running Windows on random devices so I want to troll them by the end of the year.
How about other projects? What’s the next thing?
They will continue. So far, I need to implement more drivers, enable UEFI boot from on-board NAND, implement a calculator-friendly UEFI boot user interface and explore the possibility of booting stock HP PPL OS from my UEFI.
Acknowledgements
This project is partially derived from Microsoft’s Windows 10 IoT BSP for NXP SoC platforms. I would also like to thank Wenting for sending me his Prime G2 with UART lines soldered for debugging.