How to Develop an OEM Adaptation Layer

Filename: HowToDevelopAnOAL.doc
1
[This topic is part of a beta release and is subject to change in future releases.]
How to Develop an OEM Adaptation Layer
An OEM adaptation layer (OAL) is a layer of code between the Windows CE kernel and the
hardware of your target device. You develop an OAL to communicate data between your
operating system (OS) and your target device and include code to handle interrupts,
timers, and so on. For more information about the OAL, see OEM Adaptation Layer.
Some of the startup code for your OAL may already be completed if you already
implemented a boot loader. For example, you can reuse parts of the StartUp function and
debug routines that were created during the boot loader development process. Conversely,
if you have not already developed a boot loader, the following steps do cover
implementation of stand-alone OAL startup code. For more information about developing
a boot loader, see How to Develop a Boot Loader.
Hardware and Software Assumptions
•
You developed and built your boot loader code; public headers and libraries have gone
through the Sysgen phase.
•
You are developing the OAL without assistance from CSP OAL code. If CSP libraries and
a driver exist for your hardware, use them. The libraries and driver help shorten the
time during development and test phases.
If you already have an existing OAL and want to migrate from a previous version of
Windows CE to Windows CE .NET 4.2, see Migration Issues and How to Migrate a
Board Support Package to Windows CE .NET 4.2.
The set of procedures presented in following steps provides only a basic kernel with a file
system and debug network support, and offers no support for drivers. To track your
progress in the following table, select the check box next to each step.
Note You need to create or modify other files outside of the OAL in order to create a
tiny kernel board support package (BSP). For example, you need to modify Config.bib,
which contains information about the RAM regions used by the OS.
<IN
PUT
TYP
E="
Che
ckb
ox"
Val
ue=
"ON
">
Step
Topic
1. Create a new platform directory and a subdirectory
for the OAL on your development workstation.
Not applicable
The following example shows the basic naming
convention for a platform called MyPlatform.
•
Platform directory:
%_WINCEROOT%\Platform\MyPlatform
•
OAL directory:
%_WINCEROOT%\Platform\MyPlatform\Kernel\Hal
You need to include the directories in the dirs file if you
want the directories to build as part of a platform build.
For more information about dirs file, see Dirs File.
Note To comply with directory naming
conventions, include CPU-specific or assembly
language files in a further subdirectory based on the
Filename: HowToDevelopAnOAL.doc
2
CPU name. For example, if the example platform is
ARM-based, then the StartUp code described in the
next step would be located in
%_WINCEROOT%\Platform\MyPlatform\Kernel\Hal
\ARM.
<IN 2. Implement the Startup function for the OAL.
Implementing the OAL
PUT
Note In general, StartUp initializes the CPU core, StartUp Function
TYP
including the SDRAM controller, memory
E="
management unit (MMU), and caches. The function
Che
performs this in preparation for running the
ckb
Windows CE kernel.
ox"
Parts of this code can be shared with your platform's
Val
boot loader. Be careful when initializing hardware
ue=
twice in this situation. OAL StartUp should function
"ON
whether the boot loader has run. For example, the
">
OAL should not attempt to enter x86 protected
mode if the boot loader has already done so. You can
typically obtain this function from a sample platform
using the same CPU core.
<IN 3. Create a sources file and a makefile to assemble and Creating the OAL
PUT compile the file containing the StartUp function.
Sources and Makefile
TYP
Files
E="
Che
ckb
ox"
Val
ue=
"ON
">
<IN
PUT
TYP
E="
Che
ckb
ox"
Val
ue=
"ON
">
4. Build the StartUp source file.
<IN
PUT
TYP
E="
Che
ckb
5. Create the kernel Buildexe directories and create a
dirs file to direct the build process.
This step verifies that the build files are correct and in
place. This step creates a Hal.lib binary in your
hardware platform's Lib directory.
The Windows CE kernel, of which the OAL is a part, is
an .exe file that is created as part of the BSP build
process.
Building the OAL Source
Code
Creating the Kernel
Buildexe Directory
Filename: HowToDevelopAnOAL.doc
ox"
Val
ue=
"ON
">
3
There are typically three different kernel variants: basic
kernel, kernel with KITL, and a profiling kernel. The
focus of this OAL development process is on the kernel
with KITL. You will need to create additional build
directories and sources files if you want to create the
remaining kernel images.
Note If you try to build the Kernkitl.exe image at
this stage, you will receive a number of unresolved
externals since a number of required functions have
not yet been implemented.
<IN
PUT
TYP
E="
Che
ckb
ox"
Val
ue=
"ON
">
6. Create stub versions of the following CPU-specific
OAL functions:
Creating Stubs for OAL
Functions
For ARM-based platforms only:
•
OEMARMCacheMode
•
OEMDataAbortHandler
•
OEMInterruptHandler
•
OEMInterruptHandlerFIQ
For MIPS-based platforms only:
•
CacheErrorHandler
•
CacheErrorHandler_End
For x86-based platforms only:
•
OEMNMIHandler
For SHx-based platforms only:
•
Not applicable
Note Create the source file with these stub
routines in the
%_WINCEROOT%\Platform\MyPlatform\Kernel\Hal
\<CPU> directory.
In this step and in the following steps, you will need
to create stub versions to resolve link-time
dependencies, and thus, allow for an OAL and a
kernel image to be built. You can then add
implementation details to each of the functions in an
ordered approach. The routines that follow are
roughly divided into functional areas. These areas
may lend themselves to being organized into
different source files. Any new source files should be
added to the sources file to ensure that they are
compiled and included in the OAL library.
<IN 7. Create stub versions of the following required OAL
PUT functions:
TYP • InitClock
E="
Note Required by DoPowerOff but usually
Che
called by OEMInit.
ckb
Creating Stubs for OAL
Functions
Filename: HowToDevelopAnOAL.doc
ox" •
Val •
ue=
•
"ON
">
<IN
PUT
TYP
E="
Che
ckb
ox"
Val
ue=
"ON
">
4
OEMInit
OEMCacheRangeFlush
OEMGetExtensionDRAM
8. Create stub versions of the following functions:
•
OEMDebugInit
•
OEMInitDebugSerial
•
OEMReadDebugByte
•
OEMWriteDebugByte
•
OEMWriteDebugString
•
OEMParallelPortGetByte
•
OEMParallelPortSendByte
Creating Stubs for OAL
Functions
Note The OEMParallelPortXXX functions are
obsolete. You do not need to implement them,
but stub versions must be created to build a
kernkitl image.
<IN 9. Create stub versions of the following functions:
PUT • OEMIdle
TYP
• OEMPowerOff
E="
Che
ckb
ox"
Val
ue=
"ON
">
Creating Stubs for OAL
Functions
<IN
PUT
TYP
E="
Che
ckb
ox"
Val
ue=
"ON
">
10. Create stub versions of the following interrupt
functions:
Creating Stubs for OAL
Functions
<IN
PUT
TYP
E="
Che
ckb
11. Create stub versions of the following real-time clock Creating Stubs for OAL
(RTC) functions:
Functions
•
OEMInterruptDisable
•
OEMInterruptDone
•
OEMInterruptEnable
•
SC_GetTickCount
•
OEMGetRealTime
•
OEMSetAlarmTime
•
OEMSetRealTime
Filename: HowToDevelopAnOAL.doc
5
ox"
Val
ue=
"ON
">
<IN 12. Create a stub versions of the following function:
PUT • OEMIoControl
TYP
E="
Che
ckb
ox"
Val
ue=
"ON
">
<IN
PUT
TYP
E="
Che
ckb
ox"
Val
ue=
"ON
">
13. Define the following global variables required by
Nk.lib.
Not applicable
Required for all platforms:
•
NoPPFS
Additional variable for ARM-based and x86-based
platforms only:
•
OEMAddressTable
Additional variable for MIPS-based platforms only:
•
OEMTLBSize
Additional variables for SHx-based platforms only:
•
<IN
PUT
TYP
E="
Che
ckb
ox"
Val
ue=
"ON
">
Creating Stubs for OAL
Functions
SH3CacheLines or SH4CacheLines
14. Build the kernel executable image, Kernkitl.exe,
when stub versions of all the functions and global
variables are defined.
Building the Kernel
Executable Image
Note At this point, the kernel image is not yet very
useful, but if you successfully create the resulting
Kernkitl.exe image, this establishes the OAL
framework and verifies that the build-related files
are configured properly.
After creating Kernkitl.exe, you can start adding
functionality to the image in stages.
For the remainder of the OAL development process,
each stage is implemented incrementally. However,
because there is no support for the kernel debugger,
you must perform debugging and verification at this
point using any of the following forms of debugging: a
hardware probe or debugger, serial prints, LED blinks,
or another form of debugging.
<IN 15. Implement the following CPU-specific
Microprocessor-Specific
Filename: HowToDevelopAnOAL.doc
PUT
TYP
E="
Che
ckb
ox"
Val
ue=
"ON
">
pre-OEMInitDebugSerial functions and global
variables:
6
Issues
For ARM-based platforms only:
•
OEMAddressTable
•
OEMARMCacheMode
•
OEMDataAbortHandler
For MIPS-based platforms only:
•
OEMTLBSize
•
CacheErrorHandler
•
CacheErrorHandler_End
For x86-based platforms only:
•
OEMAddressTable
•
OEMNMIHandler
For SHx-based platforms only:
•
SH3CacheLines or SH4CacheLines
The code for these functions can be copied from the OAL
for a platform using the same CPU, or by linking in the
CSP library for your CPU.
<IN
PUT
TYP
E="
Che
ckb
ox"
Val
ue=
"ON
">
16. Implement the OEMCacheRangeFlush function.
Implementing the
The code for this function can be copied from the OAL OEMCacheRangeFlush
for a platform using the same CPU, or by linking in the Function
CSP library for your CPU.
<IN 17. Stop and ensure that the image builds.
PUT
TYP
E="
Che
ckb
ox"
Val
ue=
"ON
">
<IN
PUT
TYP
E="
Che
18. Create a
%_WINCEROOT%\Platform\MyPlatform\Files directory
and create empty files in the new directory called
Platform.bib, Platform.reg, Platform.db, and
Not applicable
Not applicable
Filename: HowToDevelopAnOAL.doc
ckb
ox"
Val
ue=
"ON
">
7
Platform.dat.
Copy and edit a Config.bib file from a similar platform,
and then specify the MEMORY and CONFIG section
information for your platform.
Config.bib should minimally specify the NK and RAM
section address information. Romimage.exe uses this
address information to know how to organize the OS to
fit the hardware resources available on your standard
development board (SDB).
Note The addresses in Config.bib must be virtual,
not physical. ROMOFFSET should be used to ensure
that the OS image downloads to the correct physical
RAM or flash memory location.
For more information, see the following topics:
<IN
PUT
TYP
E="
Che
ckb
ox"
Val
ue=
"ON
">
•
MEMORY Section
•
CONFIG Section
•
Binary Image Builder File
•
Romimage
19. Create a
%_WINCEROOT%\Platform\MyPlatform\CESYSGEN
directory and copy the makefile file from a similar
platform into that directory.
Not applicable
For more information about makefiles, see Makefile
File.
<IN 20. Ensure that all appropriate platform directories have Not applicable
PUT dirs files.
TYP For more information, see Dirs File.
E="
Che
ckb
ox"
Val
ue=
"ON
">
<IN
PUT
TYP
E="
Che
ckb
21.From the command line, build an Nk.bin image by
entering the following command.
blddemo clean -q
The .bin file contains the Windows CE kernel, and
depending on the build flags, it can also contain other
Not applicable
Filename: HowToDevelopAnOAL.doc
8
ox" executables and support files.
Val For more information about the Windows CE build tool,
ue= see Build Tool.
"ON
">
<IN
PUT
TYP
E="
Che
ckb
ox"
Val
ue=
"ON
">
22. Verify that you can download the Nk.bin image and Not applicable
that you can boot up to at least the
OEMInitDebugSerial function in the OAL using any of
the following debugging tools: hardware debugger or
probe, LED write, or serial debug prints.
Put while(1); in your code to stop the boot.
Note Once OEMInitDebugSerial is reached, the
CPU will be executing in virtual address mode. Do
not use physical addresses, for example, when
writing to the debug serial port or LEDs, from this
point forward.
If OEMInitDebugSerial is never reached, you may
have incorrectly specified the OEMAddressTable
or are otherwise causing unmapped memory to be
accessed, triggering an abort or exception.
After completing this step, you have confirmed that the
kernel has set up its virtual memory structures or tables
and that caches, translation look-aside buffers (TLBs),
and write buffers are enabled.
<IN
PUT
TYP
E="
Che
ckb
ox"
Val
ue=
"ON
">
23. Implement the following serial debug functions.
Enabling the Debug
These are the same routines used by the boot loader so Serial Port
you can share the code with the OAL.
Implementing the
Serial Debug
• OEMInitDebugSerial
Functions
• OEMReadDebugByte
<IN
PUT
TYP
E="
Che
ckb
ox"
Val
ue=
"ON
24. Rebuild Nk.bin and verify that the boot process
proceeds through OEMInit.
•
OEMWriteDebugByte
•
OEMWriteDebugString
Note Sharing these functions between the boot
loader and OAL may require minor changes. For
more information, see Sharing Code Between the
Boot Loader and the OAL.
You should also verify that the kernel calls
OEMInitDebugSerial before it calls OEMInit and that
serial text is written to the initialized UART.
Not applicable
Filename: HowToDevelopAnOAL.doc
9
">
<IN 25. Implement the OEMInit function.
PUT
TYP
E="
Che
ckb
ox"
Val
ue=
"ON
">
<IN
PUT
TYP
E="
Che
ckb
ox"
Val
ue=
"ON
">
26. Implement the interrupt-related code.
Implementing an ISR
For all platforms:
•
OEMInterruptEnable
•
OEMInterruptDisable
•
OEMInterruptDone
For ARM-based platforms only:
•
OEMInterruptHandler
•
OEMInterruptHandlerFIQ
For non-ARM platforms:
•
An ISR that handles the 1 millisecond (ms) system
tick interrupt
<IN 27. Implement the power management functions:
PUT • OEMIdle
TYP
• OEMPowerOff
E="
Note Implementation of the OEMPowerOff
Che
function
is highly device-dependent and often
ckb
requires
changes to the boot loader to resume
ox"
from
the
suspend state. Implementing suspend
Val
and resume is beyond the scope of this
ue=
procedure. You may leave the OEMPowerOff
"ON
function
stubbed out.
">
<IN
PUT
TYP
E="
Che
ckb
ox"
Val
ue=
"ON
">
Implementing the
OEMInit Function
28. Rebuild the Nk.bin image and verify that it boots
successfully.
Using a hardware debugger or probe, serial prints, or
LEDS, verify that OEMInit returns and that OEMIdle is
eventually called.
Enabling Power
Management
Not applicable
Filename: HowToDevelopAnOAL.doc
<IN 29. Add KITL initialization code to OEMInit.
PUT
TYP
E="
Che
ckb
ox"
Val
ue=
"ON
">
10
Adding KITL Initialization
Code
<IN
PUT
TYP
E="
Che
ckb
ox"
Val
ue=
"ON
">
30. Add the Windows CE Target Control Shell to your OS Not applicable
image.
<IN
PUT
TYP
E="
Che
ckb
ox"
Val
ue=
"ON
">
31. Rebuild the Nk.bin image and verify that it boots
successfully. Ensure that PB can connect over KITL to
the target using the Windows CE Target Control.
<IN
PUT
TYP
E="
Che
ckb
ox"
Val
ue=
"ON
">
At the build shell prompt, enter the following command:
set SYSGEN_SHELL=1
Not applicable
Verify that the gi [proc | thrd | mod | delta | all]
commands work.
After this step, you have the basic kernel in place. The
basic kernel has interrupts enabled and uses virtual
memory, caches, TLB, and write buffers. These features
make up most of the tiny kernel or minkern
functionality.
32. Implement the following RTC functions:
•
OEMGetRealTime
•
OEMSetAlarmTime
•
OEMSetRealTime
Implementing the
Real-Time Clock and
System Timer
<IN 33. Customize memory usage by doing the following
Customizing Memory
PUT tasks:
TYP • Override MainMemoryEndAddress if you want to
Filename: HowToDevelopAnOAL.doc
E="
Che
ckb •
ox"
Val
ue=
"ON
">
<IN
PUT
TYP
E="
Che
ckb
ox"
Val
ue=
"ON
">
11
include additional contiguous memory than is
described in Config.bib.
Implement OEMGetExtensionDRAM if you want to
include additional noncontiguous memory.
34. Implement the OEMIoControl function.
The IOCTLs supported by the OEMIocontrol function
are highly platform-dependent and feature-dependent.
The following list shows some of the common IOCTLs
you may choose to support:
•
IOCTL_HAL_DDK_CALL
•
IOCTL_HAL_GETBUSDATA
•
IOCTL_HAL_SETBUSDATA
•
IOCTL_HAL_DISABLE_WAKE
•
IOCTL_HAL_ENABLE_WAKE
•
IOCTL_HAL_GET_DEVICE_INFO
•
IOCTL_HAL_GET_DEVICEID
•
IOCTL_HAL_GET_UUID
•
IOCTL_HAL_GET_WAKE_SOURCE
•
IOCTL_HAL_INIT_RTC
•
IOCTL_HAL_INITREGISTRY
•
IOCTL_HAL_POSTINIT
•
IOCTL_HAL_PRESUSPEND
•
IOCTL_HAL_QUERY_DISPLAYSETTINGS
•
IOCTL_HAL_REBOOT
•
IOCTL_HAL_RELEASE_SYSINTR
•
IOCTL_HAL_REQUEST_IRQ
•
IOCTL_HAL_REQUEST_SYSINTR
•
IOCTL_HAL_SET_DEVICE_INFO
•
IOCTL_HAL_TRANSLATE_IRQ
•
IOCTL_KITL_GET_INFO
•
IOCTL_PROCESSOR_INFORMATION
•
IOCTL_SET_KERNEL_COMM_DEV
•
IOCTL_SET_KERNEL_DEV_PORT
•
IOCTL_VBRIDGE_802_3_MULTICAST_LIST
•
IOCTL_VBRIDGE_CURRENT_PACKET_FILTER
•
IOCTL_VBRIDGE_GET_ETHERNET_MAC
•
IOCTL_VBRIDGE_GET_RX_PACKET
•
IOCTL_VBRIDGE_GET_RX_PACKET_COMPLETE
Implementing the
OEMIoControl Function
Filename: HowToDevelopAnOAL.doc
•
IOCTL_VBRIDGE_GET_TX_PACKET
•
IOCTL_VBRIDGE_GET_TX_PACKET_COMPLETE
•
IOCTL_VBRIDGE_SHARED_ETHERNET
•
IOCTL_VBRIDGE_WILD_CARD
12
See Also
Completing an OAL | Testing an Enhanced OAL | How to Create a Board Support
Package | How to Bring Up a Device by Creating a New BSP | Hardware
Adaptation How-to Topics
Filename: HowToDevelopAnOAL.doc
13
Implementing the OAL StartUp Function
The source file, located in
%_WINCEROOT%\Platform\<Platform>\Kernel\Buildexe\<Kernel Type>, specifies the
startup function of the kernel with the assignment EXEENTRY=StartUp. The StartUp
function specified is executed either by a jump from the boot loader or through the CPU
reset vector.
The StartUp function has the following two purposes:
•
It initializes the CPU to a known state.
For more information about initializing the CPU, see Initializing the CPU.
•
It calls the kernel initialization function, which takes no parameters.
For more information about initializing the kernel, see Initializing the Kernel.
For sample implementations of the StartUp and OAL initialization functions, see
%_WINCEROOT%\Platform\<Platform>\Kernel\Hal\<Microprocessor>.
For information about the implementation details for the StartUp function during the boot
loader development process, see Implementing the Boot Loader StartUp Function.
See Also
How to Develop an OEM Adaptation Layer | How to Develop a Boot Loader
Filename: HowToDevelopAnOAL.doc
14
Initializing the CPU
This is the OEM hardware initialization code where you set up ROM and DRAM access to get
the minimum CPU running. For sample hardware initialization code, see x86 Kernel.
When the StartUp function is called, the boot loader may already have initialized some of
the hardware. OAL StartUp must be flexible enough to handle this situation. For example,
it may be safe to initialize some hardware twice, while other hardware must not be
reinitialized. During execution of the StartUp function, you must initialize the CPU and put
the CPU into a known state before calling the kernel initialization function.
The following topics detail processor-specific StartUp considerations:
•
ARM Kernels
•
MIPSII and MIPSIV Kernel
•
SHx Kernels
•
x86 Kernel
To ensure that the kernel is successful in completing StartUp, include some debug
support, based on the SDB support, to show the progress through StartUp such as with
LEDs or serial output.
See Also
Initializing the Kernel | Implementing the OAL StartUp Function | How to Develop an OEM
Adaptation Layer
Filename: HowToDevelopAnOAL.doc
15
ARM Kernels
Before control is transferred to the kernel, the boot loader calls the StartUp routine to put
the CPU into an initialized state. You may also have to perform BSP/CPU specific
initializations as mentioned in the BSP user manual. The following table shows the two
ARM-based BSPs and source file locations included in Platform Builder for which the
StartUp function is specified.
BSP
Source file
ARM Integrator
%_WINCEROOT%\Platform\ARMIntegrator\Kernel\Hal\A
RM
Intel XScale DBPXA250
%_WINCEROOT%\Platform\XSC1BD\Kernel\Hal\ARM
OEMs developing for the ARM processor using the ARM kernel are expected to perform the
following tasks:
•
Put the processor in supervisor mode.
•
Disable the interrupt request (IRQ) and fast interrupt request (FIQ) inputs at the CPU.
•
Disable the memory management unit (MMU) and both the instruction and data
caches.
•
Flush or invalidate the instruction and data caches and the translation look-aside
buffer (TLB) and empty the write buffers.
•
Determine the reason you are in the startup code, such as cold reset, watchdog reset,
GPIO reset, and sleep reset.
•
Configure the GPIO lines per the requirements of the board. GPIO lines must be
enabled for on-board features like LED.
•
Configure the memory controller, set refresh frequency, and enable clocks. Program
data width and memory timing values and power up the banks.
•
Configure the interrupt controller. Mask and clear any pending interrupts.
•
Initialize the real-time clock count register to 0 and enable the real-time clock.
•
Set up the power management/monitoring registers. Set conditions during sleep
modes.
•
Turn on all board-level clocks and on chip peripheral clocks.
•
Get the physical base address of the OEMAddressTable and store in r0.
•
Jump to KernelStart.
To enable floating-point support, see ARM Vector Floating-Point Unit Support.
OEMs developing for the XScale processor can do the following to improve performance:
XScale performance can be improved by changing the Branch Target Buffer Enable bit. To
enable this, have the OAL change the bit in OEMInit. The branch target buffer enable bit
is bit 11 of the ARM control register.
See Also
Initializing the CPU | Implementing the OAL StartUp Function
Filename: HowToDevelopAnOAL.doc
ARM Vector Floating-Point Unit Support
The ARM kernels enable support for ARM floating-point units (FPUs). The standard
specification for the floating-point unit requires, at a minimum, imprecise handling of
floating point exceptions where an error is not detected until a second error occurs.
To fully enable support for the FPU, you must implement the functions shown in the
following code example.
void OEMRestoreVFPCtrlRegs(LPDWORD lpExtra, int nMaxRegs);
void OEMSaveVFPCtrlRegs(LPDWORD lpExtra, int nMaxRegs);
BOOL OEMHandleVFPException(EXCEPTION_RECORD *er, PCONTEXT pctx);
The following functions are assigned their appropriate addresses in the OAL and then
during OEMInit:
•
pOEMSaveVFPCtrlRegs
•
pOEMRestoreVFPCtrlRegs
•
pOEMHandleVFPException
See Also
ARM Kernels
16
Filename: HowToDevelopAnOAL.doc
17
MIPSII and MIPSIV Kernel
Before control is transferred to the kernel, the boot loader calls the StartUp routine to put
the CPU into an initialized state. OEMs may also have to perform additional SDB/CPU
specific initializations as mentioned in the user manual. Platform Builder ships with two
MIPS-based BSP for which the StartUp function is specified in the source file directories in
the following table.
BSP
Source file
NEC Solution Gear 2
(Rockhopper)
%_WINCEROOT%\Platform\SG2_VR5500\Kernel\Hal\MI
PS
NEC DDB-Vr4122 (Eagle)
%_WINCEROOT%\Platform\Eagle\Kernel\Hal\MIPS
OEMs developing for the MIPS processor using the MIPS kernel should perform the
following tasks:
•
Set the CPU to run in uncached mode
•
Clear watch registers
•
Setup configuration registers
•
Set BCU registers
•
Initialize and invalidate the CPU cache
•
Clear the HAL timer
•
Initialize memory controllers, if necessary
After the final step, StartUp must call KernelStart to allow the kernel to initialize. The
function address for KernelStart should preferably be an uncached and unmapped
addressed in kernel segment 1 (kseg1). Kseg1 is an address range defined in the MIPS
architecture and has the following specific access characteristics: the CPU should be in
kernel mode, there are no TLB mappings, and the address range is non-cacheable.
See Also
Initializing the CPU | Implementing the OAL StartUp Function
Filename: HowToDevelopAnOAL.doc
18
SHx Kernels
Before control is transferred to the kernel, the boot loader passes control to the StartUp
routine to put the CPU into as initialized state. Platform Builder ships with two SHx based
BSPs for which the StartUp function is specified in the source file directories in the
following table.
BSP
Source file
Hitachi US7729 HARP
(Keywest) - SH3
%_WINCEROOT%\Platform\Keywest\Kernel\Hal\SH
Hitachi US9950 HARP
(Aspen) - SH4
%_WINCEROOT%\Platform\Aspen\Kernel\Hal\SH
OEMs developing for the SHx processor using the SHx kernel are expected to perform the
following tasks:
•
Set up bus state control registers
•
Set up frequency control registers
•
Disable cache
•
Initialize SDRAM (SH3 only)
•
Initialize watchdog timer
When the FRQ Control Register is modified it creates instability in the PLL. For this reason,
when FRQCR is modified, the CPU shuts off. The watchdog timer can be programmed to
cause a system wakeup with an interval of 0.
Until the bus controller has been initialized, some of debug devices, such as discrete LEDs
and timing loops, may not be correct. BCR1 sets the types of memory that are being used.
BCR2 is used to set the bus widths of all of the memory areas.
Initialize the SDRAM memory in Area 3 (SH3 specific). Load the SDRAM mode register by
appending the initialization value with the lower address bits of the memory area reserved
by the SH3. Because the lower two address bits are not connected to the SDRAM chips (A0
& A1), you must shift the value left by two bits.
See Also
Initializing the CPU | Implementing the OAL StartUp Function
Filename: HowToDevelopAnOAL.doc
19
x86 Kernel
When the x86 StartUp function is called, you must pass the kernel OEMAddressTable to
set up the page tables and the CPU capability bits to determine the level of support passed
when KernelInitialize is called. Platform Builder ships with two X86-based BSPs for
which the StartUp function is specified in the following shared source directory.
BSP
Source file
National Geode SP4SC30
%_WINCEROOT%\Public\Common\Oak\CSP\i486\OAL
CEPC
%_WINCEROOT%\Public\Common\Oak\CSP\i486\OAL
You are expected to perform the following tasks before handing the control to the x86
kernel:
•
CPU state should be protected mode, paging disabled (CR0, CR3, and CR4 initialized)
•
Initialize memory controllers
•
Place CPU capability bits in EDI before jumping to KernelInitialize
•
Place physical address of OEMAddressTable in ESI before jumping to
Kernelnitialize
See Also
Initializing the CPU | Implementing the OAL StartUp Function
Filename: HowToDevelopAnOAL.doc
20
Initializing the Kernel
The kernel initialization function is named KernelInitialize for x86-based platforms and
KernelStart on all other platforms. They are the first non-OEM functions that are called
after StartUp has completed it tasks. The kernel initialization function calls additional OAL
functions, schedules threads, but must also complete tasks that are specific to each class
of CPU.
For processor specific kernel initialization considerations, see the following topics:
•
Initializing the ARM Kernel
•
Initializing the MIPS Kernel
•
Initializing the SHx Kernel
•
Initializing the x86 Kernel
KernelInitialize and KernelStart call SystemInitialization, which calls the following
functions:
•
OEMInit
•
OEMInitDebugSerial
•
OEMWriteDebugString
The following table shows the naming convention for your kernel's StartUp function.
Startup function
Kernel
StartUp
ARM, MIPS, x86
See Also
Other Kernel Tasks | Initializing the CPU | Implementing the OAL StartUp Function | How
to Develop an OEM Adaptation Layer
Filename: HowToDevelopAnOAL.doc
Initializing the ARM Kernel
The following sequence describes how the ARM kernel is initialized by the operating
system (OS):
•
Initialize first level page tables
•
Enable MMU and cache
•
Enable stacks for each mode
•
Initialize kernel global data
•
Perform serial debug functions
•
Call OEMInit
•
Perform memory initializations
•
Perform other initializations
See Also
Initializing the Kernel | Implementing the OAL StartUp Function
21
Filename: HowToDevelopAnOAL.doc
22
Initializing the MIPS Kernel
All MIPS BSPs are required to initialize OEMTLBSize to the number of translation
look-aside buffer (TLB) entries supported by the CPU before calling KernelStart.
The kernel calls OEMCacheRangeFlush to flush the instruction cache and get to a known
state. The OEM must implement OEMCacheRangeFlush in the OAL. The kernel also calls
OEMCacheRangeFlush to flush the data cache and get to a known state. For more
information about implementing OEMCacheRangeFlush, see Implementing the
OEMCacheRangeFlush Function.
The following sequence describes how the MIPS kernel is initialized by the operating
system (OS):
•
Implement OEMCacheRangeFlush in the OAL
•
Initialize kernel global data
•
Perform serial debug operations
•
Call OEMInit
•
Perform memory initializations
•
Perform other initializations
At this point, the scheduler is able to run and schedule threads.
See Also
Initializing the Kernel | Implementing the OAL StartUp Function
Filename: HowToDevelopAnOAL.doc
23
Initializing the SHx Kernel
The following sequence describes how the SHx kernel is initialized by the operating system
(OS):
•
Initialize the frequency register
•
Disable the CPU cache and flush
•
Initialize kernel global data
•
Serial port initializes
•
Initializes address translation hardware
•
Call OEMInit
•
Perform memory initializations
•
Initialize the status register — Load new status register with kernel mode, exceptions
enabled
•
Register bank 0, interrupts unmasked
•
Initialize the debugger sub system
•
Perform other initializations
See Also
Initializing the Kernel | Implementing the OAL StartUp Function
Filename: HowToDevelopAnOAL.doc
24
Initializing the x86 Kernel
The following sequence describes how the x86 kernel is initialized by the operating system
(OS):
•
Check to see if 4 MB page sizes are supported; if not, it uses a 4 KB page size
•
Read OEMAddressTable
•
Enable paging (4 MB page size or 4 KB)
•
Initialize kernel global data
•
Perform serial debug functions
•
Initialize the interrupt dispatch tables
•
Initialize the page tables and floating units
•
Call OEMInit
•
Perform memory initializations
•
Perform final initializations
Note
Kernelstart is called KernelInitialize for x86-based platforms.
See Also
Initializing the Kernel | Implementing the OAL StartUp Function
Filename: HowToDevelopAnOAL.doc
25
Other Kernel Tasks
The following topics contain information about the other tasks performed by the Windows
CE .NET kernel:
•
Kernel Relocation
•
Memory Initialization
•
Completing Kernel Initialization
•
Halting the System
See Also
Initializing the Kernel | Implementing the OAL StartUp Function
Filename: HowToDevelopAnOAL.doc
Kernel Relocation
During this stage, kernel data is relocated and global data for the kernel is initialized.
If using a debug build, the kernel must be part of the image, otherwise the following
message is sent and the boot is halted.
"ERROR: Kernel must be part of ROM image!"
See Also
Other Kernel Tasks | Initializing the Kernel
26
Filename: HowToDevelopAnOAL.doc
27
Memory Initialization
The kernel initializes any multiple XIP regions by reading OEMRomChain. For more
information about multi-XIP regions, see Multiple XIP Regions. The kernel checks RAM
to determine if this is a cold boot or warm rest.
If this is a cold boot, the following actions take place:
•
A debug string is sent. The following example code shows the debug string:
"Old or invalid version stamp in kernel structures - starting clean!"
•
The kernel memory structures are set up to indicate a cold boot.
Filesys.exe will use this information later to determine if the object store is a cold or
warm boot.
•
Kernel calls OEMGetExtensionDRAM.
The kernel calls to determine if there is a second, noncontiguous memory section
available to the kernel.
•
If pNKEnumExtensionDRAM is assigned a function pointer then this function is
called, and not OEMGetExtensionDRAM to add up to 16 memory sections.
The kernel can only address 512 MB of memory.
•
Calculate the amount of memory that will be allocated to the object store.
You can calculate the amount of memory by using the information in Config.bib called
FSRAMPERCENT.
•
pOEMCalcFSPages is called to provide an opportunity to override this value based
upon information gathered at boot time.
Memory is divided between the program space and the object store and the object
store can only be allocated a total of 256 MB of memory.
If this is a warm boot the following events occur:
•
The previous memory configuration is used.
•
CacheSync(0).
•
Zeros memory.
•
Zero only the amount of memory specified by the OEM in dwOEMCleanPages.
See Also
Other Kernel Tasks | Initializing the Kernel
Filename: HowToDevelopAnOAL.doc
28
Completing Kernel Initialization
After the kernel has completed initializing the CPU and memory, and initialized or
relocated the kernel data, the kernel performs the following steps to complete the kernel
initialization process.
To complete initialization
a. Kernel initializes the debug subsystem.
b. Kernel initializes the following OAL time functions:
•
KSystemTimeToFileTime
•
KLocalFileTimeToFileTime
•
KFileTimeToSystemTime
•
KCompareFileTime
c. Kernel creates an idle thread to zero the remaining memory. This thread is an idle
thread that will run after the system fully boots and comes to a steady state.
d. Kernel creates the alarm thread that is used by the notification system to trigger
time-based events.
During execution of the alarm thread the kernel will call the following functions:
•
OEMGetRealTime
•
OEMSetAlarmTime
The alarm thread relies on dwNKAlarmResolutionMSec to set the resolution of the
real-time clock alarm. By default, this is set to 10 seconds but can be overridden by the
OAL in OEMInit.
e. Kernel calls the OEMIoControl function with IOCTL_HAL_POSTINIT.
This is the last chance for an OEM to perform work before the rest of the OS is started.
See Also
Other Kernel Tasks | Initializing the Kernel
Filename: HowToDevelopAnOAL.doc
29
Halting the System
In the rare case when the system encounters a non-recoverable error and the only
recourse is to halt the system, it posts the debug message Halting system. At that point,
the kernel has effectively locked the system and posted the debug message about the
halting condition.
The kernel exports a function pointer that can be overridden by the OEM. If the default
function pointer value is replaced by the address of an OEM function in the OAL, when the
kernel goes to halt the OS, the OAL will be called. If the OEM does not override the function
pointer, the default action is to halt the system.
The kernel declares the following function pointer.
extern void (*lpNKHaltSystem)(void);
The following code examples show how the OEM can reassign the function pointer in the
OAL.
The following code example shows how to implement the OEMHaltSystem function.
void OEMHaltSystem (void )
{
Reset the device
}
The following code example shows how to implement OEMHaltSystem in OEMInit.
void OEMInit()
{
extern void (*lpNKHaltSystem)(void);
lpNKHaltSystem = OEMHaltSystem;
}
lpNKHaltSystem does not have any return values or take any parameters. The system
will not continue to function after the lpNKHaltSystem function is called.
See Also
Other Kernel Tasks | Initializing the Kernel
Filename: HowToDevelopAnOAL.doc
30
Creating the OAL Sources and Makefile Files
The Windows CE build process is driven by makefiles with build configuration information
such as CDEFINES and include and library file paths, provided by the sources file. You can
use a single sources file to build the OAL into a library called Hal.lib, which will be linked
into the kernel image in a later build step.
For more information, see Sources File and Makefile File.
To create the sources and makefile files
1. Create a sources file.
The sources file must contain the following code. The code shows the macro variables
for the sources file.
TARGETNAME=Hal
TARGETTYPE=LIBRARY
RELEASETYPE=PLATFORM
ARM_SOURCES=arm\startup.s
For more information about sources files and .bib files, see Sources File and Binary
Image Builder File.
f.
Create a makefile file.
The makefile file must contain the following code.
!INCLUDE $(_MAKEENVROOT)\makefile.def
For more information about makefile files, see Makefile File.
See Also
How to Develop an OEM Adaptation Layer
Filename: HowToDevelopAnOAL.doc
31
Building the OAL Source Code
When you build the OAL source code, you generate a file called Hal.lib. Hal.lib is the name
specified in the TARGETNAME macro variable of the sources file created when you create
the OAL sources and makefile files. For more information, see Creating the OAL Sources
and Makefile Files.
To build the StartUp source code
1. At the command prompt in the %_WINCEROOT%\Platform\MyPlatform\Kernel\Hal
directory, enter the following command:
build -c
For more information about the build tool and the parameters you can use, see Build
Tool.
g. Verify that %_WINCEROOT%\Platform\MyPlatform\Lib\ARMV4I\Retail\Hal.lib was
created.
If the Hal.lib binary was not created, consult Build.log for more information. Build.log
is a log file written by the build subsystem in the source directory.
See Also
How to Develop an OEM Adaptation Layer
Filename: HowToDevelopAnOAL.doc
32
Creating the Kernel Buildexe Directory
The Kernel\Buildexe directory contains source code files for building and linking the
kernel.
To create the Kernel\Buildexe directory
1. Copy an existing platform's %_WINCEROOT%\Platform\<Platform>\Kernel\Buildexe
directory to your own.
Make sure dirs files are added through the directories to support build recursion into
the tree. For more information about dirs files, see Dirs File.
h. Edit the %_WINCEROOT%\Platform\<Platform>\Kernel\Buildexe\Dirs file and
comment out the Kern and Kernkitlprof entries using !if0 and !endif tags.
This will simplify your task because you are only focusing on the KITL-enabled kernel
image.
i.
Edit the %_WINCEROOT%\Platform\<Platform>\Kernel\Buildexe\Dirs file and add
the Buildexe directory.
j.
Edit the Kernkitl sources file and comment out all library references except for Nk.lib,
Hal.lib, and FullLibc.lib.
You can add the other libraries later when you need them. For more information, see
Kernel Image Libraries.
See Also
How to Develop an OEM Adaptation Layer
Filename: HowToDevelopAnOAL.doc
33
Creating Stubs for OAL Functions
Stubs are routines that do not contain executable code. They act as placeholders for
functions that need to be fully implemented later. When implementing stubs, you can
leave comments that describe what you eventually need to do to add functionality to your
function. By scanning existing sample platform code, you can easily obtain the function
header, such as return type and arguments, for each of these functions.
You can either create the stub functions from scratch or copy an existing boot loader or
OAL that is of similar CPU type and board design, and then use #if0 and #endif tags for
the function contents.
To create a stub function
1. Obtain the function header definition for the function to be stubbed by referring to a
sample platform that supports similar hardware. The function header includes a return
type and a series of function arguments.
k. From the function header definition, create an empty function implementation.
l.
If the function header specifies a return type, add a placeholder return value to satisfy
the compiler.
For example, if the function returns a Boolean value, add either return (TRUE); or
return (FALSE); to satisfy the compiler. A successful function call is usually signaled
by a TRUE or 0 value, while failure is usually signaled by a FALSE or non-zero value.
However, this depends on the function and the data being returned.
The following code example shows the stub for the OEMReadData function:
BOOL OEMReadData (DWORD cbData, LPBYTE pbData) { return(TRUE); }
See Also
How to Develop an OEM Adaptation Layer
Filename: HowToDevelopAnOAL.doc
34
Building the Kernel Executable Image
The kernel, combined with the OAL, is the first service to run, and through the bring-up
stages, it runs without the benefit of any sophisticated debug services. Bring the kernel up
in stages and verify each stage before proceeding.
To build the kernel executable image
1. From the command prompt, go to the kernel subdirectory:
%_WINCEROOT%\Platform\<Platform>\Kernel.
m. At the command prompt, enter the following command:
build
This generates %_WINCEROOT%\Platform\MyPlatform\Target\ARM\Kernkitl.exe. A
Build.log file is also generated in the current working directory, which summarizes the
build process. If the build fails, errors are recorded in the build log file.
For more information about the Windows CE build tool, see Build Tool.
n. Resolve any build errors by adding the necessary stub function or variable.
For more information about creating stubs, see Creating Stubs for OAL Functions.
See Also
How to Develop an OEM Adaptation Layer
Filename: HowToDevelopAnOAL.doc
35
Microprocessor-specific Issues
With the number of CPUs being supported by Windows CE and the variations of a simple
core, installing a native application onto Windows CE has become problematic.
Wceload.exe and other installer applications use the dwProcessorType member from
the SYSTEM_INFO structure to determine what .cab file to install to the device. The
problem is that dwProcessorType may return a number that has not been documented
before the release of Wceload.exe and therefore Wceload.exe does not know what
CPU-based .cab file needs to be installed. To solve this problem, the
QueryInstructionSet API examines what instruction set is supported. The kernel knows
explicitly what instruction set it can run for a given kernel. This enables a process to query
for the level of support and, in the case of a WCELOAD query, to see if the .cab file should
be installed.
The following topics detail microprocessor specific considerations:
•
ARM Microprocessor
•
MIPS Microprocessor
•
SHx Microprocessor
•
x86 Microprocessor
See Also
How to Develop an OEM Adaptation Layer
Filename: HowToDevelopAnOAL.doc
36
ARM Microprocessor
The following issues should be noted when using an ARM kernel:
•
The ARM kernel does not restrict your use of registers.
In addition, you can use C code as well as assembler code when you are developing
your interrupt service routines (ISRs). However, be aware that the ISR should be as
small and fast as possible.
•
You can specify mapping of physical resources to the statically mapped
(direct-mapped) virtual addresses of the kernel in 1-MB blocks.
In an ARM-specific OAL, create an OEMAddressTable table that defines the mapping
from the 4-GB physical address space to the kernel's 512-MB, statically mapped
spaces. Each entry in the table consists of the following entries:
•
Virtual base address to which to map
•
Physical base address from which to map
•
Number of MB to map
While the order of the values in the table is arbitrary, DRAM should be placed first for
optimal performance. Because the table is zero-terminated, the last entry must be all
zeroes. The kernel will create two ranges of virtual addresses from this table. One
region, from 0x80000000 to 0x9FFFFFFF, will have caching and buffering enabled. The
other region, from 0xA0000000 to 0xBFFFFFFF, will have caching and buffering
disabled.
•
The OEMInterruptHandlerFIQ function must be part of the OAL for any ARM build to
succeed, even if it is just a stub.
•
Nested interrupts.
The ARM CPU architecture supports two interrupts: one hardware IRQ and one fast
interrupt request. The OEM must choose a nesting scheme and perform the
appropriate masking of the interrupt register in OEMInterruptHandler before
re-enabling interrupts. To do so, call INTERRUPTS_ON once the lower priority
interrupts have been masked. OEMInterruptHandler may call INTERRUPTS_OFF
again if it requires non-reentrant code, but this call is not required because the kernel
handles it when returning from OEMInterruptHandler.
•
When using GetSystemInfo to obtain microprocessor information, note that the
dwProcessorType member of the SYSTEM_INFO structure does not return the
correct microprocessor type.
To have dwProcessorType return the correct microprocessor type, set the
CEProcessorType global variable to PROCESSOR_ARM720 or
PROCESSOR_STRONGARM, depending on your microprocessor. To obtain the
microprocessor type, implement IOCTL_PROCESSOR_INFORMATION in OEMInit and
assign a value to CEProcessorType.
See Also
How to Develop an OEM Adaptation Layer | Microprocessor-Specific Issues
Filename: HowToDevelopAnOAL.doc
37
MIPS Microprocessor
MIPS ISRs are limited to using a subset of the available microprocessor registers.
Specifically, a MIPS ISR can only use registers AT, V0, A0, A1, A2, and A3. Use the
following design principals when creating an OAL for a MIPS microprocessor:
•
The ISR must return by means of j ra.
•
Implement a system-timer ISR that increments the variable CurMSec.
•
OEMInit needs to register the system-timer ISR.
•
Create the OAL function GetTickCount to return a value from the system timer.
•
The OEMIdle function takes a single parameter that is only used by the MIPS CPU.
This parameter is undefined for other CPUs. It is the base program status register (PSR)
value and sets interrupts to be enabled. The following code example shows how
OEMIdle is defined for the MIPS microprocessor.
void OEMIdle(DWORD dwIdleParam);
•
Nested interrupts.
The MIPS kernel utilizes a structure that provides the kernel with information about
which nested interrupts are masked while the current interrupt is being handled. To
gain access to this structure, the IntrPrio global variable must be declared in the OAL.
See Also
How to Develop an OEM Adaptation Layer | Microprocessor-Specific Issues
Filename: HowToDevelopAnOAL.doc
38
SHx Microprocessor
SH3 ISRs are limited to using a subset of the available microprocessor registers.
Specifically, an SH3 ISR can only use registers r0, r1, r2, r3, and r6. Use the following
design principals when creating an OAL for an SHx microprocessor:
•
The ISR must return by means of rts, and cannot access any addresses through the
translation look-aside buffer (TLB).
•
Implement the nonmaskable-interrupt ISR, OEMNMI.
•
OEMInit must register OEMNMI.
•
You can define the variable OEMExtraCCR to configure the cache control register. The
default value is 0.
Note To place an SH4 device in write-through mode, reset the cache control
register (CCR) to write-through mode in OEMInit.
•
The scheduler timer code is implemented in the Ktimer.c and Timer0.src files in the
OAL. Sample versions of these files are located in
%_WINCEROOT%\Platform\%BSP%\Kernel\Hal\Shx.
•
Nested interrupts.
The SHx kernel utilizes a structure that provides the kernel with information about
which nested interrupts are masked while the current interrupt is being handled. To
gain access to this structure, the IntrPrio global variable must be declared in the OAL.
See Also
How to Develop an OEM Adaptation Layer | Microprocessor-Specific Issues
Filename: HowToDevelopAnOAL.doc
39
x86 Microprocessor
The following issues should be noted when using an x86 kernel:
•
The x86 kernel does not restrict your use of registers.
In addition, you can use C code as well as assembler code when developing your ISRs.
However, be aware that the ISR should be as small and fast as possible.
•
Nested interrupts.
The x86 CPU architecture contains a single interrupt line and typically a PIC to support
nested interrupts. To support nesting by priority, the PIC must be programmed
accordingly. For more information, see the PIC documentation. When the x86 kernel
intercepts an IRQ, the kernel disables interrupts, saves the registers, and then turns
interrupts back on before calling the ISR.
•
When using GetSystemInfo to obtain microprocessor information, note that the
dwProcessorType member of the SYSTEM_INFO structure does not return the
correct microprocessor type.
To have dwProcessorType return the correct microprocessor type, implement an I/O
control code in your OAL before any other applications are run and set the
CEProcessorType global variable to PROCESSOR_INTEL_486. This value cannot be set
in OEMInit because the detect routine for the OS is not run until after OEMInit is
called.
See Also
How to Develop an OEM Adaptation Layer | Microprocessor-Specific Issues
Filename: HowToDevelopAnOAL.doc
40
OEMAddressTable
OEMAddressTable defines a table that maps a 4 GB physical address space to the
kernel's 512-MB unmapped space for a device. Each entry in the table consists of the
virtual base address to map to, the physical base address to map from, and the number of
MB to map.
The data must be declared in a READONLY section and there must be at least one non-zero
entry for the structure to be valid. For x86 platforms, the first entry must identify RAM,
and it must start at 0x80000000. For ARM platforms, 0x80000000 can be mapped to
anything. Each entry is of the following format.
Virtual Address, Physical Address, Size.
The Size must be a multiple of 4MB for x86 platforms and a multiple of 1MB for ARM. The
last entry of the table must be zeroed. You can leave holes between ranges but you cannot
have overlapping ranges.
The only valid virtual memory mapping range is from 0x80000000 to 0x9FFFFFFF. For
every entry created in the table, the kernel creates two virtual address ranges. One exists
in the virtual address range from 0x80000000 to 0x9FFFFFFF and is memory that has
caching and buffering enabled. The second exists in the virtual address range from
0xA0000000 to 0xBFFFFFFF and has caching and buffering disabled.
Any memory that is not mapped at boot time cannot be directly accessed by an ISR
without first calling CreateStaticMapping to map the address.
The following code example shows an implementation of OEMAddressTable for x86
platforms, which has the restriction of VA 0x80000000 = PA 0x00000000. ARM platforms
do not have this restriction.
public
_OEMAddressTable
_OEMAddressTable:
;
; OEMAddressTable defines the mapping between Physical and Virtual Address
;
o MUST be in a READONLY Section
;
o First Entry MUST be RAM, mapping from 0x80000000 -> 0x00000000
;
o each entry is of the format ( VA, PA, cbSize )
;
o cbSize must be multiple of 4M
;
o last entry must be (0, 0, 0)
;
o must have at least one non-zero entry
; RAM 0x80000000 -> 0x00000000, size 64M
dd 80000000h,
0,
04000000h
; FLASH and other memory, if any
; dd FlashVA,
FlashPA,
; Last entry, all zeros
dd 0
0
0
FlashSize
Filename: HowToDevelopAnOAL.doc
See Also
ARM Microprocessor | x86 Microprocessor
41
Filename: HowToDevelopAnOAL.doc
42
Implementing the OEMCacheRangeFlush
Function
The Windows CE .NET kernel has replaced calls to FlushDCache, FlushICache and
ClearTLB with calls to the OEMCacheRangeFlush function. OEMCacheRangeFlush
has been customized for various CPU architectures and componentized into libraries in the
CSP directory. These libraries must be included in order for your kernel to link correctly.
The following table shows the libraries located in
%_WINCEROOT%\Public\Common\Oak\CSP.
CPU architecture
Library
MIPS
Csp_mips.lib
ARM\ARM720t
Csp_arm720t.lib
ARM\ARM920t
Csp_arm920t.lib
ARM\Common
Csp_arm.lib
ARM\SA11x0
Csp_sa11x0.lib
ARM\XScale
Csp_xscale.lib
I486\OAL
I486oal.lib
SHX\SH3
Csp_sh3.lib
SHX\SH4
Csp_sh4.lib
Note
ARM builds typically link with one or more CSP libraries.
Some of the CSP routines reference global variables containing cache and TLB size
information. Your platform must resolve these variables. You can further optimize such
CSP implementations by hard-coding variables in a private implementation of
OEMCacheRangeFlush in your platform.
If you want to modify or override CSP implementations of caching code, you must put the
relevant source files in the Kernel\Buildexe directory and modify each of the source files in
its subdirectories to build the file.
See Also
Cache Flush Routines | How to Develop an OEM Adaptation Layer | How to Migrate a
Board Support Package to Windows CE .NET 4.2
Filename: HowToDevelopAnOAL.doc
43
Cache Flush Routines
Instruction and data caches are flushed based on the CPU architecture and the kernel.
Data Cache Flush
The data cache flush function OEMCacheRangeFlush is called to flush the data cache by
determining how many cache lines are available and then flushing each line until all lines
have been flushed.
Note The old data cache flush function, FlushDCache, is deprecated and should
never be used.
OEMCacheRangeFlush is called in the following cases:
•
CacheSync is called. This is a public API that anyone can call.
•
When the kernel loads DLLs or executable files.
•
Whenever the kernel debugger is active.
Instruction Cache Flush
The instruction cache flush function OEMCacheRangeFlush is called at various times to
flush the instruction cache by determining how many cache lines are available and then
flushing each line until all lines have been flushed.
Note The old instruction cache flush function, FlushICache, is deprecated and
should never be used.
OEMCacheRangeFlush is called in the following cases:
•
CacheSync is called with flags CACHE_SYNC_INSTRUCTIONS or
CACHE_SYNC_DISCARD. This is a public API that anyone can call.
•
When DLLs or executable files are loaded or paged in by the kernel.
•
Whenever the kernel debugger is performing active.
Note On x86-based platforms, there is only one cache flush routine called
OEMFlushCache.
See Also
Implementing the OEMCacheRangeFlush Function
Filename: HowToDevelopAnOAL.doc
44
Enabling the Debug Serial Port
The kernel calls OEMInitDebugSerial after it performs basic initialization operations but
before calling OEMInit. When OEMInitDebugSerial returns, the kernel calls
OEMWriteDebugString and displays a logon message to your terminal program through
the serial port. If the debug serial port is initialized correctly, you will see terminal output
program similar to the following code example.
Windows CE Kernel for MIPS Built on Mar 27 2001 at 17:58:35
In addition to these two functions, the following functions add support for the full range of
functionality required by the kernel:
•
OEMReadDebugByte
•
OEMWriteDebugByte
•
OEMWriteDebugString
•
OEMClearDebugCommError
These functionalities are all based in the OAL and communicate directly with your SDBs
serial port.
The kernel and other services that are up and running use serial debug port before KITL is
initialized and the debugger is engaged. The serial port should be set to the following
settings to ensure that all SDBs have a consistent approach.
Setting
Value
Baud rate
38,400
Character size
8 bit
Number of stop bits
1
Parity bit
None
See Also
How to Develop an OEM Adaptation Layer
Filename: HowToDevelopAnOAL.doc
45
Sharing Code Between the Boot Loader and
the OAL
To minimize duplication of code for some routines used by both the boot loader and the
OAL, you can share code between these two.
To share code between the boot loader and the OAL
1. Create one source file called Debug.c in
%_WINCEROOT%\Platform\MyPlatform\Kernel\Hal, which contains the code.
o. To share the code with the boot loader, create another file called Boot_debug.c in
%_WINCEROOT%\Platform\MyPlatform\Eboot.
The Boot_debug.c file should contain only the following code:
#include "..\kernel\hal\ARM\debug.c"
Using #ifdef BOOTLOADER in the source code, you can conditionally compile the code to
use physical memory addresses in the boot loader, or virtual memory addresses in the
OAL. For example, assuming your peripherals are physically located at 0x40000000, and
mapped to 0xB0000000 in OEMAddressTable, you would need to add 0x70000000 to
the register addresses when running with virtual memory enabled. The following code
example shows how to do this.
void OEMInitDebugSerial(void)
{
#ifdef BOOTLOADER
UINT32 va_periph_offset = 0x00000000; // Boot loader uses physical
addresses
#else
UINT32 va_periph_offset = 0x70000000; // Kernel startup uses virtual
addresses
#endif
volatile UART1reg
UART1_BASE;
*s2410UART1
volatile IOPreg
IOP_BASE;
*s2410IOP
...
}
See Also
How to Develop an OEM Adaptation Layer
= (UART1reg *) va_periph_offset +
= (IOPreg *)
va_periph_offset +
Filename: HowToDevelopAnOAL.doc
46
Implementing the OEMInit Function
The OEMInit function is responsible for initializing the board-level hardware. During
OEMInit, you must initialize all hardware peripherals needed to support the platform,
initiate the debug KITL transport, and set any kernel variables that are required by the
kernel to enable or alter functionality. For more information about KITL initialization, see
Adding KITL Initialization Code.
The OEMInit function contains much of the functionality present in the
OEMPlatformInit function in the boot loader. For information about implementing
OEMPlatformInit, see Implementing the OEMPlatformInit Function.
The following list shows the suggested initialization tasks for the OEMInit function:
•
Initialize interrupt mapping tables. These are two private OAL tables that map from
physical interrupts, IRQs, to logical interrupts, SYSINTR values, and vice-versa. The
following code example shows how to initialize the interrupt mapping tables:
for (i = 0; i <= IRQ_MAXIMUM; i++) {
IRQToSysIntr[i] = SYSINTR_UNDEFINED;
}
for (i = 0; i < SYSINTR_MAXIMUM; i++) {
SysIntrToIRQ[i] = IRQ_UNDEFINED;
}
•
Create any initial static mappings required by the OAL. For example,
SYSINTR_OS_TICK may map to hardware IRQ_OS_TICK—the following code example
shows how to do this.
SysIntrToIRQ[SYSINTR_OS_TICK] = IRQ_OS_TICK;
IRQToSysIntr[IRQ_OS_TICK] = SYSINTR_OS_TICK;
For more information about Windows CE interrupts, see Interrupts and Defining an
Interrupt Identifier.
•
Configure the system timer, real-time clock (RTC), or any other timekeeping device by
implementing the InitClock function and calling it from OEMInit. For more
information about initializing the system tick timer, see Initializing the System Tick
Timer.
•
Configure any CPU-level and board-level interrupt controllers.
•
Provide LED debug support. While optional, it is helpful when debugging the kernel.
•
If necessary for your CPU type, call the HookInterrupt function to register one or
more ISRs. The HookInterrupt function associates an ISR with an interrupt request
line (IRQ) value. OEMInit must register the ISR for the system tick. This is the base
functionality required by the kernel to schedule threads. For more information about
the system tick, see Implementing the System Tick.
Note HookInterrupt is not required or possible for ARM-based platforms. There
are only two interrupts and they are handled by OEMInterruptHandler and
OEMInterruptHandlerFIQ.
Filename: HowToDevelopAnOAL.doc
47
The following code example shows how you can register the interval-timer ISR,
TimerISR, for hardware interrupt 5.
void OEMInit(void)
{
...
HookInterrupt(5, TimerISR); // Hook timer interrupt
...
}
•
Mask all unconfigured interrupt sources at the CPU-level and board-level. This
prevents interrupts from accidentally being delivered during the kernel initialization.
•
Leave the 1 millisecond (ms) system tick unmasked, so that timekeeping and thread
scheduling will function properly.
The following list shows optional features that you can implement in the OEMInit
function:
•
Logging functions
•
Registry functions
•
Secure loader functions
•
Save and restore coprocessor registers
•
High performance counters
•
System halt
•
Event tracking
•
Detection of idle time
•
User notification alarm
•
Multiple execute in place (XIP) regions
•
Default thread quantum
•
Enable debug IO control
•
Erasing the object store
•
Supporting CPU utilization functionality
•
Object store cold boot
•
ARM FPU support
•
Zeroing memory
See Also
How to Develop an OEM Adaptation Layer
Filename: HowToDevelopAnOAL.doc
48
Initializing the System Tick Timer
The system tick timer should be initialized in OEMInit with a call to InitClock, also
defined in the OAL.
The following list shows the suggested tasks for the InitClock function:
•
Decide what timer interrupt source to use for the system tick. The timer interrupt
source will depend on the CPU and available external sources.
•
Initiate the 1 millisecond (ms) tick interval required by the kernel by configuring the
timer and unmasking the interrupt.
If the target device contains high-resolution timers OEMInit should also set the
pQueryPerformanceCounter and pQueryPerformanceFrequency global variables.
For more information about high-resolution timers, see Supporting High-Resolution
Timers.
See Also
How to Develop an OEM Adaptation Layer | Implementing the OEMInit Function
Filename: HowToDevelopAnOAL.doc
49
Implementing the System Tick
The system tick is the only interrupt required by the kernel and you must hook the
interrupt with HookInterrupt and then provide a system tick ISR to handle the tick.
When threads are active in the system the system tick timer goes off every millisecond.
The system tick ISR updates CurMSec every millisecond so blocking operations and
GetTickCount calls are as accurate as possible. The tick ISR only returns
SYSINTR_RESCHED to the kernel's interrupt dispatcher if a thread scheduling event is
ready to occur; that is, if dwReschedTime is less than or equal to CurMSec. If no
scheduling event is ready, the ISR returns SYSINTR_NOP. SYSINTR_NOP indicates that
the interrupted thread will be resumed immediately, without having to wait for the kernel
to run its scheduling algorithm.
The system tick ISR does not schedule an interrupt service thread (IST). It returns the
appropriate SYSINTR_* value, which causes the kernel to schedule an IST as needed.
You must implement the system tick timer as part of the OAL.
Note On ARM-based processors, you do not need to call HookInterrupt. All
interrupts are serviced by the OEMInterruptHandler function.
Testing Tick Support
To test that the system tick has been initialized and functional, you can write to a LED
when a system tick occurs.
See Also
How to Develop an OEM Adaptation Layer | Implementing the OEMInit Function
Filename: HowToDevelopAnOAL.doc
50
Implementing an ISR
An interrupt service routine (ISR) is code that handles interrupt requests (IRQs) on your
target device. The ISR is the core of the OAL and is responsible for locating an interrupt
source, masking it, and returning a unique identifier to the Windows CE kernel to indicate
which driver needs to be used to handle the event.
The ISR only needs to focus on the tick timer interrupt until other user peripherals such as
keyboard, mouse, PCMCIA, and USB, are added. When support for other interrupt sources
are added to the ISR, there are other interrupt support routines which will need to be
implemented for mapping hardware interrupts to unique identifiers—IRQ->SYSINTR
mappings, enabling and disabling interrupts, and so on.
Using your OAL code, Windows CE associates each IRQ with an ISR. When an interrupt
occurs, the kernel's exception handler calls the registered ISR for that interrupt. Microsoft
has optimized the kernel for each microprocessor and defines constants that specify the
number of IRQ lines available for each microprocessor. Using HookInterrupt in the OAL,
you can register only one ISR for each IRQ line. However, you can associate an ISR with
one or more interrupt identifiers. In addition, each ISR has the following characteristics:
•
Performs minimal interrupt processing that is enough to verify and acknowledge the
hardware that is asserting the interrupt, leaving most of the processing to be
performed by the interrupt service thread (IST).
•
Returns an interrupt identifier to the kernel when finished. This causes the kernel to
schedule the appropriate IST later.
Note The Windows CE exception handler only saves and restores a few registers
depending on the microprocessor being used. The following table shows the registers
you can use.
Microprocessor
Registers
ARM
All registers are available for use.
MIPS
Registers v0, AT, and a0-a3 are available for use.
SHx
Registers r0-r3 and r6 are available for use.
x86
All registers are available for use.
A device driver or the OAL can associate the interrupt identifier with an event by creating
an IST and calling the InterruptInitialize function. The IST then waits for the event.
When the ISR returns to the kernel, the kernel examines the returned interrupt identifier
and sets the associated event. The interrupt service handler of the kernel schedules
execution of the IST that the driver or OAL created. The IST calls functions in the
platform-dependent driver layer to read from and write to the target device.
To manage ISRs, you must implement the ISR management functions that allow the
kernel to begin, service, and complete interrupt processing. The following table shows the
functions you must implement to support ISR management.
Function
Description
OEMInterruptDisable
Disables a specified hardware interrupt.
OEMInterruptDone
Signals completion of interrupt processing.
Filename: HowToDevelopAnOAL.doc
51
OEMInterruptEnable
Performs any hardware operations necessary to
enable a specified hardware interrupt.
A device driver calls the InterruptInitialize, InterruptDisable, and InterruptDone
functions to enable or disable interrupts or to signal the kernel that processing is complete
for an interrupt. The kernel translates these calls into the OEMInterruptEnable,
OEMInterruptDisable, and OEMInterruptDone functions, respectively. You can use
the information that the kernel passes to OEMInterruptEnable to initialize an interrupt
or to allocate scratch space for the vendor-defined firmware ISR. When a driver calls
InterruptInitialize, the kernel will then call OEMInterruptEnable.
ISR management functions typically use switch statements to decode the interrupt
identifier and act on the associated hardware interrupt. The following code example shows
how OEMInterruptEnable checks for each of the supported interrupt identifiers.
BOOL OEMInterruptEnable (DWORD idInt, LPVOID pvData, DWORD cbData)
{
BOOL bRet = TRUE;
// Assume successful return value.
switch (idInt) {
case SYSINTR_TOUCH:
// Enable touch screen analog-to-digital. converter (ADC) interrupt
break;
...
case SYSINTR_PCMCIA_EDGE:
// Enable pcmcia edge interrupt and clear it, if one is pending
break;
...
default:
bRet = FALSE;
// Return FALSE for an invalid interrupt.
}
return bRet;
}
For more information about interrupts, see the following topics:
•
Defining an Interrupt Identifier
•
Creating an Interrupt Identifier
•
Initializing an Interrupt
•
Handling an Interrupt
•
Disabling an Interrupt
•
Passing Data between an ISR and an IST
Filename: HowToDevelopAnOAL.doc
See Also
How to Develop an OEM Adaptation Layer | Driver Development
52
Filename: HowToDevelopAnOAL.doc
53
Defining an Interrupt Identifier
An interrupt identifier is a unique value used by the kernel to identify a target device that
raises an interrupt that requires processing. The kernel then uses the interrupt identifier
to indicate whether all handling is complete, or whether to launch an IST that handles
further processing by the device driver. Platform Builder provides a set of predefined
interrupt identifiers, or you can create your own.
Windows CE defines a set of interrupt identifiers in the Nkintr.h file. The following table
shows the special-purpose interrupts used by the OS.
Interrupt identifier
Description
SYSINTR_NOP
Indicates that the kernel should complete processing of
the exception handler without setting an event.
SYSINTR_RESCHED
Indicates that the kernel should execute a reschedule.
SYSINTR_DEVICES
Specifies the base value for device class identifiers not
defined by your OAL.
SYSINTR_MAX_DEVICES
Specifies the maximum number of allowable device
interrupt identifiers. The default is 32.
SYSINTR_PROFILE
Used by the system for profiling. It is defined relative to
the value SYSINTR_DEVICES.
SYSINTR_TIMING
Used by the system for latency analysis. It is defined
relative to the value SYSINTR_DEVICES.
SYSINTR_RTC_ALARM
Indicates a real-time clock alarm. It is defined relative to
the value SYSINTR_DEVICES.
SYSINTR_FIRMWARE
Specifies the base value for custom, OAL-defined
interrupt identifiers. It is defined relative to
SYSINTR_DEVICES. All OEM identifiers should be equal
to, or greater than, this identifier.
SYSINTR_MAX_DEVICES
Specifies the maximum number of allowable device
interrupt identifiers. The default is 32.
SYSINTR_MAXIMUM
Specifies the maximum value for interrupt identifiers,
which is defined as SYSINTR_DEVICES +
SYSINTR_MAX_DEVICES. All interrupt identifiers must be
less than, or equal to, this value.
See Also
How to Develop an OEM Adaptation Layer | Implementing an ISR
Filename: HowToDevelopAnOAL.doc
54
Creating an Interrupt Identifier
In addition to using the predefined interrupt identifiers, you can define identifiers in your
OAL for your custom target devices. Create a file named Oalintr.h, and define your
non-kernel interrupt identifiers in Oalintr.h relative to the value SYSINTR_FIRMWARE. For
example, you could define the value of an interrupt identifier as SYSINTR_FIRMWARE+1.
The maximum value of an interrupt identifier should be less than SYSINTR_MAXUMUM or
SYSINTR_FIRMWARE+23.
The interrupt identifiers reside in the Oalintr.h file in
%_WINCEROOT%\Platform\%BSP%\Inc and those for a CEPC reside in the Oalintr.h file
in %_WINCEROOT%\Platform\CEPC\Inc. The following table shows the interrupt
identifiers that are defined in Oalintr.h.
Interrupt identifier
Description
SYSINTR_ADC
Indicates an analog-to-digital converter interrupt.
SYSINTR_AUDIO
Indicates an audio interrupt.
SYSINTR_IR
Indicates an infrared interrupt.
SYSINTR_ETHER
Indicates an Ethernet debugging adapter interrupt.
SYSINTR_KEYBOARD
Indicates a keyboard interrupt.
SYSINTR_PCMCIA_EDGE
Indicates a PCMCIA edge transition interrupt.
SYSINTR_PCMCIA_LEVEL
Indicates a PCMCIA level interrupt.
SYSINTR_PCMCIA_STATE
Indicates a PCMCIA state interrupt.
SYSINTR_SERIAL
Indicates a serial port interrupt.
SYSINTR_TOUCH
Indicates a touch screen interrupt.
SYSINTR_TOUCH_CHANGED
Indicates that the touch screen data has changed.
Note The kernel reserves all values below SYSINTR_FIRMWARE for future use. If you
redefine the predefined interrupt identifiers, you risk breaking the model device driver
(MDD) code in the platform-independent layer for native device drivers.
See Also
How to Develop an OEM Adaptation Layer | Implementing an ISR
Filename: HowToDevelopAnOAL.doc
55
Initializing an Interrupt
Windows CE initializes an interrupt either in OEMInit or as part of device driver
initialization. Part of the interrupt initialization can also include the creation of an IST.
The kernel begins the interrupt initialization process by calling OEMInit. Use OEMInit to
register an ISR by calling the HookInterrupt function. HookInterrupt associates an ISR
with a hardware IRQ. Further, you must use OEMInit to register the interrupts for the
system timer because subsequent code, such as the scheduler, relies on a system timer.
Some device drivers may require handling that cannot be completed in an ISR. When
Windows CE calls the initialization function for these types of device drivers, you can use
InterruptInitialize to register an event handle with the interrupt identifier returned by
the ISR of the target device. The initialization function of the device driver can then set up
an IST for the interrupt by calling the CreateThread function. The main loop of the thread
should wait on the event by using the WaitForSingleObject function. Do not use
WaitForMultipleObjects. The main loop should also process the interrupt.
If you create a thread in the driver initialization routine, you should set the appropriate
priority level of the thread with the CeSetThreadPriority function. This provides the
appropriate scheduling times. When the IST has a higher priority than the interrupted
thread, Windows CE interrupts the thread and runs the IST immediately. The thread
priorities can range from 0 through 255, with 0 as the highest priority. The choice of the
IST priority level is dependent upon the environment in which the IST needs to run. These
environmental factors include the number of threads, relative priority of threads, and the
amount of interrupt handling.
The following code example shows the functions that a device driver initialization routine
typically calls to create an event and a thread associated with an ISR.
// Create an event and a thread, and then associate them.
hEvent = CreateEvent(...)
// Get the event handle, register the interrupt, and then
// associate it with the event.
InterruptInitialize(SYSINTR_SERIAL, hEvent, ...)
// Create the thread for the IST, passing it the event handle.
// The IST code calls WaitOnSingleObject, so give it the event handle.
hThd = CreateThread(..., MyISTRoutine, hEvent, ...)
CeSetThreadPriority(hThd, 152);
See Also
How to Develop an OEM Adaptation Layer | Implementing an ISR
Filename: HowToDevelopAnOAL.doc
56
Handling an Interrupt
The main technique for handling an interrupt is to associate an event to a specified ISR.
Windows CE then schedules your IST when the event is triggered. A typical interrupt
handling sequence includes the following steps:
1. The hardware raises an interrupt.
p. The kernel looks up the IRQ of the interrupt, calls the registered ISR, and disables all
lower-priority interrupts.
q. The ISR performs any necessary handling, and then returns an interrupt identifier.
r.
If the interrupt identifier value returned by the ISR is SYSINTR_NOP, the kernel
completes processing without setting an event — all interrupts are enabled — and
continues performing any tasks in process before the interrupt occurred. Otherwise,
the kernel sets the event you have associated with the interrupt identifier. When the
ISR returns, the kernel enables all other interrupts except the one that is being
processed.
s. The kernel schedules the IST indicated by the interrupt identifier to run.
The IST can use driver resources to get or send data and control codes to the hardware
and to acknowledge the hardware interrupt.
t.
After completely servicing the interrupt, the IST calls the InterruptDone function. In
turn, InterruptDone calls the OEMInterruptDone OAL interface function to perform
any hardware actions necessary to enable the next interrupt of the target device.
The following code example shows the functions that an IST typically calls.
// CreateThread can supply one 32-bit parameter, so pass the event handle.
// Associate this thread with the registered event.
MyISTRoutine(HANDLE hEvent) {
while (1) {
WaitForSingleObject(hEvent, INFINITE);
// Check for thread exit signal and exit if set
// Interrupt processing here.
// On completion, call InterruptDone.
InterruptDone(InterruptId);
// Loop and wait for the kernel to set the event.
}
}
See Also
How to Develop an OEM Adaptation Layer | Implementing an ISR
Filename: HowToDevelopAnOAL.doc
57
Disabling an Interrupt
If your OS needs to disable an interrupt, this typically occurs after the kernel has received
a request to unload a driver.
To disable an interrupt
•
Implement the OEMInterruptDisable function, which is called by the kernel when
such a request has been made
To disable a driver interrupt, Windows CE initiates the following sequence of steps:
1. The kernel calls the FreeLibrary function to close or unload the device driver.
u. The kernel calls the DLL main entry function of the driver with the
DLL_PROCESS_DETACH flag passed in as a parameter.
v. The device driver calls the InterruptDisable function to disable the logical interrupt.
w. InterruptDisable calls OEMInterruptDisable to disable the physical interrupt.
If the IRQ line is not multiplexed, the ISR can physically disable the IRQ line associated
with the interrupt identifier. If the IRQ line is multiplexed, the ISR sends an
SYSINTR_NOP interrupt identifier to the kernel for interrupts with the disabled
interrupt identifier to keep track of those multiplexed interrupts that have been
disabled.
See Also
How to Develop an OEM Adaptation Layer | Implementing an ISR
Filename: HowToDevelopAnOAL.doc
58
Passing Data between an ISR and an IST
There may be times when you want to pass information between an ISR and an IST. For
example, because calling an IST each time an IRQ arrives is a time-consuming process,
you might want to design an ISR to buffer IRQ data before calling the IST. The ISR would
return SYSINTR_NOP until the buffer was full, and then would return the appropriate
SYSINTR identifier when the ISR is ready for the IST to run. Once the IST runs, it can pick
up the data that the ISR has been buffering.
To pass data between an ISR and an IST
1. Reserve physical memory for the ISR in your Config.bib file.
Config.bib contains several examples of reserving physical memory for the serial and
debug drivers.
x. Use the reserved memory in your ISR call.
Because the ISR runs in kernel mode, the ISR can access the reserved memory to
buffer data.
y. Call the MmMapIoSpace function in your IST to map the physical memory to a virtual
address.
MmMapIoSpace calls the VirtualAlloc and VirtualCopy functions to map the
physical memory to a virtual-memory address that the IST can access.
You can also call VirtualAlloc and VirtualCopy directly. For example, you can
allocate memory outside of the virtual memory space of a process by calling
VirtualAlloc with its parameters set to the following values:
•
dwSize >= 2 MB
•
flAllocationType set to MEM_RESERVE
•
flProtect set to PAGE_NOACCESS
In Windows CE .NET, an installable ISR can easily share data with an IST as the memory
can be dynamically allocated instead of being reserved in the Config.bib file.
See Also
How to Develop an OEM Adaptation Layer | Implementing an ISR
Filename: HowToDevelopAnOAL.doc
59
Enabling Power Management
Power management functions respond to system calls for turning the system off or for
idling it. These system calls may be triggered by either hardware or software events, such
as throwing a power switch or an idle timer count. The following table shows the functions
you must implement to enable power management on your target device.
Function
Description
OEMPowerOff
Places the target device in a power-off state.
OEMIdle
Places the target device in an idle state. This is the lowest
energy usage state possible balanced with the need to
return from idle quickly.
The kernel calls OEMIdle whenever there are no threads ready to run. The kernel
provides an extern DWORD dwReschedTime value that indicates when the next known
event is to occur.Asynchronous events such as user-generated interrupt from a keyboard
or mouse can occur before then and needs to be handled.
The kernel calls OEMPowerOff when the user presses the OFF button or when the system
requests the device to power off. OEMPowerOff is responsible for handling the state of
the board-level logic through the suspend and resume process.
Note Individual drivers are responsible for handling the board-level logic for
individual devices, such as video, audio, and USB.
You can find code samples of OEMPowerOff and OEMIdle in
%_WINCEROOT%\Platform\<Platform>\Kernel\Hal.
When the kernel calls the OEMIdle function, the OEM device is requested to go into a
sleep, or idle, state. This consists of saving the current state, placing the memory into a
refresh state if necessary, stopping the clock, and suspending execution.
In order to conserve power while continually awakening the target device, OEMIdle
should sleep for as long as possible. This is usually until the sooner of two events:
dwReschedTime, or the maximum delay supported by the hardware timer.
When an interrupt occurs, scheduled or otherwise, the device ends its idle state, the
previous state is restored, and the scheduler is invoked. If no new threads are ready to run,
the kernel will again call OEMIdle.
When the system returns from idle, OEMIdle must update the CurMSec variable with the
real number of milliseconds that have elapsed. The sample platforms also keep partial
millisecond counts, dwPartialCurMSec, in case another interrupt occurs, which will cause
the system to stop idling before the system timer fires.
The following code example shows how to implement OEMIdle with these variables.
static DWORD dwPartialCurMSec = 0;
sub-millisecond leftover.
void
OEMIdle(
DWORD dwIdleParam
)
// Keep CPU-specific
Filename: HowToDevelopAnOAL.doc
60
{
DWORD dwIdleMSec;
DWORD dwPrevMSec = *pCurMSec;
// Use for 64-bit math
ULARGE_INTEGER currIdle = {
curridlelow,
curridlehigh
};
if ((int) (dwIdleMSec = dwReschedTime - dwPrevMSec) <= 0) {
// already time to wakeup
return;
}
// just idle till tick if profiling or running iltiming
if (bProfileTimerRunning || fIntrTime) { // fIntrTime : Interrupt Latency
timeing.
// idle till end of 'tick'
CPUEnterIdle(dwIdleParam);
// Update global idle time and return
currIdle.QuadPart += RESCHED_PERIOD;
curridlelow = currIdle.LowPart;
curridlehigh = currIdle.HighPart;
return;
}
//
// Since OEMIdle( ) is being called in the middle of a normal reschedule
// period, CurMSec, dwPartialCurMSec, and CurTicks need to be updated
accordingly.
// Once we reach this point, we must re-program the timer (if we ever did)
Filename: HowToDevelopAnOAL.doc
61
// because dwPartialCurMSec will be modified in the next function call.
//
CPUGetSysTimerCountElapsed(RESCHED_PERIOD, pCurMSec, &dwPartialCurMSec,
pCurTicks);
if ((int) (dwIdleMSec -= *pCurMSec - dwPrevMSec) > 0) {
dwPrevMSec = *pCurMSec;
//
// The system timer may not be capable of arbitrary timeouts. Get the
// CPU-specific highest possible timeout available.
//
dwIdleMSec = CPUGetSysTimerCountMax(dwIdleMSec);
//
// Set the timer to wake up much later than usual, if needed.
//
CPUSetSysTimerCount(dwIdleMSec);
CPUClearSysTimerIRQ( );
//
// Enable wakeup on any interrupt, then go to sleep.
//
CPUEnterIdle(dwIdleParam);
INTERRUPTS_OFF( );
//
// We're awake! The wake-up ISR (or any other ISR) has already run.
//
if (dwPrevMSec != *pCurMSec) {
//
// We completed the full period we asked to sleep. Update the
counters.
//
Filename: HowToDevelopAnOAL.doc
62
*pCurMSec += (dwIdleMSec - RESCHED_PERIOD); // Subtract resched
period, because ISR also incremented.
CurTicks.QuadPart += (dwIdleMSec - RESCHED_PERIOD) *
dwReschedIncrement;
currIdle.QuadPart += dwIdleMSec;
} else {
//
// Some other interrupt woke us up before the full idle period was
// complete. Determine how much time has elapsed.
//
currIdle.QuadPart += CPUGetSysTimerCountElapsed(dwIdleMSec,
pCurMSec, &dwPartialCurMSec, pCurTicks);
}
}
// Re-arm counters
CPUSetSysTimerCount(RESCHED_PERIOD);
CPUClearSysTimerIRQ( );
// Update global idle time
curridlelow = currIdle.LowPart;
curridlehigh = currIdle.HighPart;
return;
}
Use the above code example and the system tick ISR to implement the extended idle
period. The following code example, in the system tick ISR, is used to determine whether
the kernel should schedule a thread (SYSINTR_RESCHED returned) or do nothing
(SYSINTR_NOP returned).
ULONG ulRet = SYSINTR_NOP;
if ((int)(CurMSec >= dwReschedTime))
ulRet = SYSINTR_RESCHED;
By returning SYSTINTR_NOP when appropriate, a system tick ISR can prevent the kernel
from rescheduling unnecessarily and therefore save power.
For more information about the variables the OAL needs to update in order to support
OEMIdle, see CPU Utilization.
Filename: HowToDevelopAnOAL.doc
See Also
How to Develop an OEM Adaptation Layer
63
Filename: HowToDevelopAnOAL.doc
64
CPU Utilization
You can calculate CPU utilization for the operating system (OS) by determining how much
time the OS spends in the IDLE state over a period of time. The device goes idle when the
kernel calls OEMIdle.
The OAL must update the following three global variables to support OEMIdle:
•
curridlehigh
•
curridlelow
•
idleconv
Idleconv is the conversion to be applied to the 64-bit time value stored in curridlehigh and
curridlelow to return a millisecond value. If curridlehigh and curridlelow are based on
milliseconds then idleconv is set to 1. The values of curridlehigh and curridlelow are always
increasing and the increase is based on the amount of time that the device will idle or on
the amount of time that the device idled before being wakened. From an application, a call
to GetIdleTime will return the OAL values.
See Also
Interactions between OEMIdle and the Thread Timer | Sample Platform Implementations
of OEMIdle | Enabling Power Management
Filename: HowToDevelopAnOAL.doc
65
Interactions between OEMIdle and the Thread
Timer
The OEMIdle algorithm is complicated by the fact that it must cooperate with the system
tick ISR in updating CurMSec. When an interrupt wakes the CPU from its low power mode
during OEMIdle, the ISR for the interrupt executes. Once it has executed, control returns
to the point in OEMIdle at which the CPU was put to sleep.
If a timer interrupt woke the system, the tick ISR has already updated CurMSec by one
millisecond before control returns to OEMIdle. If another interrupt woke the CPU,
CurMSec has not been incremented. It is up to OEMIdle to make sure that CurMSec is
properly updated regardless of the wakeup interrupt.
The following factors affect both OEMIdle and the system tick ISR:
•
Interrupt latency timing support code
•
Monte Carlo profiling support code
•
Possible support for software-only alarms
See Also
CPU Utilization
Filename: HowToDevelopAnOAL.doc
66
Sample Platform Implementations of OEMIdle
The sample platforms provided with Platform Builder include an implementation of
OEMIdle that is generic in regards to the platform's timer and power management
capabilities. The sample implementations abstract their interface to the platform
hardware in a manner similar to the MDD/PDD division in some device drivers. You are still
responsible for implementing OEMIdle but can choose to leverage the sample
implementation if it makes the job easier.
See Also
CPU Utilization | Types of System Tick Timers | Boilerplate Interface Routines
Filename: HowToDevelopAnOAL.doc
67
Types of System Tick Timers
In general, platforms provide one of the following types of timers:
•
Fixed interval timers that cannot be reprogrammed. These interrupt the system at a
fixed rate and cannot be updated once they are started. CEPC uses this kind of timer.
Fixed interval timers should be avoided in mobile battery-powered systems as there is
no way for the kernel to conserve power with OEMIdle.
•
Programmable interval timers (PITs) automatically count down to zero, generate an
interrupt, and automatically reload and start over. Most SHx platforms use this kind of
timer.
•
Free-running counters with value and compare registers count up or down until the
value register equals the match register, at which point they generate an interrupt.
Even after the interrupt, the value register keeps counting. To generate another
interrupt, the match register must be reprogrammed. XScale and DDB5476 platforms
use this kind of timer.
Fixed-rate timers limit the functionality of OEMIdle and the thread timer to conserve
power. For more information, see Boilerplate Interface Routines.
See Also
Sample Platform Implementations of OEMIdle
Filename: HowToDevelopAnOAL.doc
68
Boilerplate Interface Routines
The sample platforms provided with Platform Builder include generic implementations of
the following functions:
•
QueryPerformanceCounter
•
QueryPerformanceFrequency
•
OEMIdle
•
SC_GetTickCount
Note
call.
The kernel calls SC_GetTickCount to service the GetTickCount system
See Also
Sample Platform Implementations of OEMIdle | CPUClearSysTimerIRQ Routine |
CPUEnterIdle Routine | CPUGetSysTimerCountElapsed Routine |
CPUGetSysTimerCountMax Routine | CPUSetSysTimerCount Routine
Filename: HowToDevelopAnOAL.doc
69
CPUEnterIdle Routine
CPUEnterIdle enables interrupts and puts the CPU, or the platform, into a low-power
state. When an interrupt occurs it will awaken the system. CPUEnterIdle is invoked with
the same dwIdleParam parameter as OEMIdle.
This routine is prototyped as the following code example indicates.
void CPUEnterIdle(DWORD dwIdleParam);
If your platform does not support low-power modes, the implementation can be used as
shown in the following code example.
void CPUEnterIdle(DWORD dwIdleParam);
{
fInterruptFlag = FALSE;
INTERRUPTS_ON();
while (!fInterruptFlag) {
// Just wait here. Any interrupt will bump us out.
}
}
Note This requires that all ISRs set the variable fInterruptFlag, which must be
declared as volatile.
See Also
Boilerplate Interface Routines
Filename: HowToDevelopAnOAL.doc
70
CPUGetSysTimerCountMax Routine
OEMIdle calls CPUGetSysTimerCountMax to determine whether it can program the
timer ISR for the full interval until the next thread-scheduling event. If the requested
delay, indicated by dwIdleMSecRequested, is longer than the underlying hardware can
support, CPUGetSysTimerCountMax returns the maximum delay possible.
This routine is prototyped as shown in the following code example.
DWORD CPUGetSysTimerCountMax(DWORD dwIdleMSecRequested);
The following code example requires that you define IDLE_MAX_MS with the maximum
millisecond delay that the hardware supports.
DWORD CPUGetSysTimerCountMax(DWORD dwIdleMSecRequested);
{
if (dwIdleMSecRequested > IDLE_MAX_MS) {
// Our timer cannot idle more than IDLE_MAX_MS milliseconds.
// The sleep time needs to be broken into reasonable chunks.
return IDLE_MAX_MS;
}
return dwIdleMSecRequested;
}
See Also
Boilerplate Interface Routines
Filename: HowToDevelopAnOAL.doc
71
CPUSetSysTimerCount Routine
This routine is prototyped as shown in the following code example.
void CPUSetSysTimerCount(DWORD dwIdleMSec);
The dwIdleMSec parameter is the millisecond delay until the next timer interrupt. This
delay value is calculated with the help of CPUGetSysTimerCountMax so it is guaranteed
to be supported by the timer hardware. This routine should program the timer hardware
to generate a periodic interrupt with this interval.
See Also
Boilerplate Interface Routines
Filename: HowToDevelopAnOAL.doc
72
CPUClearSysTimerIRQ Routine
This routine is prototyped as shown in the following code example.
BOOL CPUClearSysTimerIRQ(void);
It takes no parameters but clears the timer interrupt and returns TRUE if the timer
interrupt is pending. If the timer interrupt is not pending, it returns FALSE.
Some parts of OEMIdle execute with interrupts disabled and it is possible that the timer
will try to interrupt the system during those parts of the code. Platforms that implement
PITs need to know whether the interrupt is pending when they update CurMSec.
See Also
Boilerplate Interface Routines
Filename: HowToDevelopAnOAL.doc
73
CPUGetSysTimerCountElapsed Routine
OEMIdle calls CPUGetSysTimerCountElapsed to determine how much time has
elapsed since the last timer tick. In some situations, CPUGetSysTimerCountElapsed is
called with interrupts off, making it possible for a timer interrupt to be pending when it is
called. This routine must compensate for the ISR, which will also update CurMSec when
its interrupt is unmasked. Systems that use a PIT should note the return value from
CPUClearSysTimerIRQ as it indicates whether the PIT interrupt was serviced in time to
avoid losing timer information.
This routine is prototyped as shown in the following code example.
DWORD CPUGetSysTimerCountElapsed(
DWORD dwTimerCountdownMSec,
volatile DWORD *pCurMSec,
DWORD *pPartialCurMSec,
volatile ULARGE_INTEGER *pCurTicks
);
The following table shows the parameters used in this routine:
Parameter
Description
dwCountdownMSec
The number of milliseconds that the
system expected to sleep before the next
timer interrupt.
pCurMSec
Points to a millisecond counter, often but
not always the global variable CurMSec.
pPartialCurMSec
Some number of timer counts, not timer
ticks, not accounted for in the current value
of *pCurMSec.
pCurTicks
Pointer to a 64-bit tick counter.
CPUGetSysTimerCountElapsed determines the number of counter increments or
decrements that have elapsed and adds it to *pPartialCurMSec. It then calculates the
number of whole milliseconds the sum represents and updates *pCurMSec with that value.
Finally, it updates *pPartialCurMSec with any counts left over from this calculation.
CPUGetSysTimerCountElapsed treats *pCurTicks as a running number of timer counts,
reflecting the number of times the counter has been incremented or decremented, not the
number of interrupts it has generated or the number of milliseconds elapsed.
The following code example shows an implementation of
CPUGetSysTimerCountElapsed for an ARM platform, using a PIT.
DWORD CPUGetSysTimerCountElapsed(
DWORD dwTimerCountdownMSec,
volatile DWORD *pCurMSec,
DWORD *pPartialCurMSec,
volatile ULARGE_INTEGER *pCurTicks
Filename: HowToDevelopAnOAL.doc
74
volatile ULARGE_INTEGER *pCurTicks
);
{
TimerStruct_t *pTimer = gSysTimers[OS_TIMER].pt;
DWORD dwTick = dwTimerCountdownMSec * OEMCount1ms;
DWORD dwCount;
// If timer IRQ pending, a full resched period elapsed
if (CPUClearSysTimerIRQ( )) {
*pCurMSec += dwTimerCountdownMSec;
pCurTicks->QuadPart += dwTick;
return dwTimerCountdownMSec;
}
// No timer IRQ pending, calculate how much time has elapsed
dwCount = pTimer->TimerValue;
if (dwCount > dwTick) {
// This is an error case. Recover gracefully.
dwCount = dwTick;
}
else {
dwCount = dwTick - dwCount;
}
pCurTicks->QuadPart += dwCount;
dwCount += *pPartialCurMSec;
*pPartialCurMSec = dwCount % OEMCount1ms;
*pCurMSec += (dwCount /= OEMCount1ms);
return dwCount;
}
The following code example shows an implementation for the DDB5476 platform using a
free-running timer with value and compare registers.
DWORD CPUGetSysTimerCountElapsed(
DWORD dwTimerCountdownMSec,
volatile DWORD *pCurMSec,
DWORD *pPartialCurMSec,
Filename: HowToDevelopAnOAL.doc
volatile ULARGE_INTEGER *pCurTicks
);
{
DWORD dwTick = dwTimerCountdownMSec * OEMCount1ms;
DWORD dwCount = R4000Compare( ) - R4000Count( );
// Note: if dwCount is negative, the counter went past the compare
// point. The math still works because it accounts
// for the dwTick time plus the time past the compare point.
dwCount = dwTick - dwCount;
pCurTicks->QuadPart += dwCount;
dwCount += *pPartialCurMSec;
*pPartialCurMSec = dwCount % OEMCount1ms;
dwCount /= OEMCount1ms;
*pCurMSec += dwCount;
return dwCount;
}
See Also
Boilerplate Interface Routines
75
Filename: HowToDevelopAnOAL.doc
76
Adding KITL Initialization Code
Kernel independent transport layer (KITL) support enables a debug communications
channel with the development workstation through Platform Builder, and is essential for
enabling kernel debugger support. If the KITL connection occurs over an Ethernet
connection, the Ethernet debug (EDBG) library used in the boot loader development
process can be used. The EDBG library will be referenced and linked into the kernel
executable. For more information about KITL, see Kernel Independent Transport
Layer.
The following list shows the suggested tasks when adding KITL initialization code to the
OEMInit function:
•
Initialize any PCI bridges and devices, and then enumerate, assign resources, and
enable these.
Note This should only be done for the KITL network interface card (NIC) and for
any bridges required by the KITL NIC.
•
Perform any other bus initialization required for the CPU in order to recognize the NIC.
•
Initialize KITL by calling the KitlInit function. You can copy most of the code by using
Halkitl.c from another platform. KitlInit must be called before any debug services can
be started. For more information about KitlInit and enabling KITL, see Enabling KITL.
•
Implement the OEMKitlInit function, which searches for either KITL NIC, KITL serial,
or parallel connections. For more information about implementing OEMKitlInit, see
Implementing the OEMKitlInit Function.
OEMKitlInit performs the search by calling the OEMEthInit function, which in turn
performs the same tasks as the OEMPreDownload function in the boot loader.
For more information about the tasks performed by OEMPreDownload, see
Implementing the OEMPreDownload Function.
•
After implementing the OEMKitlInit function, include Kitl.lib and Kitleth.lib in your
Platform\<Platform>\Kernel\Buildexe\Kernkitl sources file.
After performing the above tasks, you can enable KITL polling.
See Also
How to Develop an OEM Adaptation Layer
Filename: HowToDevelopAnOAL.doc
77
Enabling KITL
The KITL version of the OS image is placed in the release directory when the IMGNOKITL
flag is not set, and the non-KITL image is released when IMGNOKITL is set to 1. KITL
functionality can be included in both debug and retail devices, depending on your needs.
It can also be configured to start on boot, called active KITL, or to start the first time a
service on the device registers with KITL, called passive KITL. Passive KITL works with
KITL in an inactive state until it is required. Passive KITL does not affect the overall
performance of the device. For more information, see Active and Passive KITL.
The KITLTRANSPORT structure defines a KITL transport, which provides all the KITL
functionality. KITL needs to communicate with the underlining hardware including the
ability to encode and decode a packet, send and receive a frame or packet, enable and
disable the transport interrupt, and get and set configuration information. The following
table shows the functions you need to implement to support this functionality.
T
Function
Description
TransportEncode
Makes a transport ready packet that KITL can later send
from the transport through TransportSend. The
implementation frames the data with transport specific
and checksum information. Using an Ethernet transport,
TransportEncode frames the packet with a User
Datagram Protocol (UDP) header.
TransportDecode
Called when KITL receives a raw packet from the transport
through TransportRecv to extract the data field of the
packet. Using an Ethernet transport, TransportDecode
will validate the packet, checksum, and return the pointer
immediately after the UDP header.
TransportSend
Called to send a raw packet, framed by
TransportEncode, from the transport. KITL guarantees
exclusive access to the transport when TransportSend is
called. Using an Ethernet transport, TransportSend will
program the hardware and place the packet on the
Ethernet.
TransportRecv
Called to receive a raw packet from the transport. KITL
guarantees exclusive access to the transport when
TransportRecv is called. Using an Ethernet transport
TransportRecv will read from the Ethernet hardware.
TransportEnableInt
Called to enable or disable transport interrupt.
TransportGetDevCfg
Called to provide device information to the debug host
with the transport defining how the data appears. KITL
handles the data as an opaque entity and sends it to the
debug host. TransportGetDevCfg is only called at the
initial handshake with the debug host. Using an Ethernet
transport, the information usually includes IP address,
MAC address, and port number.
Filename: HowToDevelopAnOAL.doc
TransportSetHostCfg
78
Called when KITL receives the transport information of the
debug host with the transport defining how the data
appears. KITL handles the data as an opaque entity and
TransportSetHostCfg can use the information to setup
the transport. TransportSetHostCfg is only called at the
initial handshake with the debug host.
KITL is initialized by calling KitlInit typically during the OEMInit phase of the boot
process. KitlInit must be called before any debug services can be started. As part of the
KitlInit processing, the kernel calls the OEMKitlInit function to perform hardware
initialization.
KitlInit may be called any time after the KernelStart function has been called, typically
as part of OEMInitDebugSerial or OEMInit processing. KitlInit should also be called in
the resume from suspend processing in OEMPowerOff. The parameter fStartKitl tells the
kernel if it should start the IST at the time of call. If fStartKitl is FALSE, KITL behaves in a
passive mode, waiting dormant until KITLRegisterClient is called. This technique is used
on test devices that require the use of just-in-time (JIT) debugging. If fStartKitl is TRUE,
KITL will start communicating with the desktop and will block until a connection is made.
Also, when fStartKitl is set to TRUE the default services PPSH, DBGMSG, and KDBG are
started based on the configuration settings from the desktop. Because KITL is used to
attach all full-function debugging tools, if an error occurs during KITL initialization, use
KITLOutputDebugString to output debug messages. KITLOutputDebugString
eventually uses an OEM supplied function in the OAL for debug messages. The kernel then
calls the OEM's routine.
Once KITL is up and running, it is in a waiting state, waiting for data to be received by the
transport and waiting for device side services to send data out. The typical call sequence
is shown in the following code example.
RecvData ()
{
LockTransport ();
Result = KITL. TransportRecv (pData, pcbShort);
UnlockTransport ();
If (Result) {
pData = KITL.TransportDecode (pData, pcbShort);
}
}
Filename: HowToDevelopAnOAL.doc
79
When the KITL protocol has data to send, it passes a buffer to the TransportEncode
function that has space allocated for the header and trailer. TransportEncode adds the
header and trailer to the frame and passes the encapsulated data back to the kernel. The
kernel then calls TransportSend to send out the data to the desktop side, as shown in the
following code sample.
SendData (...)
{
if (KITL.TransportEncode (pData, cbShort)) {
// pData has room for Hdr/Tlr allocated
LockTransport ();
KITL.TransportSend (pData, cbShort + KITL.FrmHdrSize + KITL.FrmTlrSize);
UnlockTransport ();
}
}
If your KITL transport requires a timer, the KITL protocol supports a 1 second resolution
timer. The timer requires a call to KitlSetTimerCallback. The timer is a not a periodic
timer and is only called once every nSecs seconds. If you need the timer to be called
periodically, you can call KitlSetTimerCallback again when the timer expires. You can
also request more than one timer.
When the timer is no longer needed, call KitlStopTimerCallback to stop the timer.
See Also
How to Develop an OEM Adaptation Layer | Adding KITL Initialization Code
Filename: HowToDevelopAnOAL.doc
80
Implementing the OEMKitlInit Function
The following list shows the suggested tasks for the OEMKitlInit function:
•
Locate the KITL NIC, typically through driver globals. The KITL NIC is usually the same
NIC used by the boot loader for download operations.
•
Assign function pointers to the correct EDBG NIC driver functions. If the necessary
EDBG driver is not provided, you will need to create your own. The following code
example shows how you can assign function pointers to an EDBG NIC driver.
pfnEDbgInit
= NE2000Init;
pfnEDbgInitDMA
= NULL;
pfnEDbgEnableInts
= NE2000EnableInts;
pfnEDbgDisableInts
= NE2000DisableInts;
pfnEDbgGetPendingInts = NE2000GetPendingInts;
pfnEDbgGetFrame
= NE2000GetFrame;
pfnEDbgSendFrame
= NE2000SendFrame;
pfnEDbgReadEEPROM
= NE2000ReadEEPROM;
pfnEDbgWriteEEPROM
= NE2000WriteEEPROM;
pfnEDbgSetOptions
= NE2000SetOptions;
pfnCurrentPacketFilter= Ne2000CurrentPacketFilter;
pfnMulticastList
= NE2000MulticastList;
•
Include the library in the Buildexe\Kernkitl sources file to satisfy the linker if any KITL
driver support is required.
•
Initialize the DMA buffers for the NIC, if required, and then initialize the NIC by calling
the pfnEdbgInitDMABuffer or pfnEdbgInit, or both, function pointers. The NIC
initialization function takes a NIC address and returns a MAC value for the NIC, which
should be stored in the driver globals.
•
Create a unique device name, which will be used as a handle by Platform Builder.
Note
The device name must be the same as the one used by the boot loader.
•
Obtain an IP address from a DHCP server using the EbootGetDHCPAddr function or
through a static IP address. If you are obtaining the IP address from a DHCP server,
include Eboot.lib in the Kernel\Buildexe\Kernkitl sources file.
•
Initialize the KITL Ethernet transport code by calling the KitlEtherInit function. If the
call succeeds, fill out the KITLTRANSPORT structure.
•
Implement the following OEM Ethernet functions:
•
OEMEthCurrentPacketFilter
•
OEMEthDisableInts
•
OEMEthEnableInts
•
OEMEthGetFrame
•
OEMEthGetSecs
•
OEMEthMulticastList
•
OEMEthSendFrame
Filename: HowToDevelopAnOAL.doc
•
81
OEMEthSetFilter
During the call to OEMKitlInit, you provide the following information that the kernel will
need to support KITL high-level functionality:
•
Complete the KITLTRANSPORT structure that describes what services to start on
boot
•
Determine whether it is a cold boot
•
Provide the name of the device
•
Provide an interrupt number for interrupt based devices
•
Provide the window size for default services
•
Provide the size of the frame header specific to your transport
•
Provide the size of the frame trailer specific to your transport
•
Provide the size of the physical buffer for default services
•
Provide function pointers to the transport-specific functions described above
T
When the kernel returns from OEMInit, it tries to initialize the KITL interrupt by calling the
TransportEnableInt member of the KITLTRANSPORT structure.
TransportEnableInt is also called when a device resumes from a suspended state.
T
See Also
How to Develop an OEM Adaptation Layer | Adding KITL Initialization Code
Filename: HowToDevelopAnOAL.doc
82
Implementing the Real-Time Clock and
System Timer
Windows CE provides a variety of functions that your OAL can use to manage time. These
functions fall into two categories: real-time clock functions and system-timer functions.
Implementing Real-Time Clock Functions
Real-time clock functions manage time-of-day information. The file Rtc.c, located in
%_WINCEROOT%\Platform\<Platform>\Kernel\Hal\<Microprocessor>, defines the
interfaces that Windows CE uses to communicate with the kernel and the real-time clock.
The following table shows the functions that you must implement on platforms that
provide the time of day to the user.
Function
Description
OEMGetRealTime
Gets the system time.
OEMSetRealTime
Sets the system time.
OEMSetAlarmTime
Sets the alarm time.
You can also use the IOCTL_HAL_INIT_RTC I/O control code in a call to KernelIoControl
to force real-time clock initialization. For more information about how to set the real-time
clock, see Setting the Real-Time Clock.
Implementing the System-Timer Functions
To query the system timer, Windows CE provides the GetTickCount function. Windows
CE also provides a prototype implementation of the system-timer ISR.
The OAL system-timer function, SC_GetTickCount, returns the current number of
milliseconds since boot. Windows CE calls SC_GetTickCount in your OAL when the
Microsoft Win32® function GetTickCount is called. You must register the system-timer
interrupts in OEMInit to ensure the system timer is updated appropriately.
Each sample platform provides a sample implementation of the system-timer ISR. Each
time the interrupt goes off, the ISR should reset the hardware and increment the variable
CurMSec by one millisecond.
See Also
How to Develop an OEM Adaptation Layer
Filename: HowToDevelopAnOAL.doc
83
Customizing Memory
After calling OEMInit, the kernel calls the function pointed to by
pNKEnumExtensionDRAM if provided, otherwise it calls OEMGetExtensionDRAM
function to determine whether a contiguous bank of dynamic RAM (DRAM) is available on
the target device. The kernel also calls this function when a user adds and removes
memory. If a user adds memory, the kernel can continue processing without interruption.
However, the kernel assumes that the additional memory, called extension memory,
contains part of the object store, and therefore performs a cold boot to reinitialize the
object store when a user removes the extension memory.
While you can define memory areas in the Config.bib file, you can define extension
memory using OEMGetExtensionDRAM, OEMEnumExtensionDRAM, and the
MainMemoryEndAddress global variable only. Because the kernel will use any memory
that it is aware of, you should not define any memory areas that you want to reserve for
a driver. For more information about the Config.bib file, see Config.bib Customization.
The following list shows the various ways you can customize how your system manages
memory:
•
Configuring System Memory
•
Configuring Object Store Memory
•
Creating Physical to Virtual Mappings
•
Memory Addressing
See Also
How to Develop an OEM Adaptation Layer | Memory Architecture | Object Store and
Registry
Filename: HowToDevelopAnOAL.doc
84
Configuring System Memory
The amount of RAM allocated to the kernel for an operating system (OS) use can be
configured in four different areas:
•
Config.bib Customization
•
MainMemoryEndAddress Customization
•
OEMGetExtensionDRAM Customization
•
pNKEnumExtensionDRAM Customization
In each case, the memory identified requires that the physical-to-virtual mapping tables
are initialized so that the kernel can implement the proper support to use the memory.
In the SHx and MIPS families, the CPU defines this mapping, and the kernel requires no
additional information.
In the x86 and ARM families, you must set up the proper entries in OEMAddressTable to
identify your RAM regions. You should create RAM entries for the largest possible amount
of RAM that would be present on the device. This help avoid the need to change the OEM
adaptation layer (OAL) if you add more memory. If RAM is not mapped during the boot
process through OEMAddressTable, it can be mapped later by calling
CreateStaticMapping or NKCreateStaticMapping.
However memory is specified to the kernel, the kernel will create a single pool that will be
used for all memory allocations.
See Also
Customizing Memory | How to Configure and Build an OS Image for a CEPC
Filename: HowToDevelopAnOAL.doc
85
Config.bib Customization
The Config.bib file is the first place to declare how much memory will be made available to
the kernel. A single entry is required and should indicate the default amount of RAM that
is possible for the device.
The following code example shows a sample Config.bib file.
Section Name
RAM
Start RAM Address
80b00000
Number of Bytes
00500000
Section Type
RAM
Only one contiguous memory section of RAM can be reported in the Config.bib file. All
other memory extensions must be handled through MainMemoryEndAddress,
OEMGetExtensionDRAM, or pNKEnumExtensionDRAM customization. The start
address of RAM and the number of bytes determine the value of ulRAMEnd (start +
number of bytes) where ulRAMEnd is a parameter of the table of content specified in
Romldr.h. ulRAMEnd is calculated and set in the OS image every time an OS image is
created.
See Also
Configuring System Memory
Filename: HowToDevelopAnOAL.doc
86
MainMemoryEndAddress Customization
The MainMemoryEndAddress kernel variable is the only mechanism that allows you to
extend the size of the contiguous RAM section specified in the Config.bib file. During the
boot process, the kernel sets the value of MainMemoryEndAddress to the value stored
in the table of contents ulRAMEnd. During OEMInit, you can detect the existence of
additional contiguous RAM and reset the MainMemoryEndAddress value to indicate that
there is more contiguous RAM available.
The value of ulRAMEnd is based on the RAM section of the Config.bib file and what effect
other flags have on ROMIMAGE. For example, the AUTOSIZE flag can use more or less
memory than specified by the RAMIMAGE section and therefore affect the RAM size.
See Also
Configuring System Memory
Filename: HowToDevelopAnOAL.doc
87
OEMGetExtensionDRAM Customization
If there is a second region of RAM that is not contiguous with the RAM specified in the
Config.bib file, OEMGetExtensionDRAM can be used to report the additional memory to
the kernel. During the boot process, the kernel calls OEMGetExtensionDRAM to
determine if there is a second region of RAM that the OEM wants to report to the kernel.
If no second region is available, it returns FALSE.
See Also
Configuring System Memory
Filename: HowToDevelopAnOAL.doc
88
pNKEnumExtensionDRAM Customization
If there is a base RAM section plus more than one noncontiguous RAM section, you need
to implement the pNKEnumExtensionDRAM function pointer. If you have a base RAM
section and one or zero noncontiguous RAM sections, you do not need to implement this
function pointer. The pNKEnumExtensionDRAM variable is capable of reporting up to 15
different noncontiguous RAM regions and like OEMGetExtensionDRAM, it is called
during the cold boot phase. If pNKEnumExtensionDRAM is set, OEMGetExtensionDRAM
is not called during the boot process. Note that when adding multiple sections that there
is a performance impact for every memory section that is added to the kernel. The impact
is due to the kernel tracking and searching for free physical memory in different memory
sections.
See Also
Configuring System Memory
Filename: HowToDevelopAnOAL.doc
89
Configuring Object Store Memory
Once the available RAM is calculated, the kernel calculates how much memory will be
allocated to the object store, Filesys.exe. The kernel first uses the FSRAMPERCENT value
that is logged in Config.bib. During the boot process, it gives you access through
pOEMCalcFSPages to change the default allocation. Once the final value is calculated,
the kernel divides the memory between the kernel allocation used for running programs
and the Filesys.exe allocation used for the object store. When the system is running, this
division can be determined by calling GetSystemMemoryDivision or
SetSystemMemoryDivision.
See Also
Customizing Memory
Filename: HowToDevelopAnOAL.doc
90
Creating Physical to Virtual Mappings
After the kernel has defined physical memory, it must create a physical-to-virtual
mapping so that all physical memory is accessible to applications and interrupt service
routines (ISRs).
The physical-to-virtual mapping is a mapping from the physical address location of the
RAM, or other types of memory, to a statically mapped virtual address that is used by
applications and ISRs. In the 4-GB address space, the statically mapped virtual addresses
live in the 3-GB range 0x80000000 to 0xc0000000. On MIPS and SHx platforms, this
mapping is defined by the CPU but is not mapped through the memory management unit
(MMU).
On x86 and ARM processors, you must create an OEMAddressTable and pass it to the
kernel. Each entry in the table specifies a physical location in memory, the size of the
memory, and the static virtual memory address to which to map it. The static virtual
address is specified in the cached memory range and the kernel can then create the
uncached address that points to the same physical address.
After the kernel creates the original mapping during the boot process, an application or the
OAL can add to the statically mapped virtual address pool by calling
CreateStaticMapping or NKCreateStaticMapping. Memory mapped in this way is
located in range C400 0000 to E000 0000 and is created as uncached memory only.
See Also
Customizing Memory
Filename: HowToDevelopAnOAL.doc
91
Memory Addressing
There are two kinds of addresses in Microsoft® Windows® CE .NET: physical addresses
and mapped virtual addresses.
Physical addresses can be actual RAM or device memory that needs to be accessed by the
OS. It is defined by the physical address space from a CPU perspective. The CPU cannot
access this directly once the MMU is enabled. The kernel can only manage 512 MB of
physical memory. On some CPUs, like MIPS and SHx, the kernel can only directly
manipulate the first 1 GB of memory defined by the processors, 512 MB cached and 512
MB uncached but aliased to the same 512 MB of physical memory. On x86 and ARM
processors, you can divide this physical range by using OEMAddressTable.
Mapped virtual addresses define a mapping between the virtual and physical that can be
used by user-mode and kernel applications. There are two types of mapped virtual
addresses: static and dynamic. Static virtual addresses provide a mapping table for the
kernel that covers what virtual address range maps to a physical address range. This
mapping does not change over time and is created at boot time. OEMAddressTable
defines this static virtual to physical mapping on the x86 and ARM processors. On MIPS
and SHx, the CPU and kernel define this mapping. The static mapping can also be
extended after boot time by calling CreateStaticMapping or NKCreateStaticMapping.
Mappings created with CreateStaticMapping and NKCreateStaticMapping are
accessible only by the kernel.
A dynamically mapped virtual address is one that creates a virtual-to-physical mapping
that can be defined and then released when not in use. This type of mapping is accessible
to user-mode applications and is created by calling VirtualCopy.
Using VirtualCopy with the PAGE_PHYSICAL flag, an application can map a physical
address to a process-specific static virtual address. The process starts by calling
VirtualAlloc to allocate a virtual address range in your process to map the physical
address. This is followed by a call to VirtualCopy to complete the mapping process and
make the physical memory available in the process. A driver typically uses VirtualCopy to
map device-specific memory into its user-mode process space. The same region can be
mapped by more than one process.
CreateStaticMapping is like VirtualCopy in that it creates a mapping between physical
memory and static virtual memory. The main difference is that the static virtual address is
mapped in the kernel address space. This makes the static address accessible only by the
kernel. CreateStaticMapping is typically used to map some device-specific memory into
the kernel so that an ISR can access it. The same physical address can also be mapped by
using VirtualCopy.
See Also
Customizing Memory
Filename: HowToDevelopAnOAL.doc
92
Implementing the OEMIoControl Function
Windows CE–based functions, such as SystemParametersInfo and KernelIoControl,
supply information about the platform to applications. The Windows CE kernel obtains
some of the platform-specific information by calling your implementation of the OAL
function OEMIoControl.
To better understand what IOCTLs to implement and to learn more about the
implementation details for OEMIoControl, refer to a sample BSP that is based on
hardware similar to your own. OEMIoControl can expose any number of IOCTLs. You can
also define your own custom IOCTL, and some drivers expect specific IOCTLs to be
implemented. For more information about the IOCTLs you can implement, see the
Windows CE .NET Help.
The following topics contain information about how you can implement OEMIoControl in
your OAL to support various capabilities:
•
Supporting Interrupt Latency Timing
•
Providing System Information
•
Rebooting the Target Device
•
Setting the Real-Time Clock
•
Identifying Devices Uniquely
See Also
How to Develop an OEM Adaptation Layer
Filename: HowToDevelopAnOAL.doc
93
Supporting Interrupt Latency Timing
Interrupt latency timing measures the following two times:
•
The time between an interrupt being generated and its ISR being invoked.
•
The time between the ISR exiting and when the interrupt service thread (IST) begins
scheduled.
Latency timing is measured using Iltiming.exe and requires significant support from the
OAL. The interrupt latency measurement leverages the fact that timer ISRs should occur
at known times, on whole milliseconds. The number of timer ticks in dwIsrTime1
measures the interval between the timer interrupt occurring and the start of its ISR.
Iltiming.exe creates an interrupt object for SYSINTR_TIMING. It obtains a kernel mode
pointer to PerfCountSinceTick using one of the IOCTL_HAL_ILTIMING calls. When the
timer ISR returns with a value of SYSINTR_TIMING, the interrupt service thread in
Iltiming.exe is scheduled. The service thread runs in kernel mode, so it calls directly into
the kernel's code space to get the current PerfCountSinceTick value. It subtracts
dwIsrTime2 from this to measure the latency between when the ISR exits, presumably
very close to the time the IST is scheduled, and when the IST actually starts.
In some cases, other interrupts will occur between the time the Iltiming.exe IST is
scheduled and when it starts executing. Iltiming.exe determines when this has happened
by comparing dwIsrTime1 and dwIsrTime2; if an extra interrupt has occurred,
dwIsrTime1 will be greater than dwIsrTime2. Iltiming.exe can also determine if extra
interrupts have occurred by examining wNumInterrupts; this is why all ISRs need to set
these variables.
To support interrupt latency timing
1. Implement IOCTL_HAL_ILTIMING support in OEMIoControl. This code is hardware
independent and can be copied from any of the sample platforms. It includes the global
variables fIntrTime, dwIsrTime1, dwIsrTime2, and wNumInterrupts, among others
that should all be copied.
z. OEMInterruptEnable should return success for SYSINTR_TIMING without actually
performing an action.
aa. Implement PerfCountSinceTick and PerfCountFreq. PerfCountSinceTick is the
number of counter ticks that have elapsed since the latest timer interrupt occurred.
For more information, see PerfCountSinceTick and PerfCountFreq.
bb. All ISRs must check fIntrTime, one of the global variables, at their entry point. If this
variable is set, the ISR should set dwIsrTime1 to PerfCountSinceTick and increment
wNumInterrupts.
cc. If fIntrTime is set, the timer ISR should decrement dwIntrTimeCountdown and return
SYSINTR_TIMING if it is zero. Otherwise, it should return SYSINTR_RESCHED or
SYSINTR_NOP as normal. Once it is zero, dwIntrTimeCountdown should be reloaded
from dwIntrTimeCountdownRef; this variable is initialized through an
IOCTL_HAL_ILTIMING message.
dd. When it returns SYSINTR_TIMING, the timer ISR needs to set dwIsrTime2 to
PerfCountSinceTick.
The sample implementation of OEMIdle already checks fIntrTime and handles interrupt
latency timing as a special case.
Filename: HowToDevelopAnOAL.doc
See Also
How to Develop an OEM Adaptation Layer | Implementing the OEMIoControl Function
94
Filename: HowToDevelopAnOAL.doc
95
PerfCountSinceTick and PerfCountFreq
PerfCountSinceTick and PerfCountFreq access a high-performance counter that is
synchronized with the thread timer. They typically use the thread timer itself. They are
most effective when the underlying timer counts are fast enough to provide meaningful
data. If the thread timer is running at a rate of one tick per millisecond, these routines will
not provide much information. If the underlying counter runs at a rate of 100 ticks per
millisecond or more, the latency timers will be much more useful.
See Also
Supporting Interrupt Latency Timing | High-Performance Counter Support
Filename: HowToDevelopAnOAL.doc
96
ILTiming Support
ILTiming.exe is a testing tool that allows an OEM to measure ISR and IST latency. The
application that controls the test is in two parts and is released as source in
%_WINCEROOT%\Public\Common\Oak\Utils\Iltiming and
%_WINCEROOT%\Public\Common\Oak\Utils\It_load. ILTiming.exe works by using
system resources to track the amount of time spent in trying to get to the ISR and time
spent trying to get to the IST.
ILTiming.exe interacts with the kernel and OAL through an IOCTL_HAL_ILTIMING. If the
IOCTL_HAL_ILTIMING is supported and properly implemented, ILTiming.exe will be
able to measure ISR and IST latency. IOCTL_HAL_ILTIMING is passed to the OEM
through OEMIoControl. The parameter lpInBuf contains an ILTIMING_MESSAGE
structure and the wMsg parameter contains a command that must be executed on behalf
of the caller. The following table shows the values for the wMsg command.
Value
Description
ILTIMING_MSG_ENABLE
Enable the ILTiming process and set the frequency
of the measurements. The frequency is how often,
in ticks, before the next ISR and IST measurement
are taken.
ILTIMING_MSG_DISABLE
Disable ILTiming functionality.
ILTIMING_MSG_GET_TIMES
Return the time information about an ISR
measurement.
ILTIMING_MSG_GET_PFN
Return a function point to PerfCountSinceTick.
The ILTiming.exe file requires that you implement the following two functions:
•
PerfCountSinceTick: The amount of time since the last system tick or secondary
timer interrupt.
•
PerfCountFreq: The frequency of the system tick or secondary timer.
Both functions must be implemented to support the high-resolution counter APIs
QueryPerformanceCounter and QueryPerformanceFrequency.
See Also
Supporting Interrupt Latency Timing | Real-Time Measurement Tools
Filename: HowToDevelopAnOAL.doc
97
Providing System Information
You can implement OEMIoControl to support IOCTL_HAL_GET_DEVICE_INFO, which
provides support for SystemParametersInfo, which queries or sets system-wide
parameters and updates the user profile.
When Windows CE calls OEMIoControl with IOCTL_HAL_GET_DEVICE_INFO in the
dwIoControlCode parameter, the OS sets the nInBufSize parameter to 4 bytes and passes
a system parameters information (SPI) code in the lpInputBuf parameter. In order to
support the SPI code, OEMIoControl must support the following SPI flags:
Flag
Description
SPI_GETPLATFORMTYPE
Requests a string that identifies the Windows
CE–based platform type. Do not localize this string.
SPI_GETOEMINFO
Requests OEM-specific information. The information
string can include, but is not limited to, the model
name, model number, and manufacturer.
Although Windows CE does not restrict the lengths of the returned strings, long strings
may be unsuitable for display on some target devices or for communicating between
applications. Also, be sure to use SPI_GETPLATFORMTYPE names that are unique across
Windows CE products. Some host-side applications rely on this name for configuration.
Using unique names prevents target device type conflicts and ensures successful
integration with other Microsoft products.
The following code example shows how to handle SPI_GETPLATFORMTYPE and
SPI_GETOEMINFO requests.
// OemIoControl implementation in
%_WINCEROOT%\Platform\%BSP%\Kernel\Hal\Oemioctl.c:
const WCHAR HALPlatformStr[] = L"My platform";
const WCHAR HALOEMStr[] = L"Test";
BOOL OEMIoControl(...)
{ ...
switch (dwIoControlCode) {
case IOCTL_HAL_GET_DEVICE_INFO:
if (nInBufSize == 4) { // contains the SPI_* code
switch (*(LPDWORD)lpInBuf) {
case SPI_GETPLATFORMTYPE:
len = (strlenW(HALPlatformStr)+1)*sizeof(WCHAR);
if (nOutBufSize >= len) {
memcpy(lpOutBuf,HALPlatformStr,len);
retval = TRUE;
Filename: HowToDevelopAnOAL.doc
} else
SetLastError(ERROR_INSUFFICIENT_BUFFER);
break;
case SPI_GETOEMINFO:
len = (strlenW(HALOEMStr)+1)*sizeof(WCHAR);
if (nOutBufSize >= len) {
memcpy(lpOutBuf,HALOEMStr,len);
retval = TRUE;
} else
SetLastError(ERROR_INSUFFICIENT_BUFFER);
break;
default:
SetLastError(ERROR_INVALID_PARAMETER);
}
} else {
SetLastError(ERROR_INVALID_PARAMETER);
}
break; // End switch case for IOCTL_HAL_GET_DEVICE_INFO
...
See Also
How to Develop an OEM Adaptation Layer | Implementing the OEMIoControl Function
98
Filename: HowToDevelopAnOAL.doc
99
Rebooting the Target Device
You have the option of implementing IOCTL_HAL_REBOOT in OEMIoControl to reboot
the target device. For example, an application can display a dialog box to query the user
to reboot the target device, and then call IOCTL_HAL_REBOOT to perform the reboot.
Platform Builder provides a sample implementation in
%_WINCEROOT\Platform\Cepc\Kernel\Hal.
See Also
How to Develop an OEM Adaptation Layer | Implementing the OEMIoControl Function
Filename: HowToDevelopAnOAL.doc
100
Setting the Real-Time Clock
Some target devices cannot detect whether the real-time clock is initialized, and therefore
cannot determine when to reset it. For example, a computer-based target device that has
a clock with a battery backup may be unable to detect real-time clock initialization. You
change this behavior by implementing the I/O control code IOCTL_HAL_INIT_RTC in
OEMIoControl. The Windows CE–based file system passes IOCTL_HAL_INIT_RTC to
KernelIoControl during initialization for a cold boot sequence. The following code
example shows the SYSTEMTIME structure that the file system passes.
const SYSTEMTIME st = {1999,6,0,1,12,0,0,0};
KernelIoControl(IOCTL_HAL_INIT_RTC, (UCHAR *)&st, NULL, 0, &bytesUsed);
To set the real-time clock in OAL code
1. Validate the clock during the OEM boot process in your OAL code.
ee. Set the real-time clock to the value stored in SYSTEMTIME, if the boot process did not
return a valid time.
ff. Call OEMSetRealTime in OEMIoControl to set the real-time clock of the target
device.
The following code example shows one implementation of setting the real-time clock.
case IOCTL_HAL_INIT_RTC:
// The kernel has detected a cold boot.
// The real-time clock probably needs to be reset.
if( nInBufSize >= sizeof(SYSTEMTIME) )
return OEMSetRealTime( (LPSYSTEMTIME)lpInBuf );
else
return FALSE;
break;
See Also
How to Develop an OEM Adaptation Layer | Implementing the OEMIoControl Function
Filename: HowToDevelopAnOAL.doc
101
Identifying Devices Uniquely
You may need to implement a mechanism to uniquely identify a Windows CE–based device.
For example, you may need to identify each cell phone that runs your OS image for billing
and security purposes. Windows CE uses the DEVICE_ID structure to hold the unique
device identification number. You can implement the I/O control code
IOCTL_HAL_GET_DEVICEID in OEMIoControl to return DEVICE_ID to an application.
See Also
How to Develop an OEM Adaptation Layer | Implementing the OEMIoControl Function
Filename: HowToDevelopAnOAL.doc
102
Completing an OAL
Completing an OAL is the final step of creating an OAL. In this stage, you can implement
any other features you choose to support.
To complete an OAL
1. Create a CEDDK.
For more information, see Implementing CEDDK.dll.
gg. Erase the object store.
For more information, see Erasing the Object Store.
hh. Implement support for high-resolution timers.
For more information, see Supporting High-Resolution.
ii. Configure the process boot phase.
For more information, see Configuring the Process Boot Phase.
See Also
How to Develop an OEM Adaptation Layer | OEM Adaptation Layer
Filename: HowToDevelopAnOAL.doc
103
Erasing the Object Store
The object store for Windows CE is persistent across warm boots as long as the boot
process does not cause other code that changes the contents of RAM, such as hardware
initialization code, to get executed. On occasion, you may want to completely erase the
object store by triggering a cold reboot. For example, you may create a prototype that
tests the cold system boot process. While an application can call the
SetCleanRebootFlag function to perform a cold boot, you can also implement the
following procedure in the boot process.
To erase the object store during the boot process
1. Include the Romldr.h file in your OAL code.
jj. Declare the following variable in your OAL.
extern ROMHDR *const volatile pTOC;
The ulRAMFree member of the ROMHDR structure identifies the start of free
memory. The UlRAMFree location also identifies the start of the object store
information.
kk. Signal a cold boot in OEMInit by setting the first three DWORD variables starting at
pTOC->ulRAMFree, to 0.
The following code example shows how to set the first three DWORD variables to 0.
memset(pTOC->ulRAMFree,0,sizeof(DWORD)*3)
Setting the first three DWORD values to 0 during OEMInit ensures that a cold boot of the
object store occurs during the boot process.
See Also
Completing an OAL
Filename: HowToDevelopAnOAL.doc
104
Supporting High-Resolution Timers
The high-resolution timer functions QueryPerformanceCounter and
QueryPerformanceFrequency allow independent software vendors (ISVs) to use the
high-resolution timers available on your target device. These high-resolution timers
enable more accurate timings than the one-millisecond granularity available through the
GetTickCount function. For more information, see High-Performance Counter Support.
The OAL sample code provides default implementations of the performance querying
functions. If you do not provide high-resolution timers on your platform, the default
capabilities of the performance querying functions are implemented using GetTickCount.
In the default code, a call to QueryPerformanceCounter returns the value represented
by GetTickCount, while QueryPerformanceFrequency returns 1000.
To support high-resolution timers
1. Provide your own implementations of the OEMQueryPerformanceCounter and
OEMQueryPerformanceFrequency functions.
ll. Add code to OEMInit to set the variables pQueryPerformanceCounter and
pQueryPerformanceFrequency to point to your implementations of the
OEMQueryPerformanceCounter and OEMQueryPerformanceFrequency
functions.
mm.
Make any further calls to the QueryPerformance functions through their
respective pointers.
See Also
Completing an OAL
Filename: HowToDevelopAnOAL.doc
105
High-Performance Counter Support
There are two built-in high-performance counter routines, QueryPerformanceCounter
and QueryPerformanceFrequency. QueryPerformanceCounter returns a 64-bit
number representing timer counts and QueryPerformanceFrequency returns the
number of high-performance ticks per second. It is used to convert counts to a time value.
In many cases, platforms do not implement 64-bit high-resolution counters in hardware.
A workaround is to implement the counter using 32-bit hardware counters and implement
the rollover to 64-bits through an interrupt.
Another is to use the thread scheduling timer to update a 64-bit tick count. Like
PerfCountSinceTick, this works well if the underlying hardware timer has a large
number of ticks per millisecond. Otherwise, OEMQueryPerformanceCounter does not
offer much of an advantage over GetTickCount.
If the thread timer tick will meet your needs, you can use the boilerplate implementations
of QueryPerformanceCounter and QueryPerformanceFrequency provided with the
sample platforms. The sample routines are implemented in terms of PerfCountSinceTick
and PerfCountFreq, which are also used in interrupt latency timing.
See Also
Supporting High-Resolution Timers | Implementing the OEMInit Function |
PerfCountSinceTick and PerfCountFreq
Filename: HowToDevelopAnOAL.doc
106
OSBench Support
OSBench.exe is used to measure the performance of the scheduler. The measured
performance is stored in two areas:
%_WINCEROOT%\Public\Common\Oak\Utils\Osbench and
%_WINCEROOT%\Public\Common\Oak\Utils\Ob_load. OSBench.exe works by using
system resources to track the amount of time required to perform some scheduling tasks.
The only requirement that OSBench.exe has on the OEM is to support the
QueryPerformanceCounter and QueryPerformanceFrequency.
See Also
Supporting High-Resolution Timers | Real-Time Measurement Tools
Filename: HowToDevelopAnOAL.doc
107
Configuring the Process Boot Phase
When you finish adapting your OAL, you can set the system registry for your platform.
When Windows CE begins loading, the kernel starts the file system and examines the
HKEY_LOCAL_MACHINE\Init registry key to identify what applications to run.
Although you determine which applications the OS loads, most platforms include the
following applications:
•
Debug shell (Shell.exe)
Shell.exe communicates with Cesh.exe application running on your development
workstation. Include only Shell.exe in your OS image for debugging and development.
Shell.exe displays the Windows CE prompt in the command prompt build window after
you use Cesh.exe to transfer the Windows CE image.
•
Device Manager (Device.exe)
•
Graphics, Windowing, and Events Subsystem (Gwes.exe)
Gwes.exe also loads and initializes input device drivers such as the keyboard driver
and the touch driver.
•
Task Manager (Taskman.exe)
To control which applications run at system startup, create launch registry values. Launch
registry values do not need to be sorted in the registry, although you can specify
dependencies. You can specify up to 32 applications. The following table shows the entries
for the HKEY_LOCAL_MACHINE\Init registry key.
Entry
Value
Type
Launchnn
program.exe
REG_SZ
Dependnn
hex:xx,yy[,xx,yy]
REG_BINARY (yy is the most significant bit)
Launch registry values have optional dependencies as denoted by the Dependnn registry
value. Dependnn registry values specify applications that Windows CE must be running
before the Launchnn applications run. Dependnn registry values begin with the keyword
Depend, followed by the same decimal number as the Launchnn registry value. The
Dependnn registry values define an order in which Windows CE launches applications.
One or more dependent applications can be specified per Dependnn value. Dependent
applications are specified as a series of Words in hexadecimal notation. The following code
example shows a typical Init registry entry using dependencies.
[HKEY_LOCAL_MACHINE\Init]
"Launch10"="shell.exe"
"Launch20"="device.exe"
"Launch30"="gwes.exe"
"Depend30"=hex:14,00
"Launch50"="taskman.exe"
"Depend50"=hex:14,00, 1e,00
In the preceding example, Gwes.exe is dependent on Device.exe starting and
Taskman.exe is dependent on Device.exe and Gwes.exe starting.
Filename: HowToDevelopAnOAL.doc
108
After Windows CE calls an application using the Init registry value, the application must
call the SignalStarted function. SignalStarted indicates that the application is ready for
the rest of the OS to continue processing. The value that is passed as the parameter to
SignalStarted is the value passed on the command line of the application started from
the Init key. For more information about the format of the registry, see Registry File. For
information about default registry settings, see Registry Settings.
See Also
Completing an OAL
Filename: HowToDevelopAnOAL.doc
109
Testing an Enhanced OAL
Windows CE loads Filesys.exe before any other applications. To test your OAL, create a
test application that replaces Filesys.exe and prints in a loop by incrementing seconds
using Sleep and GetTickCount.
To test the OAL
1. Call the GetTickCount function.
nn. Print the value returned by GetTickCount.
oo. Call the Sleep function to suspend the thread.
Monitor the duration of the sleep period.
pp. Call GetTickCount again.
qq. Print the value returned by GetTickCount.
rr. Compare the two values returned by GetTickCount.
The second call to GetTickCount should return the amount of time that has elapsed since
the previous call to this function. Therefore, the value returned by the second call to
GetTickCount should be nonzero and should not match the value returned from the first
call to this function.
Note
GetTickCount returns time in the form of milliseconds.
If the value returned by the second call to GetTickCount matches the amount of time that
your system was in a suspended state, then you have successfully created a base OAL.
See Also
How to Develop an OEM Adaptation Layer