User:Kmcguire/Quick And Dirty Device Management

From OSDev Wiki
Jump to navigation Jump to search

The Quick And Dirt Device Management Scheme

Target Audience

Overview

I will throw magical energy balls, containing all the information you need to know, through your video screen and into your head. A word of warning, however. If your head is not turned correctly the energy balls may cause head injuries, head aches, and slight brain damage. Please take caution, and know that we are not responsible for any of the aforementioned symptoms. Also remember that none of the previous sentences should be taken seriously, and that your belief in magic energy balls is completely up to you. Thank you for your time.

Terms

Here are some terms for clarification in this article that I will be using.

  • Connection - A connection is the physical or conceptual identification, for which a device or componet of the computer system is connected to another componet.
  • Major Identifier - A class of interfaces that contain similar minor interfaces.
  • Minor Identifier - A specifier for a interface that also resides in a class represented by the Major Identifier.
  • Device - A single function board that exports interfaces that are related by their major identifier.
  • Motherboard Helper - Similar to a driver except the primary purpose is to aid the kernel device subsystem in loading the devices present on the motherboard.
  • Multifunction Board - A multifunction board is one in which contains the potential of having multiple distinct devices. Hence it is referenced as a board instead of a device.
  • Driver Instance - A driver which has employed to control a physical device residing on a bus. There may be multiple instances of the same driver.
  • Module - A binary file containing executable code and required data which can contain one or more driver headers.
  • Device Subsystem - A kernel subsystem responsible for loading the motherboard helper and providing the framework for device management.

What To Load?

The kernel has no idea what to load instead the motherboard helper, a external subsystem of the kernel device subsystem, as a run-time or compile-time module will make assumptions and decisions on what to load for the system. This motherboard helper is of a generic or specialized type. The specialized motherboard helpers are used in circumstances where a specialized computer system may be used such as a proprietary board using a MIPS processor, or even in trival situations the future may hold where certain motherboard manufactures support specialized features that are provided by the motherboard.

Once the motherboard helper initializes and in a general case it will initialize the common devices such as the 8253 Programmable Interrupt Timer, 8259 Programmable Interrupt Controller, and even make decisions to load more advanced chips like the APIC which replaces the 8259 and 8253 chips.

What Do We Need?

What we need to know to load a driver is only relevant to a bus driver or motherboard helper and the associated drivers for the motherboard helper or bus driver. What I mean is a driver or the PCI bus driver for a PCI bus needs to know nothing about what a driver for a ISA bus needs to know from drivers for devices on a ISA bus. Instead the PCI bus driver only needs to understand what it wants from PCI device drivers and nothing else. In special circumstances a driver or motherboard helper may require knowledge about what a PCI bus driver and a ISA bus driver need from their drivers, however this is only a what-if condition clause and should be avoided if possible. Just so you can make a mental note this information is references as connectionData in the remaining of this article, but before we actually get to explaining this connectionData we need to talk about a little before it.

Driver Major Parts

The major parts of a driver are: one or more interfaces, driver header, bus initialization handler, and the actual driving code for the device. The driver header is what ties the other major parts together. The bus initialization handler will be explained a little later, but right now we will take a exploration of interfaces which are the first major part of a driver. A interface is a group of functions that are manifested by a standard which is referenced using two identifiers. The major and minor identifiers form a pair that references a standard, or a manifestation of the standard known here as a interface. This standard however has to only be published in the kernel headers, but it could be published by other means which are up to you're creative imagination. This standard is made in the following form inside the kernel header(s):

#define DEV_PICTURE			0x1
	#define DEV_PICTURE_PICTURE		0x1
	#define DEV_PICTURE_VGA			0x2
#define DEV_IO				0x2
	#define DEV_IO_CONTROL			0x1
#define DEV_CLOCK			0x4
	#define DEV_CLOCK_CLOCK			0x1
	#define DEV_CLOCK_TIMER			0x2
#define DEV_PIC				0x5
	#define DEV_PIC_PIC			0x1

