Character Device Driver
I’ve been working on character device driver since last few days and here i can share what all i have learnt till now.
Device drivers are the Loadable Kerenl Modules(LKMs)that can be inserted in the kernel at run time. They act as black boxes and provide a well defined interface for hardware to respond to a specific application code that want to communicate with the hardware. User just gives some specific code through application and to make these commands work in the hardware is the role of device driver.
Character device is the device that can be accessed as character by character, i.e., as stream of bytes as in files.
And characeter driver is the one that handles character devices.
The basic operations that a character driver atleast provides are open, write, read, release.
Implementation:
The very first operation to perform is inserting the driver into kernel when we want to access the device and when our work is done then to remove the module from kernel space. These two operations can be performed using module_init(init_func_name) and module_exit(exit_func_name).The initialisation function and exit function are written separately and passed to these macros.
The next step is to register our driver for any no. of devices using alloc_chrdev_region(). A single driver can handle any no. of devices and therefore to recognise driver and devices major numbers and minor numbers are used.Major no. are used by kernel to recognise the driver and minor no. are used by driver to recognise devices. Major no. and minor no. as a combination are stored in dev_t datatype that is a 32-bit field with 12 bits for major and rest 20 for minor numbers.Using MAJOR() and MINOR() macros we can take out major and minor numbers from dev_t while if we have them separately we can dev_t using MKDEV().
When inserting the module durimg run time we usually don’t know the correct major number that is free so we dynamically allocate the major number and minor number and the return type of alloc_chrdev_region() is the first device number for ‘n’ number of devices.In exit function we also have to unregister our module using unregister_chrdev_region() by providing the correct device no. and the no. of devices we want to unregister.
As we are working at kernel level so the library functions and header files that were available at user level will not work here. The header files are to be included from kernel directory. Similarly, malloc() function’s work here is done by kmalloc() function.
The most important structure here is SCULL(simple characetr utility for loading localities). Scull is a character driver that acts on a memory as though it were a device. The entry point to scull is pointer to struct sculldev and sculldev is allocated memory using kmalloc() for nod no. of devices. Struct sculldev has a field struct cdev which is the kernel internal representation of the device and we also have to initilaise this cdev field while setting up the scull.The cdev_init() initialises the cdev, cdev_add() adds the device to the kernel device drivers table and and in exit function we can unregister cdev using cedv_del().There are other fields as well in struct sculldev which are to initialised as their initial values. This also contains a pointer to struct scullqset which are same as linked list and consisits of node poinitg to next scullqset and data field which pints to qsetarray and qset contain pointers to quantums which actually store the data to writen to and read from device’s memory.
We can map our own operations to the standard function_calls such as open,etc. in struct f_ops which is predefined. Therefore we are supposed to write our own open, write ,read and release functions for our device.The application will communiceta with our device using a node which can be created using mknod macro. All communication will take place through this node between application and device driver and hence device.
Open function should just initialise the device and scull_trim function is used if the device is opened in write only mode.There is a macro ‘container_of’ that actually maps our scull onto the device’s memory by linking inode, cdev and sculldev structure. The resulting sculldev in private_data field of struct file so that the opened sculldev can be used durin write and read.
Write function will actually create scullqset, qsets and quantums depending on the size of data to write. We use a macro ‘copy_from_user()’ to write to quantums from the user buffer & this is a very important macro as it prevents application from directly peeping into the kernel and hence provides security.Similarly in read function we use ‘copy_to_user()’ to copy from quantums to the user buffer.
Release function just releases all the acquired parameters when the application closes the device.There is a funcion for lseek as well if the user wants to append something to previous data or user wants to read some selected data.
I’have learnt till here till now and hope that soon i’ll be able to complete my character driver!!