Provides a work through tutorial that allows you to port an Asic implementation to the template variant.
This tutorial describes how to implement the Asic class. This is a pure virtual interface that is defined and called by the Kernel, but which must be implemented by the ASSP/Variant. The tutorial assumes that the ASSP/Variant is split into an ASSP layer and a Variant layer.
For a minimal port, it isn't necessary to provide implementations for the entire Asic class to be able to test that the kernel boots, provided that those functions that are not fully implemented have a dummy function so that the code will build.
The Asic class is defined in..\e32\include\kernel\arm\assp.h. For reference, the definition is:
class Asic { public: // initialisation virtual TMachineStartupType StartupReason()=0; virtual void Init1()=0; virtual void Init3()=0; // debug virtual void DebugOutput(TUint aChar)=0; // power management virtual void Idle()=0; // timing virtual TInt MsTickPeriod()=0; virtual TInt SystemTimeInSecondsFrom2000(TInt& aTime)=0; virtual TInt SetSystemTimeInSecondsFrom2000(TInt aTime)=0; virtual TUint32 NanoWaitCalibration()=0; // HAL virtual TInt VariantHal(TInt aFunction, TAny* a1, TAny* a2)=0; // Machine configuration virtual TPtr8 MachineConfiguration()=0; };
Taking the template port as a concrete example, the ASSP layer implementation of the Asic class is defined and implemented by the TemplateAssp class, and the Variant implemention is defined and implemented by the Template class.
Entry conditions
called in the context of the initial (null) thread
interrupts are disabled
there is no kernel heap
memory management functions are not available.
What the function should do
This is called during stage 1 of kernel initialisation.
In this function, you need to:
initialise the real time clock
initialise the interrupt dispatcher before CPU interrupts are enabled.
set the threshold values for cache maintenance. You can set separate values for:
You use the Cache::SetThresholds() interface to set these values.
As an example of what the threshold values mean, if you purge a memory region from cache, and the size of that region is greater than the threshold value, then the entire cache is purged. If the size of that region is less than or equal to to the threshold value, then only the region is purged.
The threshold values are platform specific, and you need to choose your values based on your own performance measurements. Symbian cannot make recommendations. If you choose not to set your own values, Symbian platform supplies a set of default values, which are set by Cache::Init1().
Note that there is also a Cache::GetThresholds() interface that you may find useful.
set up the RAM zones. For details, see the RAM Zone Tutorial.
Typically, you would also initialise any memory devices not initialised by the bootstrap. Any other initialisation that must happen early on should also be done here.
The kernel calls the Variant's Init1() function. On the template port, this is the Variant layer's Init1(), i.e. the functions Template::Init1(). The source for this is in ...\template_variant\specific\variant.cpp.
void Template::Init1() { __KTRACE_OPT(KBOOT,Kern::Printf("Template::Init1()")); // // TO DO: (mandatory) // // Configure Memory controller and Memrory Bus parameters (in addition to what was done in the Bootstrap) // __KTRACE_OPT(KBOOT,Kern::Printf("Memory Configuration done")); // // TO DO: (optional) // // Inform the kernel of the RAM zone configuration via Epoc::SetRamZoneConfig(). // For devices that wish to reduce power consumption of the RAM IC(s) the callback functions // RamZoneCallback() and DoRamZoneCallback() will need to be implemented and passed // to Epoc::SetRamZoneConfig() as the parameter aCallback. // The kernel will assume that all RAM ICs are fully intialised and ready for use from boot. // // // TO DO: (optional) // // Initialise other critical hardware functions such as I/O interfaces, etc, not done by Bootstrap // // if CPU is Sleep-capable, and requires some preparation to be put in that state (code provided in Bootstrap), // the address of the idle code is writen at this location by the Bootstrap // e.g. // iIdleFunction=*(TLinAddr*)((TUint8*)&Kern::SuperPage()+0x1000); // TemplateAssp::Init1(); }
The last line is a call into the ASSP layer, which is implemented as shown below. On the template port, it is the ASSP layer that initialises the interrupt dispatcher and the real time clock. The source for this is in ...\template_assp\assp.cpp:
EXPORT_C void TemplateAssp::Init1() { __KTRACE_OPT(KBOOT,Kern::Printf("TemplateAssp::Init1()")); // // TO DO: (optional) // TemplateInterrupt::Init1(); // initialise the ASSP interrupt controller // // TO DO: (optional) // // Initialises any hardware blocks which require early initialisation, e.g. enable and power the LCD, set up // RTC clocks, disable DMA controllers. etc. // }
TemplateInterrupt::Init1(); is static function that initialises the interrupt dispatcher. See Interrupt Layer Initialisation.
Entry conditions
called in the context of the supervisor thread
the kernel is ready to handle interrupts
the kernel heap and memory management system is fully functional.
What the function should do
This is called during stage 3 of kernel initialisation.
In this function, you need to:
enable interrupt sources
start the millisecond tick timer.
Optionally, replace the implementation used by Kern::NanoWait().
Any other general initialisation can also be done here.
As an example, on the template port, the function is implemented in the Variant layer, by Template::Init3().
Millisecond tick timer
The kernel expects that the kernel's tick handler routine will be called at a fixed microsecond period, the value of which is returned by the implementation of Asic::MsTickPeriod() function. The Init3() function must be implemented to start this. See Kernel Timers for background information.
The template implementation is as follows:
EXPORT_C void TemplateAssp::Init3() { __KTRACE_OPT(KBOOT,Kern::Printf("TemplateAssp::Init3()")); TTemplate::Init3(); NTimerQ& m=*(NTimerQ*)NTimerQ::TimerAddress(); iTimerQ=&m; // // TO DO: (mandatory) // // If Hardware Timer used for System Ticks cannot give exactly the period required store the initial rounding value // here which is updated every time a match occurs. Note this leads to "wobbly" timers whose exact period change // but averages exactly the required value // e.g. // m.iRounding=-5; // TInt r=Interrupt::Bind(KIntIdOstMatchMsTimer,MsTimerTick,&m); // bind the System Tick interrupt if (r!=KErrNone) Kern::Fault("BindMsTick",r); // // TO DO: (mandatory) // // Clear any pending OST interrupts and enable any OST match registers. // If possible may reset the OST here (to start counting from a full period). Set the harwdare to produce an // interrupt on full count // r=Interrupt::Enable(KIntIdOstMatchMsTimer); // enable the System Tick interrupt if (r!=KErrNone) Kern::Fault("EnbMsTick",r); // // TO DO: (optional) // // Allocate physical RAM for video buffer, as per example below. However with some hardware, the Video Buffer // may not reside in main System memory, it may be dedicated memory. // // EXAMPLE ONLY TInt vSize=VideoRamSize(); r=Epoc::AllocPhysicalRam(2*vSize,TemplateAssp::VideoRamPhys); if (r!=KErrNone) Kern::Fault("AllocVRam",r); }
Servicing the timer interrupt
The timer interrupt service routine is required only to call the Ntimer::TickQ() function and perform any housekeeping necessary to ensure that the handler itself is called again after the time reported by the MsTickPeriod() routine. Since the handler is called frequently, it is written in assembler for the fastest execution.
__NAKED__ void MsTimerTick(TAny* aPtr) { // Service 1ms tick interrupt asm("ldr ip, [r0, #%a0]" : : "i" _FOFF(NTimerQ,iRounding)); asm("ldr r2, __KHwBaseOst "); asm("adds ip, ip, #2 "); asm("ldr r3, __KOst1000HzTickMatchIncrement "); asm("subcs ip, ip, #5 "); asm("str ip, [r0, #%a0]" : : "i" _FOFF(NTimerQ,iRounding)); asm("addcs r3, r3, #1 "); asm("mov r1, #%a0" : : "i" ((TInt)(1<<KHwOstMatchMsTimer))); asm("str r1, [r2, #0x14] "); // clear interrupt asm("ldr r1, [r2, #%a0]" : : "i" ((TInt)KHwOstMatchMsTimer*4)); // r1=old match value asm("add r1, r1, r3 "); // step match value on asm("ldr ip, [r2, #0x10] "); // r3=system timer value asm("str r1, [r2, #%a0]" : : "i" ((TInt)KHwOstMatchMsTimer*4)); asm("cmp ip, r1 "); // compare to next match value #ifdef _DEBUG asm("addpl r1, ip, #10 "); // in DEBUG if timer>match value, set match value to timer + a bit asm("strpl r1, [r2, #%a0]" : : "i" ((TInt)KHwOstMatchMsTimer*4)); asm("b Tick__7NTimerQ "); // call interrupt handler anyway #else asm("bmi Tick__7NTimerQ "); // if timer<match value, OK - call interrupt handler #endif // otherwise we are late for the next tick so force a data abort exception... asm("mvn r2, #0x10000002 "); // r2=0xeffffffd asm("str r2, [r2] "); // die // Constant data embedded in code. asm("__KOst1000HzTickMatchIncrement: "); asm(".word %a0" : : "i" ((TInt)KOst1000HzTickMatchIncrement)); asm("__KHwBaseOst: "); asm(".word %a0" : : "i" ((TInt)KHwBaseOst)); }
Note that it is a requirement that the timer period should be an integral number of microseconds, even if the exact period is not 1000us. It is always possible to add code to the interrupt handler to achieve this average so that over a large number of ticks, the deviation from this average will tend to 0, by adjusting the exact number of ticks from tick to tick. See also Timers
NanoWait() implementation
Kern::NanoWait() is a function that can be called if you want to wait for very short periods of time within the kernel. You call this function and specify the number of nanoseconds. The function is, in effect, a shell that uses default implementation code provided by the generic platform. You can provide your own implementation in your port, and register this with the platform. This allows the wait functionality to be implemented in the best possible way for your platform, possibly by using a hardware timer whose frequency is independent of the CPU frequency.
To replace the default implementation, you need to:
code your own function. This has the same signature as Kern::NanoWait():
void AsicImpl::DoNanoWait(TUint32 aInterval) { // Wait for aInterval nanoseconds }
where AsicImpl is the class that is ultimately derived from Asic.
register this implementation by adding the following call into your Asic::Init3() function:
Kern::SetNanoWaitHandler(AsicImpl::DoNanoWait);
You can see where this goes by looking at the template port at: ...\base\cedar\template\template_assp\template_assp.cpp
It is worth implementing this early so that it is possible to get trace output to see what the kernel is doing. This function is passed one character at a time. Normally this is sent to a UART, though it can be output through any convenient communications channel.
On the template port, this is implemented in the Variant layer, by Template::DebugOutput() in ...\template_variant\specific\variant.cpp.
If no power management has been implemented, then this function is called when the system is to idle to allow power saving. This function can just return, until power management is implemented. Once power management has been implemented, then idling behaviour will be handled by the power controller, i.e. the Variant's implementation of the DPowerController class
This function is used to return the number of microseconds per tick. To avoid timing drift, a tick frequency should be chosen that gives a round number of microseconds per tick. The function can return zero until the tick timer has been implemented.
On the template port, this function is implemented in the ASSP layer, and can be found in the source file ...\template_assp\assp.cpp. It is a simple function that just returns the value.
EXPORT_C TInt TemplateAssp::MsTickPeriod() { // // TO DO: (mandatory) // // Return the OST tick period (System Tick) in microseconds ( 10E-06 s ). // return 1000; // EXAMPLE ONLY }
See also Timers.
This is a function that the kernel uses to get the system time. Its signature is
Tint SystemTimeInSecondsFrom2000(Tint& aTime);
An implementation must set the aTime reference to the number of seconds that have elapsed since the start of the year 2000. This is a positive number; a negative number is interpreted as time before 2000.
For the template reference board, the implementation is as follows:
EXPORT_C TInt TemplateAssp::SystemTimeInSecondsFrom2000(TInt& aTime) { aTime=(TInt)TTemplate::RtcData(); __KTRACE_OPT(KHARDWARE,Kern::Printf("RTC READ: %d",aTime)); return KErrNone; }
Until a real time clock is implemented, this function can just return KErrNone.
This function calls the register access functions in the TTemplate class. See ...\template_assp\template_assp.cpp for implementation details.
Note that tracing output is provided when the KHARDWARE bit in the kerneltrace flags is set for the debug build.
This is a function that the kernel uses to set the system time. Its signature is
Tint SetSystemTimeInSecondsFrom2000(Tint aTime);
This sets the real time clock to the number of seconds that have elapsed since the start of the year 2000. This is a positive number; a negative number is interpreted as time before 2000.
For the template reference board, the implementation is as follows:
EXPORT_C TInt TemplateAssp::SetSystemTimeInSecondsFrom2000(TInt aTime) { // // TO DO: (optional) // // Check if the RTC is running and is stable // __KTRACE_OPT(KHARDWARE,Kern::Printf("Set RTC: %d",aTime)); TTemplate::SetRtcData(aTime); __KTRACE_OPT(KHARDWARE,Kern::Printf("RTC: %d",TTemplate::RtcData())); return KErrNone; }
Note that tracing output is provided when the KHARDWARE bit in the kerneltrace flags is set for the debug build. In this function, the trace output shows the value passed in from the kernel and then shows the value read back from the real time clock for verification.
The function Kern::NanoWait() can be called if you want to wait for very short periods of time within the kernel. You call this function and specify the number of nanoseconds. You can either use the default implementation of this function, or you can provide your own.
The default implementation provided by Symbian platform that Kern::NanoWait() uses is a busy loop that is calibrated by calling Asic::NanoWaitCalibration(). NanoWaitCalibration() should return the number of nanoseconds taken to execute 2 machine cycles. This is obviously dependent on the CPU clock speed, so if variants are likely to run at different speeds, then this should be implemented in the Variant layer.
This approach cannot always take into account factors such as processor frequency scaling. An alternative approach is for the Variant to supply its own implementation to be used by Kern::NanoWait(). Note that you do not replace Kern::NanoWait() itself as this is a shell function that results in a call to the the implementation. See Asic::Init3() for detail on how to replace the implementation.
On the template port, Asic::NanoWaitCalibration() is implemented in the ASSP layer, and not in the Variant layer, and can be found in the source file ...\template_assp\assp.cpp. It is a simple function that just returns the value.
EXPORT_C TUint32 TemplateAssp::NanoWaitCalibration() { // // TO DO: (mandatory) // // Return the minimum time in nano-seconds that it takes to execute the following code: // nanowait_loop: // subs r0, r0, r1 // bhi nanowait_loop // // If accurate timings are required by the Base Port, then it should provide it's own implementation // of NanoWait which uses a hardware counter. (See Kern::SetNanoWaitHandler) // return 0; // EXAMPLE ONLY }
You might find it useful to review User-Side Hardware Abstraction Technology first.
This is the HAL handler for the HAL group THalFunctionGroup::EHalGroupVariant.
This returns a TPtr8 descriptor representing an area containing machine configuration information.
The address of this object is obtained by calling Kern::MachineConfig(). However, the Variant (either the ASSP layer or the Variant layer or both) is responsible for the content.
In the template port, the function is implemented in the Variant layer:
TPtr8 Template::MachineConfiguration() { return TPtr8((TUint8*)&Kern::MachineConfig(),sizeof(TActualMachineConfig),sizeof(TActualMachineConfig)); }
Here, the machine configuration information is represented by an object of type TTemplateMachineConfig, which derives from TMachineConfig. In effect, TMachineConfig represents information that is common to all, while the Variant can extend this to contain whatever information is appropriate.
Note that TActualMachineConfig is a typedef for TTemplateMachineConfig.
If a startup reason is available from hardware or a preserved RAM location, it should be returned by the function. The default is to return EStartupColdReset.
On the template port, this is implemented in the ASSP layer:
EXPORT_C TMachineStartupType TemplateAssp::StartupReason() { __KTRACE_OPT(KBOOT,Kern::Printf("TemplateAssp::StartupReason")); #ifdef _DEBUG // REMOVE THIS TUint s = Kern::SuperPage().iHwStartupReason; __KTRACE_OPT(KBOOT,Kern::Printf("CPU page value %08x", s)); #endif // REMOVE THIS // // TO DO: (mandatory) // // Map the startup reason read from the Super Page to one of TMachineStartupType enumerated values // and return this // return EStartupCold; // EXAMPLE ONLY }
Copyright ©2010 Nokia Corporation and/or its subsidiary(-ies).
All rights
reserved. Unless otherwise stated, these materials are provided under the terms of the Eclipse Public License
v1.0.