Driver Code Structure
- Driver Code Structure
- Driver interface exported to Hypervisor
- Description of the Driver interface functions
- Function
DRV_InitInstance - Function
DRV_ExitInstance - Function
DRV_GetVersion - Function
DRV_SetDeviceName - Function
DRV_Start - Function
DRV_setup - Function
DRV_End - Function
DRV_Connect - Function
DRV_Disconnect - Function
DRV_MassErase - Function
DRV_Erase - Function
DRV_BlankCheck - Function
DRV_Program - Function
DRV_Verify - Function
DRV_Command
- Driver Life-Cycle
- The G_DATA macro
- Custom Settings
- Driver interface exported to Hypervisor
Driver interface exported to Hypervisor
Every driver must export the following fixed set of function that will be called from the Hypervisor:
retVal_t DRV_InitInstance(ctx_p ctx);
retVal_t DRV_ExitInstance(ctx_p ctx);
uint32_t DRV_GetVersion(void);
retVal_t DRV_SetDeviceName(ctx_p ctx);
retVal_t DRV_Start(cmd_p cmd);
retVal_t DRV_setup(cmd_p cmd);
retVal_t DRV_End(ctx_p ctx);
retVal_t DRV_Connect(cmd_p cmd);
retVal_t DRV_Disconnect(ctx_p ctx);
retVal_t DRV_MassErase(cmd_p cmd);
retVal_t DRV_Erase(cmd_p cmd);
retVal_t DRV_BlankCheck(cmd_p cmd);
retVal_t DRV_Program(cmd_p cmd);
retVal_t DRV_Verify(cmd_p cmd);
retVal_t DRV_Read(cmd_p cmd);
retVal_t DRV_Dump(cmd_p cmd);
retVal_t DRV_Command(cmd_p cmd);
retVal_t DRV_Help(cmd_p cmd);
retVal_t DRV_ErrDecode(retVal_t errCode, char * const p, int maxlen);
Description of the Driver interface functions
This commands can be divided in two set, the first is the function that are called directly from the Hypervisor to manage the lifecycle of the driver. The second set is function called as a result of a command comming from the user/project.
The first list contain the functions:
- DRV_InitInstance
- DRV_ExitInstance
- DRV_SetDeviceName
- DRV_End
All this function as only one parameter that is the execution context (ctx). This parameter is required by all API call to the Hypervisor and it's the only way for the driver to store local information.
All the other function as a parameter called cmd that contains the full command line. For this functions the execution contex is available as a field inside the cmd parameter. The available fields inside the cmd param are:
char cmdLine[MAX_CMD_LINE_LEN+1]; ///< Complete command line
int inScript; ///< Command are form script
int cmdId; ///< Interna id of the command
char cmd[MAX_CMD_PARAM_LEN+1]; ///< First token of the command line (that is the command)
int argc; ///< Argument Count (like C main function param argc)
char argv[MAX_CMD_PARAM][MAX_CMD_PARAM_LEN+1]; ///< Argument value (like C main function param argv)
ctx_p ctx; ///< Execution context
The field ctx must be passed as argument to all the Hypervisor API function.
Function DRV_InitInstance
This function is the first called on the driver, it's called for every execution to let the driver initialize itself. typically the body of the function is:
retVal_t DRV_InitInstance(ctx_p ctx)
{
CHECK_VER_MACRO; // Must be present here to check for compatibilty between the driver and the Hypervisor
FN_allocateDriverData(ctx, sizeof(drvData_t)); // Allocate driver local data
RET_OK;
}
In this function the driver can do other required initialization that are not related to a specific device. Allocation of memory to store data for the driver must be done only in this function as shown.
Function DRV_ExitInstance
This function is always called at the end of a programming session to let the driver do every cleanup activity. Deallocation of data allocate on the DRV_INitInstance must be done here. Typically body of this function is:
retVal_t DRV_ExitInstance(ctx_p ctx)
{
FN_freeDriverData(ctx);
RET_OK;
}
Function DRV_GetVersion
This function must return the version of the driver for logging purpose. For convention the version is to be considered a 32bit hexadeciman number, the 3th byte is the major version and the 4th byte is the minor version.
Function DRV_SetDeviceName
This function is called rigth after the InitInstance to let the driver know what device are goingo to program. Tipically implementation is:
retVal_t DRV_SetDeviceName(ctx_p ctx)
{
_fnRet(FN_setDriverCustomSettings(ctx, drvSetupList, &G_DATA->settings));
RET_OK;
}
The call to FN_setDriverCustomSettings is put here to inform the Hypervisor of all custom settings required by the driver that can be found inside the project. The argument drvSetupList is a structure that define all this parameter.
Function DRV_Start
Called by the Hypervisor to let the driver intialize itself to program the specific device. The basic implementation is:
retVal_t DRV_Start(cmd_p cmd)
{
const ctx_p ctx = cmd->ctx;
_fn(FN_POD_loadFpga(ctx, FPGA_PROT_JTAG, 2, "jtag"), ERR_CODE(ERR_MODULE_BASE, ERR_DRV_START));
FN_POD_fpgaProtocolReset(ctx);
FN_POD_fpgaLogVersion(ctx);
RET_OK;
}
The most importat call here is the FN_POD_loadFpga. This is required to configure the FPGA and load the bitstream implementing the required protocol.
After that the FN_POD_fpgaProtocolReset is called to reset the FPGA to it's initial state.
The FN_POD_fpgaLogVersion is called to write into the log file the version of the FPGA configuration used.
Moreover the driver can do every other initialization required.
Function DRV_setup
This function is called for every settings inside the project that the Hyoervisor is not able to parse and decode. The standard settings are parsed automatically, plus the hypervisor is able to parse an decode also the custom driver setting if configured by the call to the FN_setDriverCustomSettings function, so this function is normaly used to parse setting that have non-standard format.
The most common implementation is to just return an error:
retVal_t DRV_setup(cmd_p cmd)
{
const ctx_p ctx = cmd->ctx;
_errnoCheck(snprintf(cmd->response, CMD_INT_BUFFER_SIZE, "Can't decode %s", cmd->cmdLine));
cmd->response[CMD_INT_BUFFER_SIZE-1] = '\0';
FN_RETURN_EX(ERR_CODE(ERR_MODULE_BASE, ERR_DRV_SETDEVSET), cmd->response);
return(NO_ERR);
}
Function DRV_End
This function is called by the Hypervisor to let the driver close the connection with the device. This function is always called also in case of error. The basic implementation is:
retVal_t DRV_End(ctx_p ctx)
{
UNUSED(ctx);
FN_POD_syncMode(ctx, FALSE);
_fn(FN_powerOffTarget(ctx), ERR_CODE(ERR_MODULE_BASE, ERR_DRV_CONNECT));
RET_OK;
}
The call to FN_powerOffTarget to disconnect and powerof the target device is put here since the DRV_Disconnect function is not called in case of an error.
Function DRV_Connect
Corresponding to the DRVCONNECT command from the project. The main function is to power-on the target an do the connection to it.
Function DRV_Disconnect
Corresponding to the DRVDISCONNECT command from the project. The main function is to close the connection and power-off the target.
Function DRV_MassErase
Corresponding to the DRVMASSERASE command from the project.
Function DRV_Erase
Corresponding to the DRVERASE command from the project.
Function DRV_BlankCheck
Corresponding to the DRVBLANKCHECK command from the project.
Function DRV_Program
Corresponding to the DRVPROGRAM command from the project. The common implementation is:
retVal_t DRV_Program(cmd_p cmd)
{
const ctx_p ctx = cmd->ctx;
AlgoProgFunctMapTable_t functTable[] = {{MEM_CODEFLASH, ALGO_flashProgram_sel},
{MEM_DATAFLASH, ALGO_flashProgram_sel},
{' ', NULL}};
_fn(DRV_ProgramImpl(cmd, GLOBAL_BUFFER_SIZE, functTable), ERR_CODE(ERR_MODULE_BASE, ERR_DRV_PROGRAM));
RET_OK;
}
Here the table functTable contain the association from the memory type available for this device and the driver function that can program that specific memory type.
DRV_ProgramImpl function analyze all the memory region available for this device in combination with the cmd, configure the system with the correct range and then call the correct function.
Function DRV_Verify
Corresponding to the DRVVERIFY command from the project. Typical implementation is:
retVal_t DRV_Verify(cmd_p cmd)
{
const ctx_p ctx = cmd->ctx;
char memType = cmd->argv[1][0];
AlgoProgFunctMapTable_t functTable[] = {{MEM_CODEFLASH, ALGO_flashVerifyReadout_sel},
{MEM_DATAFLASH, ALGO_flashVerifyReadout_sel},
{' ', NULL}};
_fn(DRV_VerifyImpl(cmd, GLOBAL_BUFFER_SIZE, functTable, functTable), ERR_CODE(ERR_MODULE_BASE, ERR_DRV_VERIFY));
RET_OK;
}
As for the DRV_Program function here a utility function from the Hypervisor is used to do the commnon task and call the correct function to execute the verify.
Function DRV_Command
Corresponding to the DRVCOMMAND command from the project. This function handle all the custom command defined for this device. An example implementation is:
retVal_t DRV_Command(cmd_p cmd)
{
const ctx_p ctx = cmd->ctx;
AlgoCmdFunctMapTable_t functTable[] = {{"SECTORERASE", DRV_CMD_sectorErase},
{"READ_DCF", DRV_CMD_readDcf},
{"READ_LC", DRV_CMD_readLc},
{"SET_LC", DRV_CMD_setLc},
{"JTAG_PWD", DRV_CMD_setJtagPassword},
{"SET_DCF", DRV_CMD_setDcf},
{"SET_DEFAULT_DCF", DRV_CMD_setDefaultDcf},
{"PROGRAM_DCF", DRV_CMD_programDcf},
{"PROGRAM_PWD", DRV_CMD_programPassword},
{"", NULL}};
_fn(DRV_CommandImpl(cmd, functTable), ERR_CODE(ERR_MODULE_BASE, ERR_DRV_COMMAND));
RET_OK;
}
Driver Life-Cycle
The G_DATA macro
The G_DATA macro is used everywhere on the driver code, it's a standard way to handle local storage for the driver since the driver code cannot ever declare any global variable.
An example implementation taken from TRICORE:
typedef struct
{
bool p5v;
bool eraseUCB;
} customSettings_t;
typedef struct
{
customSettings_t settings;
uint32_t cpu0BaseAddr;
uint32_t lastAddressSet;
uint32_t uniqueId;
uint32_t dspr0Size;
char tricoreType;
DapCmd selectedCmd;
uint8_t* glb_buff;
bool buffReady;
bool initPsprAndDspr;
bool useJtag;
uint8_t numberOfTcCores;
const char* uk_FileName;
uint32_t uk_dataAddr;
uint32_t uk_stackAddr;
uint32_t uk_codeAddr;
uint32_t uk_codeAltAddr;
uint32_t uk_codeStartAddr;
uint32_t uk_algo_buffIdx;
} drvData_t;
typedef drvData_t* drvData_p;
#define G_DATA DRV_DATA(drvData_p)
The struct type drvData_t define all the data required by the driver, this data is allocated by the API function FN_allocateDriverData and accessible using the system macro DRV_DATA.
The G_DATA macro is defined here as a shortcut to make all the field directly available.
The fields of this struct is defined by the driver developer in accordance with his own requirement.
The first field is normally defined to save the custom driver setting comming from the project. In this case there are two custom setting saved on the fields p5v and eraseUCB.
Custom Settings
The custom settings required by the driver (also defined on the DB) normally are parsed and decoded by the Hypervisor. The driver develop just prepare a struct to describe this settings and pass that structure to the Hypervisor using the function FN_setDriverCustomSettings as seen above.
The structure to declare are like this (also from TRICORE):
fn_setup_list_t drvSetupList[] =
{
// Name type lookup var offset min max
{"PROG_5V", SETUP_TYPE_BOOL, NULL, offsetof(customSettings_t, p5v), 0, 0},
{"REPLACE_UCB", SETUP_TYPE_BOOL, NULL, offsetof(customSettings_t, eraseUCB), 0, 0},
{NULL, 0, NULL, 0, 0, 0},
};
You can see the two field declare above on the structure customSettings_t and also the NAME of this settings as defined on the DB.