It is well-understood that reusing the same code across multiple products is ideal. However, how can it be done when each product has different features, constants, and chips? Here are a few techniques I used to have one device driver support several HP LaserJet printers with different features, capabilities, and chips.
Years ago when I started with the device driver, there was just one product it had to support, so the driver was written specifically for that product. When I ported the driver to the next product, I used #define and #ifdef statements to manage the product-specific code, like this:
#ifdef PRODUCT_SKYWALKER # define CHIP_BASE_ADDRESS 0x80004000 #else // for product Vader # define CHIP_BASE_ADDRESS 0x6002C000 #endif ... #ifdef PRODUCT_SKYWALKER *regConfig |= HQ_MODE_1; #else *regConfig |= HQ_MODE_2; #endif *regConfig |= MEMORY_LOCAL;
But, when I added support for a third product, the code quickly became messy:
#ifdef PRODUCT_SKYWALKER # define CHIP_BASE_ADDRESS 0x80004000 #else // for product Vader or Obiwan # define CHIP_BASE_ADDRESS 0x6002C000 #endif ... #ifdef PRODUCT_SKYWALKER *regMode |= HQ_MODE_1; #else // for Vader or Obiwan *regMode |= HQ_MODE_2; #endif #ifndef PRODUCT_OBIWAN // Skywalker and Vader only *regConfig |= MEMORY_LOCAL; #endif
I analyzed the mess and determined that I was switching on product-specific features and chip-specific features. Product-specific features were unique to that product. Chip-specific features were unique to that chip, no matter which product used it. So, I created two types of feature switches, product-features and chip features. Based on the product, I turn on appropriate product-specific switches, including which chip that product uses. Then based on the chip, I turn on other appropriate switches. So, I modified the code this way.
// Use product switches to specify chip and enable features #if defined PRODUCT_SKYWALKER # define FEATURE_MEMORY_LOCAL # define CHIP_CHOCOLATE #elif defined PRODUCT_VADER # define FEATURE_MEMORY_LOCAL # define CHIP_POTATO #elif defined PRODUCT_OBIWAN # define CHIP_POTATO #else # error Unrecognized product #endif // Use chip switches to enable chip features #if defined CHIP_CHOCOLATE # define BASE_ADDRESS 0x80004000 # define HQ_MODE HQ_MODE_1 #elif defined CHIP_POTATO # define BASE_ADDRESS 0x6002C000 # define HQ_MODE HQ_MODE_2 #else # error Unrecognized chip #endif *regMode |= HQ_MODE; #ifdef FEATURE_MEMORY_LOCAL *regConfig |= MEMORY_LOCAL; #endif
By breaking down product differences and chip differences into features, this code uses the product name switches only once at the beginning of the code to specify feature switches. Then feature switches are used throughout the code as necessary. When porting to a new product, a new product switch can be added to the list of products at the beginning to turn on its appropriate features and the code is ready to go.
This is a very simple example but, as the number of supported products and differences increases, the number of switches also increases. This style helps keep the device driver code manageable and usable across several chips and products.
Until the next switch…