Just for a example and not for usage in a actual production environment is the above interface major and minor values. This implementation is completely user controlled. Meaning someone out in No Man's Land could download you're kernel and decide he wants to include a new interface for a driver or motherboard helper to export. He could just select a free major and minor identifier which would be completely okay. The problem would arises if he later downloads a new version of you're kernel which ended up through official channels picking the major and minor identifiers he used. This would cause his kernel to malfunction because in the main stream kernel version the major and minor used the same major and minor he decided to use in a older main stream kernel version. This problem luckily would only affect those that decided to venture out alone in the world of standardized interfaces for drivers. The main point is that as long there is a official mediator of the major and minor identifiers there should never be any wide scale problems or at least not introduced by the kernel development team. In the worst case scenario the drivers would have to be simply recompiled once the major and minor values were changed and hopefully introduced into the main stream through the official communication channels.

The Point Of Interfaces

Interfaces allow us to realize our goal of allowing the devices to become managed by dividing them into major and minor categories. For example a VGA device needs to provide to same functionality as a SVGA device, as well as any other video output device needs to also provide the same. If not then a developer would be forced to write different code for each of these devices. So we provide the solution of exporting a list of functions that always: stay in the same order, have the same return values, and accept the same arguments. This creates a standard is which should be intended to remain for as long as possible. This standard is manifested in the interface in other words. Now that a interface is defined the developers can feel comfortable that their code will work properly on a VGA, SVGA, or any other video output device for many years. At the same time the SVGA device may have extra functionality that is not present in the VGA device. To include this extra functionality we build another interface and manifest this standard onto it. This also allows developers to prove if the device supports this extra functionality only by looking for the interface instead of checking for each single part of extra functionality. All interface functions should try to provide a error mechanism, if needed, for proper handling by the calling software if it is unsupported. This could be a case in some circumstances where a functionality was not present, but is not critical to the software which would allow it to gracefully continue. A fault tolerant method is to have documentation for the interfaces that specify their behavior which allows developers to plan for the what-ifs. In some cases one type of device may share a common functionality with another different type of device. To make development easier it could be more rational to split the common functionality into it's own interface that is shared among the drivers.

The Interface Look And Feel

Lets use a example driver for a generic VGA driver.

struct tBitmapInfo{
	unsigned int 	address;
	unsigned short 	width;
	unsigned short	height;
	unsigned int	format;
};
typedef unsigned int POS;
int BltBlt(struct tBitmapInfo *info, POS fromX, POS fromY, POS fromWidth, POS fromHeight, POS toX, POS toY, POS toWidth, POS toHeight);

Here we depict a function that copies video data from somewhere in memory to the actual video card allowing a specification of the source format, source offset, source dimensions, and the destination offset. This would be a generic function that should be supported by all video devices.

int Clear(POS x, POS y, POS width, POS height);

Might we want to clear the screen we should have a function to do this which should be supported by all video devices. I want to let you know I am not trying to tell you how to write a VGA driver but rather using a simple one as a example. So with that said these two functions should be basic enough to actually create a interface.

struct tOurPictureInterface{
	int (*BltBlt)(struct tBitmapInfo *info, POS fromX, POS fromY, POS fromWidth, POS fromHeight, POS toX, POS toY, POS toWidth, POS toHeight);
	int (*Clear)(POS x, POS y, POS width, POS height);	
};

Now we have created a standard that is manifested in a interface form. Once matured it can be published so that developers can reply on its functionality being present or if not the supporting code for the interface can emulate it if possible, or return a error value. We can also create other interfaces to represent functionality for a VGA, SVGA, or both and others.

Driver Header

The major parts of a driver are: one or more interfaces, driver header, bus initialization handler, and the actual driving code for the device. The interfaces were discussed a little above and will be futher discussed a little later along with the bus initialization handler. For the time being we will explore a little of the glue that I like to call the driver header.

The driver header glues together the major parts of a driver. We have just discussed interfaces but before we discuss the initialization handler and actual driver code we will cover the driver header in detail. A driver header needs to include some information such as: what type of connection in the system do we have, the interfaces we export, our connection dependent data, and our initialization routine for the connection. The type of connection could be explained by showing a list of connection types defined in our device subsystem's source.

#define DEV_CONNECTION_DEV		0x0
#define DEV_CONNECTION_MOTHERBOARD	0x1
#define DEV_CONNECTION_PCI		0x2

