Just before the driver is unloaded the kernel will look for a global function named "device_uninit()" and if found it will be called to allow the driver to clean up.
There is also a "device_release()" function which is called for every claimed device before device_uninit() is called for the driver.
This device_init() and device_uninit() are the only functions that will be called directly by the kernel. For the driver to do any usefull work it must export one or more device-nodes through the Syllable Device FS. This is a logical file system that is mounted at "/dev/" and controll all devices. Each device is present as a magic file located in "/dev/" or a sub-directory of "/dev/". Initially "/dev/" only contain "/dev/null" and "/dev/zero" which is controlled by the Syllable VFS itself. All other directory and device-nodes are created by device drivers. A device driver can create a device-node with create_device_node() and remove it with delete_device_node(). When creating a device node the driver must provide a function pointer table with entry points to the drivers functionality. The functions in the table will be called by the Syllable VFS to controll the device. The most important functions are read(), write() and ioctl() but there are also functions to open/close the device aswell as functions called by select() to make it possible for one thread to wait for IO on multiple devices.
A typical device driver will create one node in device_init() and delete it again in device_uninit().
int g_nMyDeviceNode = -1; status_t device_init( int nDeviceID ) { struct MyDeviceNode sNode; g_nMyDeviceNode = create_device_node( nDeviceID, -1, "misc/mydevice", &g_sOperations, &sNode ); if ( g_nMyDeviceNode < 0 ) { return( g_nMyDeviceNode ); /* Failed to create the device node */ } else { return( EOK ); } } status_t device_uninit( int nDeviceID ) { delete_device_node( g_nMyDeviceNode ); return( EOK ); }
If this is the first time someone attempts to open the CPiA device the driver is not loaded and "/dev/video/cpia" does not exists. If this is the first time anything inside /dev/ is touched neighter does the "/dev/video" directory.
To make it possible for the kernel to search for drivers in a efficient way the driver-binaries are located in a directory tree similar to the resulting tree inside /dev/. For example the CPiA driver from the above example whould be located in "/system/drivers/dev/video/cpia".
If the kernel is asked to open for example "/dev/video/cpia" it would start by opening the "/dev" directory which would cause the "/system/drivers/dev" directory to be iterated. During the iteration all drivers found will be attempted to load and initiate and all directories will be replicated inside /dev/. Since "/system/drivers/dev" contains a directory named "video" this will cause "/dev/video" to be created. When "/dev/" is successfully opened it will attempt to open "/dev/video" which should now exist. Opening "/dev/video" will cause the "/system/drivers/dev/video" directory to be iterated and the "cpia" binary to be loaded. The CPiA driver will then probe for a CPiA device and if found it will create a device node named "/dev/video/cpia" which will then be found and opened when the kernel descend into the "/dev/video" directory.
In the trivial example above there was direct match between the name of the driver and the name of the device inside /dev. Since one driver might export more than one device this is not always the case. For example a IDE disk driver whould export one device for each disk connected to the bus and one device for each partitions found on those disks. The device-tree exported by a IDE driver might look something like this:
/dev/disk/hda/raw /dev/disk/hda/0 /dev/disk/hda/1 /dev/disk/hdb/raw /dev/disk/hdc/raw /dev/disk/hdc/0
In this case the ide driver should be located in "/system/drivers/dev/disk/ide". If someone attempts to open the first partition on the master disk connected to the first controller it whould have to open "/dev/disk/ide/hda/0".
When descending the path the kernel will first create the "/dev/disk" and the "/dev/disk/ide" directory. Then it will load the ide-driver which will detect that there are 3 disks connected to the two controllers before decoding the partition tables and add all the nodes listed above. At his point "/dev/disk/ide/hda/0" already exists and no other drivers need to be loaded to fullfill the request.
PCI_bus_s* psBus = get_busmanager( PCI_BUS_NAME, PCI_BUS_VERSION ); if( psBus == NULL ) { // handle error }
You would now have a pointer to a PCI_bus_s structure, which is defined in the PCI busmanager header file and contains pointers to the busmanager's functions. Access to the USB bus works similar, alhough of course the functions in the USB_bus_s structure are different.
int nHandle = sMyDevice.nHandle // get Handle from the busmanager if( claim_device( nDeviceID, // device ID of the driver nHandle, "My device", // name for the device DEVICE_AUDIO ) // type of the device != 0 ) { // handle error }
If you have claimed any device then the kernel will call the "device_release()" driver function for this device. An implementation could look like this:
status_t device_release( int nDeviceID, int nDeviceHandle, void* pData ) { release_device( nDeviceHandle ); // Release device ... // free device data, release irq, ... }
The data pointer points to the data set which is the by the set_device_data() function.
Please note that if you support hardware that can be removed while the computer is running ( e.g. USB devices ) you have to call release_device( nHandle ) if the busmanager informs you about the removal.
If you want to support hardware, which is not managed by any busmanager, you can do a register_device() call yourself:
int nHandle = register_device( "My device", // name "isa" ); // busname claim_device( nDeviceID, nHandle, "My device", DEVICE_AUDIO );
This will not prevent two drivers to access one device, but it will show some nice information to the user.
status_t device_suspend( int nDeviceID, int nDeviceHandle, void* pData ) { ... // save state }
When the computer wakes up again, the kernel calls the device_resume() function which takes the same parameters as the device_suspend() function. The driver has to wakeup the device and restore its state here.