Asic Class Tutorial

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.

Asic::Init1() implementation

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:

    • purging (invalidating) a cache

    • cleaning a cache

    • flushing (i.e. cleaning and invalidating) a cache.

    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.

Asic::Init3() implementation

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

Asic::DebugOutput() implementation

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.

Asic::Idle() implementation

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

Asic::MsTickPeriod() implementation

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.

Asic::SystemTimeInSecondsFrom2000() implementation

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.

Asic::SetSystemTimeInSecondsFrom2000() implementation

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.

Asic::NanoWaitCalibration() implementation

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
    }

Asic::VariantHal() implementation

You might find it useful to review User-Side Hardware Abstraction Technology first.

This is the HAL handler for the HAL group THalFunctionGroup::EHalGroupVariant.

Asic::MachineConfiguration() implementation

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.

Asic::StartupReason() implementation

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
    }