These are the connection types with DEV_CONNECTION_DEV being identification for the motherboard helper to the kernel device subsystem, DEV_CONNECTION_MOTHERBOARD being support for common board components such as the 8253 Programmable Interrupt Timer; 8259 Programmable Interrupt Controller; PCI, and DEV_CONNECTION_PCI identifying the connection between PCI drivers and the PCI bus driver. Of course more can be listed here, but we only need enough to help you gain a understanding of what is going on.

The connection dependent data and our initialization routine are actually combined in this device management scheme. Let me show you a three data formats with each one being for a connection in the order of: DEV_CONNECTION_DEV, DEV_CONNECTION_MOTHERBOARD, DEV_CONNECTION_PCI.

#define DEV_CONNECTION_DEV_VERSION 0x00000000
struct tdevBusData{
	u16		vendor;
	u16		board;
	i32 		(*Init)(void);
}; typedef struct tdevBusData DEV_CONNECTION_DATA; typedef struct tdevBusData* PDEV_CONNECTION_DATA;
#define DEV_CONNECTION_MOTHERBOARD_VERSION 0x00000000
struct tdevBusDataMobo{
	u32		type;
	i32 		(*Init)(void);
}; typedef struct tdevBusDataMobo DEV_CONNECTION_MOTHERBOARD_DATA; typedef struct tdevBusDataMobo* PDEV_CONNECTION_MOTHERBOARD_DATA;
#define DEV_CONNECTION_PCI_VERSION	0x00000000
struct tdevBusDataPci{
	u32 vendor;
	u32 device;
	i32 (*Init)(u32 bus, u32 slot);
}; typedef struct tdevBusDataPci DEV_CONNECTION_PCI_DATA; typedef struct tdevBusDataPci* PDEV_CONNECTION_PCI_DATA;

Here we have defined three structures for converying minimal scope information about each device to the appropriate bus, or the motherboard helper. You should note that these may be added to or changed just like interfaces. There only serve to present a standard or a language that can be understood between the bus or motherboard helper and the drivers. You may also notice that the DEV_CONNECTION_PCI_DATA connection data provides space for a vendor and device identifier. It would be wasteful for us to include this information into every driver header becoming the point to making the a customized data area for drivers and their buses or their motherboard helper.

Let us take a peek at the actual driver header structure.

struct tdevDriver{
	u8 		*szName;
	u16		connection;
	u16		connectionVersion;
	void 		*connectionData;			///
	void		*priv;
	u16		ifaceCount;
	DEVINTERFACE	iface[];
}; typedef struct tdevDriver DRIVER; typedef struct tdevDriver* PDRIVER; 
typedef struct tdevDriver MOTHERBOARDHELPER; typedef struct tdevDriver* PMOTHERBOARDHELPER;

We can first see that we have a field szName which is a user defined null terminated string. The connection field provides a value from our DEV_CONNECTION_* constants: DEV_CONNECTION_DEV,DEV_CONNECTION_MOTHERBOARD,DEV_CONNECTION_PCI. The connectionVersion field provides a fail safe mechanism that helps developers or users catch drivers compiled with older connectionData structures used and a listing which we seen but did not cover is: DEV_CONNECTION_DEV_VERSION,DEV_CONNECTION_MOTHERBOARD_VERSION,DEV_CONNECTION_PCI_VERSION. The connectionData field provides a pointer to the structure that contains the language that can be understood between the bus driver or motherboard helper and drivers that fit under these appropriately and some of the structures which we gave as a examples were: DEV_CONNECTION_DATA,DEV_CONNECTION_MOTHERBOARD_DATA,DEV_CONNECTION_PCI_DATA. The priv field is a speacil kernel writable only field and any value placed here should become over writen once a instance of the driver is created. We will cover instances later but for now the ifaceCount field contains the number of DEVINTERFACE structures that follow. The DEVINTERFACE structures are access by (iface[index]). Lets see if a example of this information put into the real world could help you grasp what is going on. Here is a motherboard helper structure, which is exactly the same as the driver structure.

