The big fact is that different types of drivers need different interfaces. A interface could be implemented in many different ways but the easiest is to use actual call instructions and a __cdecl calling convention. For an example you might have:
PCI Driver Interface
Open( ... )
Close( ... )
...
PCIE Driver Interface
Open( ... )
Close( ... )
...
Graphics Driver Interface
SetPixel( ... )
GetPixel( ... )
BltBlt( ... )
...
Block Device Interface
Read( ... )
Write( ... )
GetBlockSize( ... )
GetGeometryInformation( ... )
PCI Controller Driver Interface
Open( ... )
Close( ... )
... < which very well might actually load the driver for the devices attached to it's controller and buss(es) of the controller> ..
I myself always liked to use a motherboard module. I have to call it a module because there were a lot of people that raised a holy war when I called it a motherboard driver, but I always felt that some motherboard offer specialized features. Take for instance if your OS is compiled and ran on a platform besides the x86 for instance ARM. On ARM the board is generally very specific and at times there exists no way to enumerate the devices attached.. only a driver for the board would know what is there or not. Then you also have the ability to write a general x86 motherboard driver.. oops... module so it can handle all general cases such as built-in floppy controllers at backwards-compatibility ports..ect..ect.
I like the idea of building flexible stacks of driver, but I think there is a cost of time and space. You have a more complicated design, more instructions to execute, and increased storage requirements.
Code: Select all
[motherboard module]
|
+------------[PCI Controller Driver]
|
+---------------------[PCI Driver Interface][Graphics Driver Interface]
Where some drivers actually have more than one interface. Allowing them to export functions specific for the PCI implementation and other specific to the driver they manage such as a graphics cards in which the driver would allow control of through a graphics driver interface.
The actual implementation could be done like this:
Kernel Module Header Included By Module During Compile-Time
Code: Select all
typedef unsigned long uint32_t;
typedef unsigned short uint16_t;
typedef unsigned char uint8_t;
typedef struct
{
uint16_t id;
void *iface;
} KINT_ENTRY;
typedef struct
{
uint32_t kiv; /* kernel interface version */
uint16_t ecnt;
KINT_ENTRY *e;
} KINT_HDR;
typedef struct
{
void (*Startup)(void);
void (*Shutdown)(void);
} KINT_DRV_MOTHERBOARD;
#define KINT_DRVID_PROCESSOR 1
#define KINT_DRVID_MOTHERBOARD 2
#define KINT_DRVID_PCICONTROLLER 3
#define KINT_DRVID_USBCONTROLLER 4
#define KINT_DRVID_PCIDRIVER 5
#define KINT_DRVID_GRAPHICS 6
#define KINT_DRVID_BLOCK 7
#define KINT_VERSION 0xa3448654 /* 0xa3 major, 0x44 minor, and maybe 0x8654 is the revision.. doesnt matter */
Module Implementation Example
Code: Select all
void gmb_Startup(void);
void gmb_Shutdown(void);
static KINT_DRV_MOTHERBOARD mod_ki_motherboard = {
&gmb_Startup,
&gmb_Shutdown,
};
static KINT_ENTRY mod_kie[] = {
{
KINT_DRVID_MOTHERBOARD,
&mod_ki_motherboard
}
};
KINT_HDR mod_ki_hdr = {
KINT_VERSION,
sizeof(mod_kie) / sizeof(KINT_ENTRY),
&mod_kie[0]
};
void gmb_Startup(void)
{
}
void gmb_Shutdown(void)
{
}
/* some test code to demonstrate the 'ecnt' field works */
int _tmain(int argc, _TCHAR* argv[])
{
printf("number of interface:%u\n", mod_ki_hdr.ecnt);
return 0;
}
A graphics driver's interface structure might look like this.. using the same variable names as above.
Code: Select all
static KINT_DRV_PCIDRIVER mod_ki_pcidriver = {
....
};
static KINT_DRV_GRAPHICS mod_ki_graphics = {
...
};
static KINT_ENTRY mod_kie[] = {
{
KINT_DRVID_PCIDRIVER,
&mod_ki_pcidriver
},
{
KINT_DRVID_GRAPHICS,
&mod_ki_graphics
}
};
Monolithic/Module/Compile-In
The implementation above supports monolithic modules or modules that are compiled in. The only difference is the way the KINT_HDR is aquired. If they are compiled in you need the kernel to either search for modules in it's image OR generate a array during compile-time that the kernel uses to register each driver's KINT_HDR. If you later move the module to loadable you can simply load the image and find a symbol for the KINT_HDR and the same concept applies.