NetBSD Documentation:Writing a pseudo device |
Probably the first important decision you need to make is what you are going to call your new device. This needs to be done up front as there are a lot of convenience macros that generate kernel structures by prepending your device name to the function call names, will help if you have an idea of the config file entry you want to have. The config file entry does not have to match the header file name. In our skeleton driver we have decided to call the pseudo-device "skeleton", so we shall have a config file entry called skeleton. This means that the attach, open, close and ioctl function calls are named skeletonattach, skeletonopen, skeletonclose and skeletonioctl respectively. Another important decision is what sort of device you are writing - either a character or block device as this will affect how your code interacts with the kernel and, of course, your code itself. The decision of block vs character device depends a lot on the underlying hardware the driver talks to, if the device the driver talks to operates by reading and write data in fixed chunks then a block device is a good choice, an example of such a device is a hard disk which usually reads and writes data in blocks of 512 byte sectors. If the hardware reads and writes data one byte at a time then a character device is normally the best choice, an example of such a device is a serial line driver. Note that some drivers support both a block mode and character mode of access to a device, in this case the character mode is sometimes called the "raw" device because it gives access to the hardware without the data blocking abstractions operating on the access. For a pseudo-device the choice is more flexible because there is no underlying hardware to consider. The choice is driven by the use the pseudo-device is going to be put to, a block device may be useful if you are going to emulate a hard-disk or similar. Our skeleton driver is to be a character device.
Once the decisions have been made we can start cutting code, before we do this though we need to decide where our file should go. If you are writing a pseudo-device that will be used by multiple architectures then the appropriate place to put the driver code is in /usr/src/sys/dev. If the pseudo-device is specific to a particular architecture then put the driver code under the architecture specific directory, for example on the i386 architecture this would be /usr/src/sys/arch/i386/i386. The include file should go into /usr/src/sys/sys for the architecture independent device and in the include directory under the architecture specific directory for an architecture specific device, for example, on the i386 architecture this would be /usr/src/sys/arch/i386/include. In either case ensure that you update the relevant Makefile so your include file is installed. One thing you will note is the struct skeleton_softc at the top of pseudo_dev_skel.c. You must have a softc structure declared with the name of your device with "_softc" appended and the first element of this struct needs to be a struct device type, the name of the entry is not important but it must be first as the autoconfig system relies on the softc struct being declared and that its first element is a struct device. There needs to be a softc struct for each minor number a device handles. The softc structure can hold more elements than just the struct device if the minor devices require state information to be kept about them.
#define cdev_skeleton_init(c,n) cdev__oci_init(c,n)This defines a macro we can use to define the cdevsw entry in another file. For a more complex driver you can just copy one of the other defines and modify as required.
#include "skeleton.h" cdev_decl(skeleton)Wait a minute! We haven't created a skeleton.h! That is correct, we don't create that file. It will be created by config(8), we shall see later how this is done. The second line there sets up the function prototypes for the skeleton driver - you should replace skeleton with the name of your pseudo-device. That takes care of the declarations. Now we need to add the device into the bdevsw/cdevsw table. Since skeleton is a cdev we need to find the cdevsw array and add an entry to it. You should add an entry to the end of the array - trying to add an entry in the middle of the cdevsw table will mess up all the other device drivers. So, at the end of the cdevsw table we add an entry like this:
cdev_skeleton_init(NSKELETON, skeleton), /* 71: Skeleton pseudo-device */Again, NSKELETON is not defined by us anywhere. When config(8) is run it will generate a skeleton.h file with NSKELETON defined in it, the symbol defines the number of these devices to create - the number comes from the config file. Note that cdev_skeleton_init is the macro we defined in conf.h and that the second parameter ("skeleton") is the name of our pseudo-driver. This macro concatenates the name of the pseudo-driver with the function call names (open, close, ioctl, etc) to produce the function names that we have defined in our code. This is how the kernel knows to run your code. The last bit of the puzzle is the number in the comment next to the entry. You must copy the format of the other entries, increment the number and put the function of your device into the comment. The number is important. This number is the major number of your device. You need to make a note of this number for later.
defpseudo skeletonWhich tells config(8) we have a pseudo-device called skeleton. Next we need to tell config(8) which files are associated with the skeleton pseudo-device. In this case we only have on file but a more complex pseudo-device may have more files, simply add each file required on a line in the same manner. For our example we only need one line that looks like this:
file dev/skeleton.c skeleton needs-flagThe file on the line is a key word to say we are defining a device to file association. The second field is the location of the file relative to the root of the kernel source tree (normally, /usr/src/sys). The third field is the name of the driver that this file is associated with, in our case this is skeleton - our sample pseudo-device. The fourth and last field is a control flag that tells config(8) to write the skeleton.h include file. Note that here the file is called skeleton.c, if we were using the example files here, we would have to either rename pseudo_dev_skel.c to skeleton.c or change this entry. Since we said above that we are calling it skeleton, it would probably be more suitable to call it skeleton.c.
pseudo-device skeletonTo the kernel config file, note the name of the pseudo-device matches the name given in the defpseudo line in the previous section. New defines can be added to the kernel makefile by using the options kernel config file keyword, config will build a makefile with the options named added as -D command line options to the cc command.
mknod /dev/skel c 71 0Once this has been done you should be able to open your new device and test it out. The file sample.c shows the skeleton pseudo device in action. This file assumes you have followed the instructions here and have created /dev/skel, this device is opened and a parameter structure passed to the device driver via an ioctl call. To compile the sample code use this command line:
cc -o sample sample.cWhich will produce a binary called sample. NOTE: you will have to have run make includes in the directory you copied pseudo_dev_skel.h to install the header file into the system includes directory otherwise the compiler will complain about a missing include file. Once you have compiled the programme, run it and then look at your kernel messages either on the console screen or in /var/log/messages, they should have a message that looks like this:
May 17 20:32:57 siren /netbsd: Got number of 42 and string of Hello WorldWhich is a message printed by the skeleton ioctl handler when it receives a SKELTEST ioctl request; notice that the number and the string printed are the ones we put into the param structure in sample.c.
|
|