static DEV_CONNECTION_DATA connectionData = {
	.vendor = 0,
	.board = 0,
	.Init = &mobo_generic_init	
};
MOTHERBOARDHELPER mobo_generic = {
	.szName = "MOTHERBOARD GENERIC",
	.connection = DEV_CONNECTION_DEV,
	.connectionVersion = DEV_CONNECTION_DEV_VERSION,
	.connectionData = &connectionData,
	.priv = 0,
	.ifaceCount = 0,
	{
		// no interfaces defined or needed by the generic motherboard.
		// -- however this is up to you and you're own kernel ideas!
	}
};

You can see that the connection field is specified as DEV_CONNECTION_DEV which repersents the kernel device subsystem. The connectionVersion field also represents the version value used from the kernel sources when the driver.. I mean motherboard helper was compiled. The connectionData is a important field by showing the relationship between the DEV_CONNECTON_DATA structure and the driver header.

Here is a PCI driver structure.

static DEV_CONNECTION_MOTHERBOARD_DATA connectionData = {
	.type = DEV_MOTHERBOARD_PCI,
	.Init = &pci_generic_init
};
DRIVER pci_generic = {
	.szName = "PCI GENERIC",
	.connection = DEV_CONNECTION_MOTHERBOARD,
	.connectionVersion = DEV_CONNECTION_MOTHERBOARD_VERSION,
	.connectionData = &connectionData,
	.priv = 0,
	.ifaceCount = 0,
};

The connection field in this driver specifies the DEV_CONNECTION_MOTHERBOARD value which says this driver has a connectionData field with relevant information to a driver that implements the DEV_CONNECTION_MOTHERBOARD mechanisms. In normal circumstances the motherboard helper will implement the DEV_CONNECTION_MOTHERBOARD mechanisms or language. Before I get too far I want to show a example list of constants that a generic motherboard helper would understand. These constants are specified in the DEV_CONNECTION_MOTHERBOARD_DATA.type field.

#define DEV_MOTHERBOARD_VGA 	0x1
#define DEV_MOTHERBOARD_PCI 	0x2
#define DEV_MOTHERBOARD_CLOCK	0x3
#define DEV_MOTHERBOARD_PIC	0x4
#define DEV_MOTHERBOARD_APIC	0x5

Just to note that this is just a design, and it is not absolutely required that you follow this method but at least understand what is going on so you have a idea of how you might like to change or reimplement this in your own way. We can see that the PCI driver is using the constant DEV_MOTHEROAD_PCI value for it's type field in the DEV_CONNECTION_MOTHEROARD_DATA structure. This is primary used in my example here to allow the motherboard helper to search for a PCI driver by looking through all the drivers that have the connection field value set to DEV_CONNECTION_MOTHERBOARD and have the DEV_CONNECTION_MOTHERBOARD_DATA.type field set to DEV_MOTHERBOARD_PCI. If for example there existed a PCI bus on a PCI bus you could end up with a driver header looking like this.

DRIVER pci_generic = {
	.szName = "PCI To PCI Bridge Generic",
	.connection = DEV_CONNECTION_PCI,
	.connectionVersion = DEV_CONNECTION_PCI_VERSION,
	.connectionData = &connectionData,
	.priv = 0,
	.ifaceCount = 0,
};

You can notice I set the connection field to DEV_CONNECTION_PCI instead of DEV_CONNECTION_MOTHERBOARD in the previous example. You might also allow one module contain two driver headers by using something of the such.

DRIVER pci_generic = {
	.szName = "PCI GENERIC",
	.connection = DEV_CONNECTION_MOTHERBOARD,
	.connectionVersion = DEV_CONNECTION_MOTHERBOARD_VERSION,
	.connectionData = &connectionDataPci,
	.priv = 0,
	.ifaceCount = 0,
};
DRIVER pcitopci_generic = {
	.szName = "PCI To PCI Bridge Generic",
	.connection = DEV_CONNECTION_PCI,
	.connectionVersion = DEV_CONNECTION_PCI_VERSION,
	.connectionData = &connectionDataPciToPci,
	.priv = 0,
	.ifaceCount = 0,
};

This (above) could allow each to have their own interfaces or even share the exact same interfaces and driver device driving code, but provide functionality for the motherboard helper and the PCI bus driver. This gives a lot of flexibility when writing drivers not only for today's systems but tomorrow's possibilities. Lets take one last look at a example of a driver header, but this time we will see a VGA driver's driver header.

