Microsoft® Windows® CE 5.0
Learn about the initial, low-level startup sequence and the hardware platform functions that are performed when the boot loader and OEM abstraction layer (OAL) are developed and the kernel is run. The startup sequence is an integral part of developing the OAL for a board support package (BSP), and the development process must be implemented correctly for initializing the CPU as well as on-chip and off-chip peripherals.
The process described in this article is the minimum set of functions that need to be implemented. It might be necessary for user-defined functions to be implemented where appropriate, depending on the specific device and peripheral set you are developing. Although the startup sequence for the different hardware platform CPU architectures that Windows CE supports is very similar, for the sake of simplicity, this article discusses the ARM kernel startup sequence.
Creating a BSP is the initial development activity required for building a Windows CE-based device. You can develop a BSP on your own or start from a BSP provided by Microsoft or a third party. Leveraging an existing BSP can greatly reduce the amount of development work required.
Regardless of the starting point you choose, the development process and startup sequence for the boot loader and the kernel are almost the same. This article describes this startup sequence and the functions you must implement to develop a boot loader and kernel that will boot Windows CE on a device. It also provides information about the recommended functionality that should be implemented in each function that is called by the boot loader and kernel. Because several of the functions calls do very similar actions, the kernel startup work can leverage the work done to develop the boot loader.
Like many development tasks, the process for developing a BSP involves high-level and low-level tasks. For example, at the highest level, you must select the hardware platform, and at the lowest level, you must develop a boot loader and the OEM abstraction layer (OAL) for the kernel. The following illustration shows the high-level steps needed to develop the low-level software for a BSP.
You begin by selecting or developing the hardware platform that the BSP will target. This includes developing a board with a CPU that can run the Windows CE operating system (OS), selecting the peripheral devices that will interface with the device, and including the interfaces required to support BSP development and debugging. For example, you might not need to include support for serial connections on a device for consumers, but a serial connection is typically important for low-level software development. You should consider populating your device with a debug header that can interface to a debug board for development, but can be depopulated before the device is used for production.
After you have identified and created the hardware platform, you must develop a BSP that will run Windows CE on the device. Often, you can modify a BSP from Microsoft or a third party, which will greatly reduce your BSP development work. The process of starting with a BSP from Microsoft is called BSP cloning. You can find information about this process in the Windows CE Help documentation. If a BSP is not available, you will have to develop one entirely on your own. Because developing a BSP entirely on your own is a significant amount of work, Microsoft recommends that you start with a BSP from Microsoft or a third party.
At this point, you move to the low-level development tasks by developing a boot loader that can reside in persistent memory on the device. The boot loader's primary function is to initialize enough of the hardware and CPU to allow the hardware to communicate with the development environment for downloading a Windows CE-based run-time image. You can extend the boot loaders functionality to be used for any other purpose that your device requires. Later sections of this paper discuss the low-level startup functions that you must implement in the boot loader.
After a boot loader has been developed, you download it and write it into persistent memory on the device using the tools specified by the silicon vendor. You then start development work on the OAL portion of the BSP. Part of the OAL work consists of implementing the bootstrapping kernel functions required to start the Windows CE kernel. Developing the OAL is a step-by-step process of implementing the startup functions and implementing the code that will initialize the hardware on the device in preparation for running the Windows CE kernel. You can leverage or share many of the startup functions that are implemented in the boot loader during OAL development. Once you have developed, downloaded, and debugged the OAL, a minimal Windows CE kernel will be running on the hardware platform.
Next, you add support for the peripherals that you have chosen for the device. To do this, you have to iteratively add and debug the drivers for these peripherals one at a time. If you have multiple developers working on drivers, the drivers can be developed in a series and added to the BSP one at a time. Depending on what peripherals you have chosen, or if you are using a System-On-Chip (SOC), you can add drivers from Microsoft or from a third party. You can leverage these drivers as the starting point for each driver that you develop.
Once the device drivers have been developed, you should have a BSP with an OAL and the drivers for each peripheral that you intend to support on the hardware platform. Next, you will plan and implement power management.
Power management is an important part of the driver and BSP development process. Once you have planned your power management system, which includes deciding to what extent your device will rely on battery power, you will use the Power Manager to help you implement those power management capabilities. Although power management is not the focus of this article, you can find documentation that explicitly covers power management in Windows CE Help and the MSDN Library. There you will find details about how to make each peripheral driver power-management-aware, as well as how drivers interact with the Power Manager and OAL.
As you develop the OAL, bring up device drivers, and implement power management, you must test each component. The Windows CE Test Kit (CETK) provides a wide variety of tests to help you with this process. For information about the CETK, see Windows CE Help and the MSDN Library.
The final steps in the BSP development process are to create a software development kit (SDK) and package the BSP into an .msi install file so that it can be installed by others. An SDK is a set of headers, libraries, connectivity files, run-time files, OS design extensions, and Help documentation that developers use to write applications for a specific OS design. The contents of an SDK allow developers to create and debug an application on the run-time image built from your OS design. Windows CE provides an SDK Wizard to create an SDK from your BSP and an Export Wizard to package your BSP into an .msi install file.
For detailed information on all aspects of the BSP development process, see Windows CE documentation.
For the most part, the boot loader and kernel share the same Startup functions, as well as some of the code that is called out in the subsequent functions. Consequently, you can leverage or share much of the code initially implemented in the boot loader startup sequence during the kernel startup sequence.
The following table shows the startup functions used by the boot loader and the kernel for an ARM-based startup sequence. The startup sequence for the kernel calls into functions that are part of the shared-source license agreement that installs code into the private tree. Because of this, you should sequence the functions as they are shown below. Within each function you might need to customize the order in which components are initialized, depending on your CPU, memory, and peripheral set.
|Boot Loader Startup Sequence||Kernel Startup Sequence|
Function calls in bold text denote functions that OEMs need to implement.
Documentation is provided in Platform Builder and on MSDN that covers the step-by-step process, with a checklist, for bringing up a boot loader and OAL.
The following table shows the boot loader startup sequence.
|Boot Loader Startup Sequence|
Functions in bold text denote functions that OEMs need to implement.
Startup() -> Startup.s
The boot loader Startup function is the first code that is run on the device after a hardware reset or run-time reset, and when the system wakes up. This function performs the following tasks:
- Sets the device to supervisor mode
- Performs the necessary initialization for the following hardware:
- Memory controller
- System clocks
- Translation look-aside buffers (TLBs)
In the case of a wakeup event, the Startup function reads the saved address for the wakeup event, and then jumps to that location. If it is not a wakeup event, the boot loader is copied to memory and run.
The Startup code is typically located in the Startup.s file located in the %_WINCEROOT%\Platform\<Hardware Platform Name>\Src\Bootloader or %_WINCEROOT%\Platform\<Hardware Platform Name>\Src\Bootloader\Eboot directory. The Startup code is typically in assembly language because it is the first code that runs on the device and needs to do low-level hardware initialization and communication with the CPU.
Because each CPU and hardware platform requires different initialization, you must modify the Startup.s file, depending on the CPU you are using. The silicon vendor provides the CPU initialization information. The Startup code can be shared with the OAL and can be reused later during that part of the development process.
EbootMain() -> Main.c
Depending on the CPU you are using for BSP development, EbootMain is typically the entry point for the C code to be run. EbootMain is typically located in the Main.c file in the %_WINCEROOT%\Platform\<Hardware Platform Name>\Src\Bootloader directory or in the %_WINCEROOT%\Platform\<Hardware Platform Name>\Src\Bootloader\Eboot directory. Although Main.c can perform any initializations on the device, it should end with a call to the BLCOMMON library entry point, BootloaderMain.
BootloaderMain() -> Blcommon.c
The BLCOMMON library entry point is the BootloaderMain function. BootloaderMain is the primary call in the BLCOMMON library that implements a common and consistent framework for a boot loader. The BLCOMMON library source file is located in the Blcommon.c file in the %_WINCEROOT%\Public\Common\Oak\Drivers\Ethdbg directory.
The BLCOMMON library provides the following functionality:
- Relocates the boot loader to RAM for faster execution
- Decodes the .bin file contents
- Verifies checksums
- Keeps track of load progress
The BLCOMMON library calls well-defined OEM functions throughout the process to handle hardware platform or solution-specific customizations.
OEMDebugInit() -> Main.c
OEMDebugInit is the first function called from the BootloaderMain function when the boot loader is started. This call is typically used to initialize serial UART debugging output. The OEM often chooses to call the OEMInitDebugSerial function to initialize a serial UART for debugging messages.
After OEMDebugInit completes, the Windows CE banner appears indicating that this interface is available. The OEM function interface can use OEMWriteDebugString, OEMWriteDebugByte, and OEMReadDebugByte after OEMDebugInit completes.
The OAL and the boot loader can share this implementation.
OEMDebugInit is located in the Main.c file in the %_WINCEROOT%\Platform\<Hardware Platform Name>\Src\Bootloader OR %_WINCEROOT%\Platform\<Hardware Platform Name>\Src\Bootloader\Eboot directory.
OEMPlatformInit() -> Main.c
OEMPlatformInit is the next function called from BLCOMMON after the OEMDebugInit function completes and the Windows CE banner is displayed.
OEMPlatformInit is an OEM hardware platform initialization function for the clock, PCI interface, or NIC interface. OEMPlatformInit is also where the interruption in the boot up process takes place to display the boot loader menu. The NIC interface for downloading the image is assumed for the following download functions.
OEMPlatformInit is located in the Main.c file in the %_WINCEROOT%\Platform\<Hardware Platform Name>\Src\Bootloader or %_WINCEROOT%\Platform\<Hardware Platform Name>\Src\Bootloader\Eboot directory.
OEMPreDownload() -> Main.c
OEMPreDownload is also called from BLCOMMON before actually downloading a run-time image. This function retrieves the IP address for the device and connects to the development workstation. You can customize OEMPreDownload to prompt the user for feedback to obtain a static IP address or skip the download and jump to an image that might already exist on the device. The preferred method is to use the settings provided in Platform Builder.
Return values for OEMPreDownload are typically obtained through a call to EbootInitEtherTransport, which retrieves this information from Platform Builder. You can select downloading or booting from a resident run-time image by options passed from Platform Builder to the boot loader through EbootInitEtherTransport. You can also set up OEMPreDownload to read hardware switch settings to determine the code that should be returned.
The following tables shows the possible return codes for the OEMPreDownload function.
|BL_DOWNLOAD (0)||The Ethernet transport should be initialized by calling EbootInitEtherTransport.|
|BL_JUMP (1)||The download function in the BLCOMMON framework is bypassed.|
|BL_ERROR (-1)||Error condition.|
If a signed run-time image has been downloaded, the signature is checked and verified before calling OEMLaunch.
OEMPreDownload is located in the Main.c file in the %_WINCEROOT%\Platform\<Hardware Platform Name>\Src\Bootloader or %_WINCEROOT%\Platform\<Hardware Platform Name>\Src\Bootloader\Eboot directory.
OEMLaunch() -> Main.c
OEMLaunch is the last boot loader function called out of BLCOMMON and is responsible for jumping to and launching the run-time image.
OEMLaunch can use the EbootWaitForHostConnect function, which is defined in Eboot.lib, to wait for returned arguments when you have chosen passive KITL or not to connect to the target device at boot time. Other flags are set to define the transport mechanism as a serial, USB, or Ethernet connection.
OEMLaunch jumps to the first instruction specified by the dwLaunchAddr parameter, which is the location of the run-time image Startup function. Startup calls the KernelStart function, as defined below. The launch address is obtained from the .bin file and is stored in the length field of the last block.
At this point, the boot loader is done executing code and the Startup function in the OAL is called.
OEMLaunch is located in the Main.c file in the %_WINCEROOT%\Platform\<Hardware Platform Name>\Src\Bootloader or %_WINCEROOT%\Platform\<Hardware Platform Name>\Src\Bootloader\Eboot directory.
The following table shows the startup functions used by the kernel for an ARM-based startup sequence. The startup sequence for the kernel requires calls into functions that are part of the shared-source license agreement that installs code into the private tree. Because of this you should sequence the functions as they are shown below. Within each function you might need to customize the order in which components are initialized depending on your CPU, memory and peripheral set.
|Kernel Startup Sequence|
Functions in bold text denote functions that OEMs need to implement.
ARM Kernel Startup Overview
Startup() -> Startup.s
The kernel Startup function is the first code that runs after the boot loader jumps to the run-time image that is now on the device, or when the development boot loader is removed and a production boot loader is flashed to the device and kernel Startup is called from the production boot loader.
Although in this example kernel Startup is a different source file than the boot loader Startup function, they have similar code and can be shared. Startup is responsible for detecting a hardware or run-time reset or detecting when the system wakes up. The kernel Startup function performs the necessary hardware initialization, including initializing the CPU, memory controller, system clocks, serial port, and caches, and detecting when the system wakes up.
Kernel Startup code is typically located in the Startup.s file in the %_WINCEROOT%\Platform\<Hardware Platform Name>\Src\Kernel\OAL directory. This directory can also share the boot loader Startup.s code. The Startup code is typically in assembly language because this is the first code that runs on the device and needs to perform low-level hardware initialization.
Because each CPU and hardware platform requires different initialization, you must modify the file containing the kernel Startup function depending on the CPU you are using. The silicon vendor provides this CPU initialization information.
Kernel Startup is responsible for computing the physical address for the OEMAddressTable and loading it into memory for use by the KernelStart function. The Startup function sets up any hardware platform or CPU-specific configurations that are required for the kernel to access ROM and DRAM, and then jumps to KernelStartup. The Startup routine should initialize any processor-specific cache or memory management unit (MMU), but not enable them
KernelStart() -> Armtrap.s (Private)
The KernelStart function is the main entry point for the kernel. The KernelStart function initializes the first-level page table based upon the contents of the MemoryMap array and enables the MMU and caches. Kernel symbolic addresses are not valid and must be translated through the MemoryMap array to find the correct physical address until the MMU is enabled. KernelStart also initializes the stacks for each mode of operation, and initializes global data for the kernel.
The KernelStart function is located in the Armtrap.s file in the %_WINCEROOT%\Private\Winceos\Coreos\Nk\Kernel\ARM directory.
ARMInit() -> Mdarm.s (Private)
The ArmInit function, which is the main ARM initialization function, is responsible for calling the OEM-supplied initialization functions. ARMInit makes the following function calls:
- Calls OEMInitDebugSerial to initialize the debug serial port through OEMInitDebugSerial
- Uses OEMWriteDebugString to display the Windows CE banner
- Calls OEMInit to perform hardware platform initialization
The ARMInit function is located in the Mdarm.c file in the %_WINCEROOT%\Private\Winceos\Coreos\Nk\Kernel\ARM directory.
OEMInitDebugSerial() -> Mdarm.s (Private)
The OEMInitDebugSerial function initializes the debug serial port on the device. The debugging functions are: OEMDebugInit, OEMWriteDebugString, OEMWriteDebugByte, and OEMReadDebugByte.
The code for OEMInitDebugSerial is very similar to the OEMDebugInit function called from the boot loader. Therefore, most of the OEMDebugInit code can be reused or shared.
OEMInit() -> Init.c
The kernel calls OEMInit after it has performed minimal initialization in ARMInit. When OEMInit is called, interrupts are disabled and the kernel is unable to handle exceptions. OEMInit initializes the following hardware:
- All other board-level hardware peripherals needed to support the hardware platform
Note If a KITL connection is being used, then the KITL connection can also be initialized during OEMInit through a call to the OALKitlStart function.
The OEMInit function contains much of the functionality present in the OEMPlatformInit function in the boot loader.
The OEMInit function is a kernel call from ARMInit and can be found in the Init.c file in the %_WINCEROOT%\Platform\<Hardware Platform Name>\Src\Kernel\OAL directory.
KernelInit() -> Kwin32.c (Private)
The KernelInit function is where the kernel is initialized. KernelInit calls functions to initialize the following kernel components:
- Memory pool
- Kernel process
When KernelInit returns, the kernel is ready to schedule the first thread through SystemStatupFunc.
The KernelInit function can be found in the Kwin32.c file in the %_WINCEROOT%\Private\Winceos\Coreos\Nk\Kernel directory.
HeapInit() -> Heap.c (Private)
The HeapInit function initializes the kernel heap and is located in the Heap.c file in the %_WINCEROOT%\Private\Winceos\Coreos\Nk\Kernel directory.
InitMemoryPool() -> Physmem.c (Private)
The InitMemoryPool function initializes the kernel memory pool and is located in the Physmem.c file in the %_WINCEROOT%\Private\Winceos\Coreos\Nk\Kernel directory.
ProcInit() -> Schedule.c (Private)
The ProcInit function initializes the kernel process and is located in the Schedule.c file in the %_WINCEROOT\Private\Winceos\Coreos\Nk\Kernel directory.
SchedInit() -> Schedule.c (Private)
The SchedInit function initializes the scheduler and is located in the Schedule.c file in the %_WINCEROOT\Private\Winceos\Coreos\Nk\Kernel directory.
SchedInit creates the SystemStatupFunc thread.
FirstSchedule() -> Schedule.c (Private)
The FirstSchedule function starts the scheduler and is located in the Schedule.c file in the %_WINCEROOT\Private\Winceos\Coreos\Nk\Kernel directory.
SystemStartupFunc() -> Schedule.c (Private)
The SystemStartupFunc function is called after all the required initialization has been completed and when the system is ready to schedule startup and run kernel threads. SystemStartupFunc is where calls are made to start kernel monitoring through CreateKernelThread, and execute IOCTL_HAL_POSTINIT through the OEMIoControl function.
SystemStartupFunc is located in the Schedule.c file in the %_WINCEROOT\Private\Winceos\Coreos\Nk\Kernel directory.
All Windows CE device development begins by creating a targeted BSP for the device. The BSP also becomes the foundation for applications that run on the device by exporting an SDK from the BSP. Because of this, you must be sure that the high level BSP process is followed and the low-level function calls for the boot loader and kernel are implemented appropriately. With the information from this article, you should have a fundamental understanding of the BSP process and what the development effort is to get a boot loader and kernel running on a device.