We recently ported a device firmware from a platform of very limited hardware resources (32K RAM, 4M NOR) to a more capable one (256M RAM, 256M NAND). The device supports dynamic plugging of different modules with analogy/digital I/O channels on them. It is the time when a module is plugged that device firmware receives an interrupt, and access the module firmware to finish configuration and perform according state machine change.
Due to the limited resources on the old platform, we compile these module firmware (let’s say we support 60+ different modules) separately from the main device firmware. When a module is plugged, we search a linked list on flash to locate the module firmware, and dynamically load it to RAM. Since hardware resources are no longer a worry now, one change we have made to improve performance is to build all module firmware into main device firmware at compile time, and load it to RAM once at system startup. And instead of traversing through the linked list in flash, we need to find the specific function entry point in RAM to call for the plugged module.
We reuse the same C code for each module. The entry point of each module is located in its own source file, e.g., cRIO_9215.c. And some “ME-TOO” modules work in a similar way so they share the same firmware code.
void cRIO_921x_main(tCartridgeCmd cmd, u8* memory, void* param, tStatus* status)
{
..
}
void cRIO_9263_main(tCartridgeCmd cmd, u8* memory, void* param, tStatus* status)
{
..
}
One developer in my team was assigned this task. He came up with something like below, quite a natural one. I bet it’s the first thought for most of us.
It works. All I need to do is to go through this 200+ lines of code, and check if there is any module missing or if one is using a wrong handler. Meanwhile, I need to keep my eyes wide open to make sure it is at the right place to “break”.
Frankly I can accept this, if this is a once-for-all change and we will never add new modules in future. Unfortunately, that’s not the case. New module requests keep coming from marketing at almost every release.
So I’d like to come up with a better solution which is more maintainable, and less code to touch when there comes a new module support.
So we created a new solution where a map is created from the module list to their sole handler. A preliminary prototype is given below. A string
of 128 characters is used as the key to this map, so it can contain around 20+ modules for a single handler. That’s pretty safe in the near future. If it’s not, then compiler will be happy to throw an error anyway. To add a new module, just add to an appropriate key in the table if it’s “ME-TOO”. Otherwise, append a new line to the table. It’s less likely to make an error, and easier for future reviewers to finish their jobs.
#include <stdio.h>
#include <string.h>
void cRIO9201_main(void) { printf("cRIO9201_main\n"); }
void cRIO9203_main(void) { printf("cRIO9203_main\n"); }
void cRIO9205_main(void) { printf("cRIO9205_main\n"); }
void cRIOParallelDI_main(void) { printf("cRIOParallelDI_main\n"; }
typedef void(tCartridgeARMHandler)(void);
typedef char tModuleList[128];
typedef struct
{
tModuleList _modules;
tCartridgeARMHandler *_handler;
}tArmHandlerMap;
static tArmHandlerMap _handlerMap[] =
{
{"9201, 9221", cRIO9201_main},
{"9203", cRIO9203_main},
{"9205, 9206", cRIO9205_main},
{"9411, 9421, 9422, 9423, 9435, 9436, 9437", cRIOParallelDI_main}
};
tCartridgeARMHandler* getArmHandler(int handlerID)
{
unsigned int i;
char id[10];
sprintf(id, "%d", handlerID);
unsigned int count = sizeof(_handlerMap) / sizeof(_handlerMap[0]);
for (i = 0; i < count; ++i)
{
if (strstr(_handlerMap[i]._modules, id))
return _handlerMap[i]._handler;
}
return NULL;
}
int main()
{
getArmHandler(9201)();
getArmHandler(9203)();
getArmHandler(9435)();
getchar();
return 0;
}