static DEV_CONNECTION_PCI_DATA connectionDataPci = {
	.vendor = 0x1013,
	.device = 0xB8,
	.Init = &video_vga_pci_init
};

static DEV_CONNECTION_MOTHERBOARD_DATA connectionDataMotherboard = {
	.type = DEV_MOTHERBOARD_VGA,
	.Init = &video_vga_generic_init
};

static IPICTURE_PICTURE ifacePicturePicture = {
	.BltBlt = &BltBlt,			// basic picture functions not related to vga
	.SetFormat = &SetFormat,
	.Clear = &Clear
};
static IPICTURE_VGA ifacePictureVga = {
						// vga speacilized functions
	0 /* just a example but no interface functions shown */
};

DRIVER clgd5446_vga = {
	.szName = "CIRRUSVGA",
	.connection = DEV_CONNECTION_PCI,
	.connectionVersion = DEV_CONNECTION_PCI_VERSION,
	.connectionData = &connectionDataPci,
	.priv = 0,
	.ifaceCount = 2,
	.iface = {
		{
			.szName = "PICTURE.PICTURE",
			.major = DEV_PICTURE,
			.minor = DEV_PICTURE_PICTURE,
			.iface = &ifacePicturePicture
		},
		{
			.szName = "PICTURE.VGA",
			.major = DEV_PICTURE,
			.minor = DEV_PICTURE_VGA,
			.iface = &ifacePictureVga
		},
	}
};

DRIVER generic_vga = {
	.szName = "VGA",
	.connection = DEV_CONNECTION_MOTHERBOARD,
	.connectionVersion = DEV_CONNECTION_MOTHERBOARD_VERSION,
	.connectionData = &connectionDataMotherboard,
	.priv = 0,
	.ifaceCount = 2,
	.iface = {
		{
			.szName = "PICTURE.PICTURE",
			.major = DEV_PICTURE,
			.minor = DEV_PICTURE_PICTURE,
			.iface = &ifacePicturePicture
		},
		{
			.szName = "PICTURE.VGA",
			.major = DEV_PICTURE,
			.minor = DEV_PICTURE_VGA,
			.iface = &ifacePictureVga
		},
	}
};

Here we can see that in a example VGA driver that might be used in development of our kernel we actually have two drivers in one module. The first is a driver for the Cirrus CLGD5446' video card, and the second is a generic VGA driver. Each includes the appropriate values so that in reality this same module with two drivers could support two physical video cards in the system. One being a generic VGA adapter and only being a PCI card inserted into the PCI bus for the Cirrus CLGD5446 chip set. I hope I have given you a good understanding of the driver header. Lets continue by exploring the interface structures just a bit.

Initialization Handler

The initialization handler will provide the ability for a bus driver or the motherboard helper to create a instance of their driver which will immediantly start to control to device they were intended for. The language to tell the driver what device to control in a system is implemented in the connectionData field's custom data pointed to such as: DEV_CONNECTION_DEV_VERSION,DEV_CONNECTION_MOTHERBOARD_VERSION,DEV_CONNECTION_PCI_VERSION for examples. It is dependent on these structures being able to convey the information needed to be passed. For example lets take a look at the DEV_CONNECTION_PCI_DATA structure.

#define DEV_CONNECTION_PCI_VERSION	0x00000000
struct tdevBusDataPci{
	u32 vendor;
	u32 device;
	i32 (*Init)(u32 bus, u32 slot);
}; typedef struct tdevBusDataPci DEV_CONNECTION_PCI_DATA; typedef struct tdevBusDataPci* PDEV_CONNECTION_PCI_DATA;

Here we can see that it defined that the driver that wishes to use it as a connection must export a function that takes the arguments bus and slot. This enables to driver to perform a query back to the PCI bus driver for what resources it needs to control by identifying it's self as bus->slot when making calls. If more information was need it should be defined here by the developers of the PCI bus system for the kernel.

