Shweta, at her PC in her hostel room, was all set to explore the characters of Linux character drivers, before it was taught in class. She recalled the following lines from professor Gopi’s class: “… today’s first driver would be the template for any driver you write in Linux. Writing any specialised/advanced driver is just a matter of what gets filled into its constructor and destructor…”
With that, she took out the first driver’s code, and pulled out various reference books, to start writing a character driver on her own. She also downloaded the online book, Linux Device Drivers by Jonathan Corbet, Alessandro Rubini, and Greg Kroah-Hartman. Here is the summary of what she learnt.
W’s of character drivers
We already know what drivers are, and why we need them. What is so special about character drivers? If we write drivers for byte-oriented operations (or, in C lingo, character-oriented operations), then we refer to them as character drivers. Since the majority of devices are byte-oriented, the majority of device drivers are character device drivers.
Take, for example, serial drivers, audio drivers, video drivers, camera drivers, and basic I/O drivers. In fact, all device drivers that are neither storage nor network device drivers are some type of a character driver. Let’s look into the commonalities of these character drivers, and how Shweta wrote one of them.
The complete connection
As shown in Figure 1, for any user-space application to operate on a byte-oriented device (in hardware space), it should use the corresponding character device driver (in kernel space). Character driver usage is done through the corresponding character device file(s), linked to it through the virtual file system (VFS). What this means is that an application does the usual file operations on the character device file. Those operations are translated to the corresponding functions in the linked character device driver by the VFS. Those functions then do the final low-level access to the actual device to achieve the desired results.
Note that though the application does the usual file operations, their outcome may not be the usual ones. Rather, they would be as driven by the corresponding functions in the device driver. For example, a write followed by a read may not fetch what has just been written to the character device file, unlike for regular files. Remember that this is the usual expected behaviour for device files. Let’s take an audio device file as an example. What we write into it is the audio data we want to play back, say through a speaker. However, the read would get us audio data that we are recording, say through a microphone. The recorded data need not be the played-back data.
In this complete connection from the application to the device, there are four major entities involved:
- Application
- Character device file
- Character device driver
- Character device
The interesting thing is that all of these can exist independently on a system, without the other being present. The mere existence of these on a system doesn’t mean they are linked to form the complete connection. Rather, they need to be explicitly connected. An application gets connected to a device file by invoking the open system call on the device file.
Device file(s) are linked to the device driver by specific registrations done by the driver. The driver is linked to a device by its device-specific low-level operations. Thus we form the complete connection. With this, note that the character device file is not the actual device, but just a place-holder for the actual device.
Major and minor numbers
The connection between the application and the device file is based on the name of the device file. However, the connection between the device file and the device driver is based on the number of the device file, not the name. This allows a user-space application to have any name for the device file, and enables the kernel-space to have a trivial index-based linkage between the device file and the device driver. This device file number is more commonly referred to as the <major, minor>
pair, or the major and minor numbers of the device file.
Earlier (till kernel 2.4), one major number was for one driver, and the minor number used to represent the sub-functionalities of the driver. With kernel 2.6, this distinction is no longer mandatory; there could be multiple drivers under the same major number, but obviously, with different minor number ranges.
However, this is more common with the non-reserved major numbers, and standard major numbers are typically preserved for single drivers. For example, 4 for serial interfaces, 13 for mice, 14 for audio devices, and so on. The following command would list the various character device files on your system:
$ ls -l /dev/ | grep "^c"
<major, minor> related support in kernel 2.6
Type (defined in kernel header linux/types.h
):
dev_t
contains both major and minor numbers
Macros (defined in kernel header linux/kdev_t.h
):
MAJOR(dev_t dev)
extracts the major number fromdev
MINOR(dev_t dev)
extracts the minor number fromdev
MKDEV(int major, int minor)
creates thedev
from major and minor.
Connecting the device file with the device driver involves two steps:
- Registering for the
<major, minor>
range of device files. - Linking the device file operations to the device driver functions.
The first step is achieved using either of the following two APIs, defined in the kernel header linux/fs.h
:
+ int register_chrdev_region(dev_t first, unsigned int cnt, char *name); + int alloc_chrdev_region(dev_t *first, unsigned int firstminor, unsigned int cnt, char *name);
The first API registers the cnt number of device file numbers, starting from first, with the given name. The second API dynamically figures out a free major number, and registers the cnt number of device file numbers starting from <the free major, firstminor>
, with the given name. In either case, the /proc/devices
kernel window lists the name with the registered major number. With this information, Shweta added the following into the first driver code:
#include <linux/types.h> #include <linux/kdev_t.h> #include <linux/fs.h> static dev_t first; // Global variable for the first device number
In the constructor, she added:
if (alloc_chrdev_region(&first, 0, 3, "Shweta") < 0) { return -1; } printk(KERN_INFO "<Major, Minor>: <%d, %d>\n", MAJOR(first), MINOR(first));
In the destructor, she added:
unregister_chrdev_region(first, 3);
It’s all put together, as follows:
#include <linux/module.h> #include <linux/version.h> #include <linux/kernel.h> #include <linux/types.h> #include <linux/kdev_t.h> #include <linux/fs.h> static dev_t first; // Global variable for the first device number static int __init ofcd_init(void) /* Constructor */ { printk(KERN_INFO "Namaskar: ofcd registered"); if (alloc_chrdev_region(&first, 0, 3, "Shweta") < 0) { return -1; } printk(KERN_INFO "<Major, Minor>: <%d, %d>\n", MAJOR(first), MINOR(first)); return 0; } static void __exit ofcd_exit(void) /* Destructor */ { unregister_chrdev_region(first, 3); printk(KERN_INFO "Alvida: ofcd unregistered"); } module_init(ofcd_init); module_exit(ofcd_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Anil Kumar Pugalia <email_at_sarika-pugs_dot_com>"); MODULE_DESCRIPTION("Our First Character Driver");
Then, Shweta repeated the usual steps that she’d learnt for the first driver:
- Build the driver (
.ko
file) by runningmake
. - Load the driver using
insmod
. - List the loaded modules using
lsmod
. - Unload the driver using
rmmod
.
Summing up
Additionally, before unloading the driver, she peeped into the /proc/devices
kernel window to look for the registered major number with the name “Shweta”, using cat /proc/devices
. It was right there. However, she couldn’t find any device file created under /dev
with the same major number, so she created them by hand, using mknod
, and then tried reading and writing those. Figure 2 shows all these steps.
Please note that the major number 250
may vary from system to system, based on availability. Figure 2 also shows the results Shweta got from reading and writing one of the device files. That reminded her that the second step to connect the device file with the device driver — which is linking the device file operations to the device driver functions — was not yet done. She realised that she needed to dig around for more information to complete this step, and also to figure out the reason for the missing device files under /dev
.
We will deal with her further learning in our next article.
When will we get ubuntu again..
What do you mean by that? Please elaborate.
First, I would like to thank you for the tutorials. I have created a basic char driver called ‘physmem’. I compiled, inserted module (insmod) and create a device (
sudo mknod /dev/physmem0 c 250 0 ). then I did cat /dev/physmem0 , with dmesg, I see ‘open invoked’ and ‘read invoked’. however, when I did sudo echo hi > /dev/physmem0. it complained about ‘permission denied” so I logged in as superuser ‘su’ and executed the above command (echo hi….). the system got hunged. I looked at the log (dmesg), i see infinite entries of ‘write invoked’. ..so what caused it to call device_write() so many times?
//physmem.c
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
/*
cat /proc/devices | head -28 | tail -10
ls -l /dev | grep “250”
sudo mknod /dev/physmem0 c 250 0
sudo mknod /dev/physmem1 c 250 1
cat /dev/physmemo0
*/
#define DRIVER “physmem”
#define DEVICE_NAME “physmem”
#define PHYSMEM_VENDOR_ID 0x10EC //for Realtek8139 pci network device
#define PHYSMEM_DEVICE_ID 0x8139
/*Globals */
static int Major;
static int Device_open = 0; /*To prevent muliple access*/
static dev_t dev; //First Device number
static struct cdev c_dev;//Character device structure
static struct class *c1;//Device class
static int device_open(struct inode*, struct file*);
static int device_release(struct inode*, struct file*);
static ssize_t device_read(struct file*, char*, size_t, loff_t*);
static ssize_t device_write(struct file*, const char*, size_t, loff_t*);
static struct file_operations fops = {
.read = device_read,
.write = device_write,
.open = device_open,
.release = device_release
};
/*
* Called when a process tries to open the device file, like
* “cat /dev/mycharfile”
*/
static int device_open(struct inode* inode, struct file* file)
{
printk(“(physmem)Device open invoked.n”);
//try_module_get(THIS_MODULE);
return 0;
}
static int device_release(struct inode* inode, struct file* file)
{
//kfree(file->private_data);
Device_open–;
/*
* Decrement the usage count, or else once you opened the file, you’ll
* never get get rid of the module.
*/
//module_put(THIS_MODULE);
}
static ssize_t device_read(struct file* file, char* buffer, size_t length, loff_t* offset)
{
unsigned char ch = 0;
struct driver_info* driv_info = (struct driver_info*)file->private_data;
printk(“(physmem)Device read invoked. n” );
//rwlock_t _lock = RW_LOCK_UNLOCKED;
/*
if(driv_info->id == 0)
{
write_lock(&_lock);
ch = dequeue(ringbuffer1);
write_unlock(&_lock);
}
else if( driv_info->id == 1)
{
write_lock(&_lock);
ch = dequeue(ringbuffer2);
write_unlock(&_lock);
}
*/
//copy_to_user(buffer, &retValue, length);
return 0;
}
static ssize_t device_write(struct file* file, const char* buffer, size_t length, loff_t* offset)
{
//struct driver_info* driv_info = (struct driver_info*)file->private_data;
printk(“(physmem)Device write invoked.n” );
int ret = 0;
return ret;
}
static int physmem_init(void)
{
printk( “(physmem)Module init.n”);
Major = register_chrdev(0, DEVICE_NAME, &fops);
if( Major < 0 )
{
printk("(physmem)Device registration failed.n");
}
/*
if (alloc_chrdev_region(&dev, 0,1, DEVICE_NAME) < 0)
{
printk( "(physmem)Module init::alloc_chrdev_region failed.n");
return -1;
}
if( c1 = class_create(THIS_MODULE, "chardrv") == NULL )
{
printk( "(physmem)Module init::class_create failed.n");
unregister_chrdev_region(dev, 1);
return -1;
}
if( device_create( c1, NULL, dev, NULL, "physmem_device") == NULL )
{
printk( "(physmem)Module init::device_create failed.n");
class_destroy(c1);
unregister_chrdev_region(dev, 1);
return -1;
}
cdev_init(&c_dev, &fops);
if( cdev_add(&c_dev, dev, 1) == -1)
{
device_destroy(c1, dev);
class_destroy(c1);
unregister_chrdev_region(dev, 1);
return -1;
}
*/
}
static void physmem_exit(void)
{
printk("(physmem)Module exit.n");
unregister_chrdev(Major, DEVICE_NAME);
//unregister_chrdev_region(dev, 1);
}
module_init(physmem_init);
module_exit(physmem_exit);
MODULE_LICENSE("GPL");
The problem is basically with the return 0 in your write call. With
that you are basically failing the write and so the write is getting
retried, which again fails, and so on & so forth. Hence it keeps
repeating the same, causing the infinite loop. Instead change the
“return ret” to “return length”.
I had the same problem. When I changed the return statement in my_write to your suggestion (return len) everything started to work correctly.
Conclusion: It is worth to read all comments.
:)
How she get 0 and 3 for majar and minor number
As per our code.
How she got the 250 major number for her device drive file
What are you getting? As it is dynamically allocated, you may get a different one. Whatever you get, change the mknod parameter accordingly.
Hi Anil,
I am using ur articles as my first steps in drivers. I really appreciate the care you take to describe steps in such detail. I did read this one yesterday and today and still couldn’t clearly understand some points. In a paragraph about major and minor numbers, I am a little confused as to what they exactly mean. It says the first API registers ‘cnt number’ of device file numbers. What is the need to have many numbers for a single driver? is it for redundancy?
and most importantly, may be it would help if you can differentiate between major number and minor number in terms of which is required for what purpose. I could understand both together choose the driver for that device file.
Thanks
Say, major refers to a category of devices, like serial. So, all identical serial ports would be managed by the same driver, and all their device files will have the same major number. Then, how do we differentiate, as to which device file corresponds to which device? And exactly that is done by the minor number – it is not for redundancy.
Can you please give me some tutorails on network drivers?
alokal.123@gmail.com
Check out my slides at http://www.slideshare.net/anil_pugalia/network-drivers
Anil, your posts read like a best seller novel… Congrats! What an awesome job. There are no words to express my gratitude to you. Thanks!
Moises.
Thanks for such a high appreciation.
Hi Anil, Your articles are awesome Please give some tutorials on PCI Sound Card drivers using ALSA.
For the time being, I have completed the 24 article series on Linux Device Drivers. Currently, writing on mathematics in open source.
how to getting minor number
We are not getting. We decide which minor to use? That’s a decision of the driver.
thank u anil
please tell me how to use major and minor numbers statically and dynamically
I think both are mentioned in the article. Dynamically is done using alloc_chrdev_region() and statically using register_chrdev_region()
thank u for giving replay ,
how to allocate minor number
but i have small clarification
is it correct.
major no is identifies the driver
minor no is identifies the device
please clarify it anil
In most typical cases, yes.
thank u anil
please suggest me is it correct
why we go for dynamic char dev register?
ans: which major number your device will use
When we are writing a device driver for a non-standardized device, we need to pick up a free major number. That’s where we use the dynamic char dev register, which gets us a free major number.
how to configure the driver
What does that statement mean?
hi Anil when is spin locks and mutex areused
For synchronization of mutual access of resources.
both are used for same or not
Yes but in different scenarios. mutex can be used only in process content, spin locks anywhere.
hi Anil how r u
what is the entry point for the driver
What do you mean by entry point? Its like asking what is the entry point of a library?
entry point is starting end point of a node which connect the application layer to kernel
Not really clear. But, in that case system calls are the entry points for the driver.
why synchronization machanisam used in linux driver devalopment?
For providing synchronization between various kernel threads and user process & thread instance executions.
thank you sir
hello sir,
what does “a+w” means in the “sudo chmod a+w /dev/ofcd* ” command.Please tell me sir.
I can tell you but you won’t learn that way. Do a “man chmod”
After reading man page i now understood what does a+w means but a new doubt arises is why you have used “ofcd* ” in place of ofcd??
* is a wildcard matching any pattern after ofcd, say 0, 1, …
Hello sir,
Why __init and __exit tags is used to build the sample driver file ?
Should all driver files should have these tags or not necessary ?
What is the significance of these tags ?
Thank you,
Najmuddin
These should be used for many reasons. Two of them being, kernel’s initialization & cleanup code optimization, and for consistent behaviour between when built into kernel vs built as a module.
Hello sir,
Thank you for the reply..
I dint get this statement..”and for consistent behaviour between when built into kernel vs built as a module”.
Thank you,
Najmuddin
When built as a module, insmod & rmmod triggers the corresponding init & exit functions. But when built into the kernel, there is no insmod or rmmod, but still these functions should be appropriately invoked, which is achieved by these section keywords, by putting them in the corresponding sections.
Awesome explanation. I was also thinking why those were needed. Your mode of writing is great!!
Thanks Rengasami.
Nice tutorial I like your posts …
Thanks for liking them.
Hello anil ,
Really great work you are doing to make it easy understanding as well as implementing device drivers on linux ,keep up the good work .
I just have a question ,what is the difference between the 2 functions u mentioned in the article ( register_chrdev_region() ,alloc,chrdev_region() ),im noticing that the alloc_* one takes one more parameter which is the firstminor ,that means that the difference is just choosing the first number in the sequence of minor numbers ,but u also stated here in many of your comments ,that the first one is used statically ,while the second is used dynamically ,can u explain that in more details ? what is the need to allocate something dynamically or statically ,what’s the difference ,why should i use one while not using the other ?
Thanks in advance ,and thx for the good articles as well .
Much Regards
Jacky
While writing drivers for the standard categories like serial, usb, etc the major numbers are pre-designated. In such cases, we would like to register the character driver with the fixed pre-designated number, and hence use the register_chrdev_region(). However, in case we want to write a driver for a device not falling into a pre-designated major number, we would like to use a free major number, which then should be obtained & registered using the alloc_chrdev_region().
when i install kernel image i got error. i want help
Please provide details.
Hi anil,
I really appreciate your work and its quite helpful for beginners like me.
I gave a wrong argument value for “unregister_chrdev_region()” and by the time i figured this out, many character devices with different major number got created under same device name.
Is it possible to remove these devices? if yes, Please tell me how to remove.
Thanks,
Anand
Technique #1: Just write the following code in the init function & do the insmod of the module:
for (i = 250; i >= 238; i–) unregister_chrdev_region(MKNOD(i, 0), 3);
Technique #2: Reboot your system. :)
Thanks anil
Hi Anil,
Is there any easy lookup for Kernel Api’s? like how to find the list of api’s we can use in writing any driver(eg: here Char driver).
– Satya
The easiest lookup is the corresponding header file. :)
Hi Anil,
It was a great experience attending one of your traning class.
Hope you remember one of the question I put before you in that particualr class
Regarding Drive registeration with file system other than VFS ,similar sort of question(NOT exact) have been asked and answered in following link
http://stackoverflow.com/questions/14501437/how-does-open-works-for-normal-file-and-device-drivers.
1) How does virtual file system(VFS) get to know on which file system the
underline file resides?
2) What does actually cdev_add() do? in terms of registering a device to the
kernel.
Though these queries have been answered(didn’t really get it) but would like to hear your point of views on these queries.
Thanks,
1) Drivers register with VFS, and then user commands go through VFS. So, VFS is the common point for both, with driver informing it first, as what it supports, and then the user app accessing something supported by the underlying driver.
2) cdev_add() registers the required information with VFS.
Related to MODULE_LICENSE(“GPL”)….here we are given the license to our diver…… in this statement, GPL is just indicates that its a general purpose license but where is the license key …..??
What do you mean by license key? Open Source doesn’t believe in hiding stuff using keys. The mention of the license indicates that it is open source.
i think some registration key (or) license key, is to be given when we are using the device driver….like we did in the device driver installation in windows…///
thanks for the sharing////
No dude. Nothing like that. Its Linux not Windows. Load it and use it, that’s all.
static dev_t first;
sir plz tell me,why u take first as static??
any specific reason??
For it to be visible only locally within this file.
but for such purpose a device structure is good to used??
plz share the fine programs for spin-lock and other kernel timers..
With device structure it is not within the file, but accessible to anyone having the device structure.
And what is this fine programs?
well written code for spinlock,kernel timers..i have difficulties to understand these stuff..plz explain in next tutorial..
Right now, I am writing articles on maths in open source – so I may not be able to write on these topics.
unregister_chrdev_region(first, 3);
for this line its throwing me error line “implicit declaration of function unregister_chrdev_region” after make command !! what is the problem here ? the .c file is the exact same as above.
Which kernel version are you using?
can i get figure1 char drivers overview