Windows CE Running on Raspberry Pi 5?
It's useless, yes I know, but is fun and I learned a lot of things. I had never built Windows CE from "scratch" before, and it was a good opportunity to feel how it was the experience to develop for Windows CE, from the BSP to application.
The Windows CE, or Windows Embedded Compact, is a real-time operating system developed by Microsoft for embedded systems. It is operating system with a small footprint and a very specific set of features. It was designed to be a real-time operating system for devices that have a small amount of resources, like a PDA, a GPS, or a set-top box. For sure you have used a device with Windows CE, but you didn't know.
It was released, the first version, in 1996 (I had 6 years), and the last version, 7.0, in 2013. It has been on extended support since 2018. Although Microsoft still allows license sales to continue until 2028, it is not recommended for new projects. In other words, you could consider that Windows CE is dead. No one in their right mind would start a new project with Windows CE.
There is no official support for Windows CE on Raspberry Pi (any hardware version). And there will never be. But, as I said, it was a fun project to try. To start I had to think on what is needed to run Windows CE on a device. The first thing is the BSP (Board Support Package). The BSP is a set of drivers and configuration files that allow the operating system to run on a specific hardware. The second thing is the operating system itself. The Windows CE is a modular operating system, and you can choose what you want to include in the image, and the BSP has the implementation of the HAL (Hardware Abstraction Layer) that is the bridge that glue the operating system with the hardware.
So, there is no Windows CE BSP for Raspberry Pi 5. Write one from scratch, using the calls and HAL architecture that Windows CE expects, is a huge task. I'm want only to have fun, not to work hard. So, shortcuts are needed. After some web researching I found a BSP for QEMU. More specific, for the vexpress-a15
machine. Great, so, I can build Windows CE based on this BSP and run it on QEMU under Raspberry OS.
The only way to generate a Windows CE image is using Visual Studio 2008 with the Platform Builder plugin installed. The source code for Windows CE is not available, what the Platform Builder does is build the BSP and then link it with the OS components and generate the image.
The experience is great and straightforward. You create a new platform builder project, select the BSP, select the features that you want on your OS and then build the image. Done! It's really this simple. The only annoying thing is the installation process of the tooling, that takes a lot of time.
Well, build is easy, and debugging sometimes. Let me be fair, if you have the BSP running and ok the application debug is easy and smooth, really great remote target debugging experience. But for debug the BSP, the HAL, the drivers, it's a nightmare. As for any Kernel debugging. For Windows CE there is the KITL (Kernel Independent Transport Layer) that allows you to debug the kernel and drivers, but the serial should be working already, of course 😅. And I had another issue, I was trying to run it on Linux under QEMU. After some hard time, and thanks to help from @Milek7, I had my Windows CE customized and running under QEMU.
After this experience I could understand why so many developers love Windows CE. It's was really straightforward to develop for it, and the tools are easy and great. Also, I need to be fair, it was much simpler, is nothing compared to what a modern Linux OS can be configured for nowadays. On Linux ecosystem there are tons of configurations and options, this sometimes scares the developers.
This is the script that I used to run Windows CE on QEMU:
#!/bin/bash
# Run QEMU in the background and redirect output
(qemu-system-arm \
-global virtio-mmio.force-legacy=false \
-machine type=vexpress-a15 \
-m 512 \
-smp 4 \
-accel tcg,thread=multi \
-device loader,addr=0x80000000,file=NK.bin.raw \
-device loader,addr=0x800010d0,cpu-num=0 \
-device loader,addr=0x84000000,file=dev.bin \
-device virtio-tablet-device \
-blockdev driver=file,filename=disk.img,node-name=d0 \
-device virtio-blk-device,drive=d0 \
-audiodev id=none,driver=none \
-monitor telnet:127.0.0.1:55555,server,nowait \
-serial pty \
-serial stdio \
) &> /tmp/qemu-output.log &
QEMU_PID=$!
# polling the monitor output
while true; do
CMD_OUTPUT=$(echo "xp /16ch 0x84000000" | nc -w 1 127.0.0.1 55555 | tr -d '\000')
# echo "$CMD_OUTPUT"
if [[ "$CMD_OUTPUT" == *"'P' 'O' 'W' 'E' 'R' 'O' 'F' 'F'"* ]]; then
echo "POWEROFF"
pkill qemu
exit 0
fi
sleep 2
done &
# Tail the QEMU output, check for a specific string
tail -f /tmp/qemu-output.log | while read -r line; do
if [[ $line == "ON" ]]; then
echo "Matched line: $line"
gpioset 0 23=1
fi
if [[ $line == "OFF" ]]; then
echo "Matched line: $line"
gpioset 0 23=0
fi
done
There are some interesting things in this script.
-device loader,addr=0x80000000,file=NK.bin.raw \
-device loader,addr=0x800010d0,cpu-num=0 \
These loader to first load the Windows CE image and then to point the PC
(Program Counter) to the entry point of the image. So, only load the image is not enough, you need to point the CPU to the entry point.
-device loader,addr=0x84000000,file=dev.bin \
This was a hack that I did to make the Processor Type
name to be loaded dynamically. The Windows CE image has a string with the processor type name, and this is used by the control panel to show the processor name. So, I created a dev.bin
file with the string that I want to show, and then I load it on the memory RAM, under the WinCE image. And on the BSP I read it:
char *ramData = (char *)0x84000000;
int i = 0;
while (*ramData != 0) {
proc_info->szVendor[i] = (WCHAR)*ramData;
i++;
ramData++;
}
At BSP level, is Kernel level, we have access to the physical memory directly, no need to translate the virtual address to physical address.
CMD_OUTPUT=$(echo "xp /16ch 0x84000000" | nc -w 1 127.0.0.1 55555 | tr -d '\000')
Another hack was the way I set the script to "turn off" the QEMU. I wrote a string on the memory, at same address from the processor name, and the used the QEMU monitor, that was running in a telnet server, to read the memory and check if the string was there. If it was, then I turn off the QEMU.
The suspend ioctl was not implemented, and I was too lazy to implement it. So, I simply get the id of the ioctl and write the string on the memory. It was a fun way to turn off the QEMU:
case 16842920:
ramData = (char *)0x84000000;
// write the poweroff string to the ram address
ramData[0] = 'P';
ramData[1] = 'O';
ramData[2] = 'W';
ramData[3] = 'E';
ramData[4] = 'R';
ramData[5] = 'O';
ramData[6] = 'F';
ramData[7] = 'F';
ramData[8] = 0;
_flush();
DEBUGMSG(1, (TEXT("OEMIoControl: POWEROFF\r\n")));
return FALSE;
}
I had to blink a LED on the Raspberry Pi 5 board. An demo without a blinking LED is not a demo. So, access the GPIO from the Windows CE was impossible. But, I had the serial port 'COM1' implemented. So, I wrote a simple application that writes to the serial port, and on the script it reads the output from stdio:
-serial pty \
-serial stdio \
The first serial, the Kernel logging, I redirect to a pty, and the second serial, the application serial, I redirect to stdio. So, I can read the output from the application on the script, that I also redirect to a file at end of the QEMU command:
) &> /tmp/qemu-output.log &:
# Tail the QEMU output, check for a specific string
tail -f /tmp/qemu-output.log | while read -r line; do
if [[ $line == "ON" ]]; then
echo "Matched line: $line"
gpioset 0 23=1
fi
if [[ $line == "OFF" ]]; then
echo "Matched line: $line"
gpioset 0 23=0
fi
done
It was a fun project, and I learned a lot of things. Feel how Windows CE dev experience was, was important to me. So, I can understand now the pain that developer that are migrating fells, and get inspiration to build better tools and experiences for Linux developers.
The QEMU is a formidable tool, and I'm always impressed by the things that you can do with it. The QEMU monitor is a powerful tool, and I'm sure that I will use it more in the future.
The Windows CE is dead, but it was a great OS for the time that it was released. I'm sure that I will use some of the concepts and ideas in the future.
“You have to know the past to understand the present.” - Carl Sagan
@Milek7 wince-qemu repo: https://github.com/Milek7/wince-qemu
My custom repo: https://github.com/microhobby/wince-qemu
⚠️ Disclaimer: Sorry, I can't provide the Windows CE image by legal reasons. You need to have the licenses to build it, run it or distributed it.