The example above for the Init function is called a initialization handler. All drivers should export some sort of initialization mechanism even if it is not in the ordinary function call way. In specialized cases which I can not think of it could be possible to not need a initialization handler, however that would be completely out of scope of this article but we do leave room for it. Lets take a look at a motherboard helper initialization handler.

i32 mobo_generic_init(void){
	/// do a sanity check if possible or needed
	......
		/// sanity check failed then exit and hopefully no harm done.
		.......
	/// create a instance of ourselves
	.......
	///  check if the APIC is avaible and use that instead of the PIC.
	.....
		/// find and get the PIC running
		......
	/// find and get any generic clock running that is possible.
	.....
	/// find and get pci bus running.
	......
	return 1;
}
static DEV_CONNECTION_DATA connectionData = {
	.vendor = 0,
	.board = 0,
	&mobo_generic_init	
};

Here is a general outline of what you might do in a generic motherboard helper.

1.0 Do a sanity check if possible or needed.

Here we might make a final determination of weather the driver should actually load. This is normally the second part of a two part system. The first part was where for instance the PCI bus driver associated a vendor and device identifier with this driver header. However in some real world cases this driver might not actually be able to handle this device and this is the chance it gets to make a determination and exit before creating a instance.
1.1. If sanity check failed then exit.
The sanity check failed because the driver's initialization handler determined there was a insane condition present.

2.0 Create a instance.

We create a instance and the kernel device subsystem performs the necessary loading of the interfaces and properties.

3.0 Check if the APIC is avalible and use that instead of the PIC.

For a generic motherboard helper we might try to see if the APIC exists and use that instead of the PIC.

4.0 Find and get the PIC running.

The APIC did not exit so lets find a driver for the PIC, or in a similar case the APIC did exist and we find a driver for it instead.

5.0 Find and get any generic clock running that is possible.

We look for any clocks that we can support and try to initialize them.

6.0 Find and get PCI bus running.

Get the PCI bus up and running. Some devices might need a clock or other common devices so we load it last.

Of course that is simple a rough outline of what you might or could do. I make no assertion that you should or should not do these things or in this order but rather assume you will use it to better your idea of what you want to do. This exact same case could be applied to a PCI driver such that.

int pci_init(void){
	// do a sanity check..
	// create a instance..
	// scan the PCI bus
		// search for a driver for each device found
		// try to initialize the driver and let it create a instance if needed.
	// exit function
	return 1;
}

The motherboard helper initialization handler would actually end up making a direct call to this pci_init initialization handler, and once it runs it might make more calls into the various drivers it might load which would terminate with returns and arrive hopefully if all goes well back at the original caller which would be the kernel device subsystem.

The Device Subsystem

The device subsystem in the kernel source tree is responsible for providing the basic structures or framework on which the drivers are built from. It also provides some general services to the drivers such as: searching, hardware resource management, instance creation and destruction, and system calls. To help explain some of the next sections lets implement some basic framing for a kernel device subsystem to work from.

// This is just a list of drivers known by the subsystem. This drivers may reside in memory by being staticaly compiled with the
// kernel, or on disk.
OLIST dev_drivers = 0;
// This is a list also, except it repersents instances of the list dev_drivers. When a instance is created the actual driver is known
// to be placed into a running state driving the device. This list allows searching of availible drivers.
PDEVINSTANCE dev_instances = 0;
PDEVINSTANCE dev_CreateInstance(PDRIVER driver);
// The entry point called by the kernel's main flow when booting.
void dev_init(void);
// A resource allocation linked list.
struct tdevres *dev_resl = 0;
// The function to allocate resources to the link list.
int dev_res_alloc(PPRIVATE priv, u8 type, u16 start, u16 count);

You can see that I maintain a list of known drivers as dev_drivers which of these could live in memory from being staticly linked with the kernel during the build, or drivers which have been detected by different means not only during boot by also during run-time of the kernel. The drivers in the dev_drivers are never actually executed but instead remain there in a sort of database form for future or current usage.

The future of current usage happens when through the chain of booting of drivers, they are called and the driver creates a instances of it's self or when the system user specifies a command to the device subsystem to perform a rescan and attempt to load missing drivers. This could caused by the kernel being booted and the user noticing a driver missing for some hardware. The user downloading the driver and then wanting to load and have this driver picked up with out restarting the kernel.

