Driver Code Structure

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

sequenceDiagram Participant H as Hypervisor create Participant D as Driver H->>D: (Load Driver) H->>D: DRV_InitInstance activate D H->>D: DRV_GetVersion H->>D: DRV_SetDeviceName H->>D: DRV_Start activate D H->>D: DRV_Connect create Participant T as Target D->>T: (Power on) D->>T: (Connect) loop on Memory Type H->>D: DRV_MassErase D->>T: (MassErase) H->>D: DRV_BlankCheck D->>T: (Blank Check) H->>D: DRV_Program D->>T: (Program) H->>D: DRV_Verify D->>T: (Verify) END H->>D: DRV_Disconnect destroy T D->>T: (Power off) H->>D: DRV_End deactivate D H->>D: DRV_ExitInstance deactivate D destroy D D->>H: (Unload Driver)

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.