BSP Common Test Development
Purpose
- Establish re-usable tests to verify requirements satisfied on any given platform, regardless of architecture or platform specifics.
- These tests are stored in the dev-kit (common) and brought into platform builds as externals.
- Provide platform specific support for the tests at the platform level, keeping platform and architecture details encapsulated at this level.
Files Used in Tests
Re-usable Tests for Specific Requirements
common:
- Files at this level should have no architecture or platform specific information, ideally. Use PAL interceptor calls for testing.
- Including test-platform-data.h and test-platform-functions.h (maintained in platforms rather than common) allows for expanded interaction with the PAL (still abstracted).
- tcp#.htm (test cases)
- Test cases related to specified requirements
- tpp#.cpp (test procedure)
- Implementation of test cases, testing requirements conformity.
- Platform and architecture agnostic -- have no details of registers, addresses, bits, or functions
PAL interceptor for testing: (common)
- Files at this level should have no architecture or platform specific information, ideally.
- ipal.h
- This file is used to specify which functions will be called when the kernel calls into the PAL, and when the PAL calls back into the kernel. This allows tests to set and read data as needed to gather test results.
- tpp#-specializeIPal.h
- tpp#_specializeIPal.cpp
- Adapter layer: delegates abstract test functions into platform function calls
- Implements PAL interceptor functions needed for testing.
Platform/BSP level, Support for ALL Re-usable Tests to be run on a platform
- test-platform-data.h
- The defines, enums, structs, or other content should be limited to only what is needed for test support (e.g. NUMBER_OF_CORES), without architecture or platform specifics.
- test-platform-functions.h
- enum of mapped platform interrupts
- Use DEFINEs to map generic interrupt names (e.g. WINDOW_TIMER_INTERRUPT) to specific interrupts for the platform architecture.
- Declare setter function(s) to prepare for tests and set specified events for tests accessible by PAL interceptor.
- Declare getter functions to manage comparisons of mapped values and functions accessible by PAL interceptor.
- test-platform-functions.cpp (This file supports ALL test procedure needs rather than being test specific.)
- Platform and architecture specific information (bit masks, register addresses, memory locations, ...)
- Defines and enums needed at the lowest level for addresses, bit masks, etc.
- Functions called by PAL interceptor layer to set/get values for tests, or to trigger events.
- Translates generic test data and function calls to platform/architecture specific data and functions to perform actions.
- Translates platform specific results to generic data formats for tests to process.
Examples
These examples show ways to keep the common (dev-kit) tests platform agnostic, putting architecture and platform specifics in test-platform-functions for PAL testing.
Getting interrupt status
For example, a test commands "get window timer interrupt status" which is translated to extracting the specific interrupt data and returning a status of "isEnabled=true, isPending=false". The test determines if that is correct or not.
This particular implementation is for an ARM platform using GIC specific information. The mapping of interrupts in test-platform-functions.h and the implementation of getInterruptStatus would be different for x86 or PowerPC platforms. This allows the common tests to run on any platform without needing to be changed.
In test-platform-functions.h, an abstracted interrupt status data structure and getInterruptStatus function interface to be used by specializeIPal:
#define WINDOW_TIMER_INTERRUPT INT_NON_SECURE_PHYSICAL_TIMER
#define THREAD_TIMER_INTERRUPT INT_VIRTUAL_TIMER
struct InterruptStatus
{
bool isEnabled; // masked
bool isPending;
bool isUsed;
};
struct InterruptStatus getInterruptStatus(uint32_t interrupt);
In test-platform-functions.cpp:
struct InterruptStatus getInterruptStatus(uint32_t interrupt)
{
struct InterruptStatus status;
uint32_t address_offset, bit_position;
uintptr_t vaddr;
// lookup to map test interrupt to correct register
switch(interrupt)
{
case WINDOW_TIMER_INTERRUPT:
case THREAD_TIMER_INTERRUPT:
vaddr = GIC_REDIST_vaddr;
break;
default:
vaddr = GIC_REDIST_vaddr;
break;
}
//Get the number of the register where the interrupt is route to
address_offset = (interrupt >> 5);
address_offset = address_offset*4; //The address of each register is 4bytes step
//Get the bit on the register that the interrupt is route to.
bit_position = interrupt & 0x001F;
// GIC_REDIST_vaddr could be DIST version, depending on generic interrupt identified
uint32_t enableInterruptRegisterValue = (uint32_t)*(uint32_t*)(vaddr + GICD_ISENABLE + address_offset);
uint32_t pendingInterruptRegisterValue = (uint32_t)*(uint32_t*)(vaddr + GICD_ISPENDIN + address_offset);
if (pendingInterruptRegisterValue & (1 << bit_position)) //&& mask_bit;
{
status.isPending = true;
} else {
status.isPending = false;
}
if (enableInterruptRegisterValue & (1 << bit_position)) //&& mask_bit;
{
status.isEnabled = true;
} else {
status.isEnabled = false;
}
return status;
}
In the test code (tpp or specializeIPal):
status_window_timer_interrupt = getInterruptStatus(WINDOW_TIMER_INTERRUPT);
status_thread_timer_interrupt = getInterruptStatus(THREAD_TIMER_INTERRUPT);
if ((status_window_timer_interrupt.isEnabled == false) && (status_thread_timer_interrupt.isEnabled == false))
{
testPtr->maskingCorrectPerCore[currentCore] = true;
} else {
testPtr->maskingCorrectPerCore[currentCore] = false;
}
Triggering interrupts to test PALInterruptHandler
For example, a test needs to trigger platform interrupts to test the PALInterruptHandler function while keeping platform specifics in the PAL test framework to allow the common test to be used on multiple architectures/platforms.
In the test procedure main: (e.g. tpp019a.cpp)
// Run the test tpp019_100();
In the test procedure: (e.g. tpp019a.cpp) The test step (e.g. tpp019_100)
void tpp019_100(void){
waitUntilNextPeriod();
// Call the low-level test funtion in the specialized iPAL using supervisor mode
uintData_t status0 = supervisorModeCallback(testStructPtr->testCase019_100, 0x100);
if (status0)
{
waitUntilNextPeriod();
... (check results) ...
}
else
{
pTestIO->out << "supervisorModeCallback failed" << flush;
}
}
In the test code (specializeIPal):
A pointer to the function is saved in the test structure to allow access from the test procedure. This is done in testPALcoldstart.
The test struct "testStructPtr" (in specializedIPal.h) has a member "uintData_t (CDECL *testCase019_100)(uint32_t);" to store the pointer.
extern "C" void DEOSPALPPI testPALcoldstart(void * platformSpecificInfoPtr)
{
...
testStructPtr->testCase019_100 = testCase019_100;
...
}
uint32_t testCase019_100(uint32_t unused)
{
CriticalLevelType previousCriticalLevel = enterCritical(/*CriticalLevelType*/ masterCritical);
...
disableGlobalInterrupts();
// Enable and set to pending all interrupts not set to skip in test-platform-functions
Loop...
triggerInterrupt(interruptNumber);
...
exitCritical(previousCriticalLevel);
return unused;
}
In test-platform-functions.cpp:
void triggerInterrupt(uint32_t interrupt)
{
uint32_t address_offset, bit_position;
uintptr_t vaddr;
// lookup to map test interrupt to correct register
if (interrupt < 32)
{
vaddr = GIC_REDIST_vaddr;
}
else
{
vaddr = GIC_DIST_vaddr;
}
//Get the number of the register where the interrupt is route to
address_offset = (interrupt >> 5);
address_offset = address_offset*4; //The address of each register is 4bytes step
//Get the bit on the register that the interrupt is route to.
bit_position = interrupt & 0x001F;
uint32_t* setEnableInterruptRegister = (uint32_t*)(vaddr + GICD_ISENABLE + address_offset);
// Unmask the interrupt by enabling it
*setEnableInterruptRegister = (1 << bit_position);
uint32_t* setPendingInterruptRegister = (uint32_t*)(vaddr + GICD_ISPENDIN + address_offset);
// Trigger the interrupt by setting it to pending
*setPendingInterruptRegister = (1 << bit_position);
return;
}