The resource allocation is performed after a driver creates a instance of it's self. Once a instance is created the driver will attempt to allocate any resources such as: memory ranges (physical and virtual), I/O ports, and any other type of resource. The structure for holding a resource allocation and some common resource types are:

struct tdevres{
	unsigned char		type;
	unsigned int		value[2];
	struct tdevres		*next;
};
#define DEV_RES_IO	1
#define DEV_RES_REGION	2

You is completely up to you how to impelment this, but for a example I have listed two resouces types with will be stored in the type field of the struct tdevres structure. The first is DEV_RES_IO which repersents a I/O port range that can be reserved by a driver. The second is DEV_RES_REGION with repersents a region of memory that can be reserved by a driver. If I wanted I could include a value that repersented a region of memory being allocated as physical or virtual if it meet the needs of my kernel. Once a resource allocation is made no other drivers or software in the system should ever have direct access to this resources except the driver that made the allocation of them.

Searching Services

The searching services allow drivers, kernel space software, or user space software to query for certain types of drivers or instances. This ability for user space software to access query services is encapsulated by the system calls provided by the device sub system too. The simple reasoning behind having the subsystem implement the searching services is to keep drivers from having to implement the same exact code over and over again. Instead lets maintain this functionality in one place instead of multiple places.

Hardware Resource Management Services

The hardware resource management services allow drivers to allocate and free resources. This provides a means for devices to determine if the resources they need are currently in use, and if it is possible for them to configure to use a different set of resouces then they can do so with minimal user intervention. This also prevents drivers from unintentionaly trying to use the same resources which give a high fail safe mechanism. Drivers should never use a resource before first allocating it and checking for a error condition being present on the allocation call.

Instance Creation And Destruction Services

The instance creation and destruction services help drivers perform a common task which is to actually employ themselves to control a device and become registered in the kernel as a loaded, initialized, and running device driver. This also allows changes in the core kernel device mangement subsystem to happen with out changing and recompiling all drivers since they ended up using their own instance creation and destruction routines. It becomes a much better idea to allow this to become centralized rather than bulking up every driver when this exact same code group has to be implemented each time instead of just once using the instance creation and destruction services provided by the kernel device management subsystem.

System Call Services

The device subsystem does not implement the actual system call mechanism, but serves as a entrance for user space software to access the subsystem through the usage of system calls. You might implement system calls in numerous ways however it is most prabobale that you're implementation supports a naming convention or call function with arguments and a result. These three things are most likely exactly what the system call into the device subsystem would need also: a function name, arguments, a result.

Overview

The device subsystem is comprised of searching services, hardware resource management, instance creation and destruction services, basic structures and framework for drivers, and system call services.

Driver Module Format

The driver module format will cover a stand alone binary file that contains the driver(s) machine code, and the driver(s) data. From this point you can move to compile-time linking or run-time linking for the drivers. I prefer to first implement the compile-time linking since it is a little easier with less overhead requirements from you're kernel. Such modules that contain generic disk drivers and other critical things are most likely best done in a compile-time linking fashion while leaving all the other non-critical ones left to be loaded during run-time. However this is simply a suggestion and it is up to you how to implement this into you're own kernel.

Compile-Time Linking

Compile-time linking of drivers is when they are included into the kernel build using a static or automated method. A static method is when you directly add the source and supporting source and headers into the source tree with out a isolation method to prevent the compilation of the driver with out editing the actual source. A automated method involves using a external tool that will staticly include the drivers into the source tree during a compile, but the drivers will remain out of the source tree when the automated mechanism is disabled or removed. A example of a automated method could be the when using Makefiles and having a driver directory with the first Makefile inside. This Makefile could be responsible for triggering a action that results in a static include of the driver into the kernel source for the current compilation. This makefile would be the mechanism for enabling or disabling the automated compile-time linking of drivers into the kernel.

I prefer compile-time linking personally since it has requires less overhead in the kernel at compile and run time. It does take a little effort to produce a quality automated mechanism however I will try to guide you into a quick and dirty one that will work for a small kernel project.

The Makefile And Driver Directory

Run-Time Linking

Kernel Abstractions

IRQ Routing Support

Clock Support