Date: Sun, 23 Feb 1997 19:09:25 -0800 From: Julian Elischer <julian@whistle.com> To: "Brian J. McGovern" <mcgovern@spoon.beta.com> Cc: hackers@freebsd.org Subject: Re: Device driver cookbook. Message-ID: <33110665.167EB0E7@whistle.com> References: <199702221509.KAA12718@spoon.beta.com>
next in thread | previous in thread | raw e-mail | index | archive | help
Brian J. McGovern wrote: > > I promised I'd eventually get to it, and I'll be working on it heavily > this weeknd. Below is the first "installment" where I walk through a simple > pseudo-device driver that you can read from with a fixed buffer. Subsequent > additions will make it more flexable. I'm releasing it to "hackers" now, so > that everyone has a chance to comment, and let me know if they like/don't like/ > would like me to change anything. Thanks for the comments ahead of time. the skeletons I checked in to /usr/share/examples do this as well. you migh include a pointer.. (they also get the device filesystem parts correct etc.) a comment.. if open checks the dev_t then read doesn't need to.. (one reason for open is to verify all thes sorts of things) (why I think you shoul dinclude an open() ) > #include <sys/param.h> > #include <sys/systm.h> > #include <sys/ioctl.h> > #include <sys/conf.h> > #include <sys/uio.h> > #include <sys/kernel.h> > I'd just have hte driver there at teh bottom and not keep reiincluding parts.. just show the bit you are talking about... > character device driver locally). What we will do now is #define CDEV_MAJOR > to be equal to the number we just chose. This is one of those "standards" that > just make sense. You'll find that doing it this way, rather than just > hard-coding the major number, makes it easier to change later, as well as > lets other developers know what to look for, in case they ever have to work > on your driver. It'll also help stop typos, as well. If I get off my rear end and finish devfs, then major numbers can be assigned dynamically (you give a major of -1 and it will do that now) > > You'll notice that the string is declared static. This is so that it will > not conflict with any other variables called "message" elsewhere in the > kernel. You will see, rather quickly, that nearly all global variables, > or exported functions, are declared static. which means that they are not exported.. everything is referenced via the well know structs cdevsw and isa_driver > > Now, we only have a few more things to set up for our device driver. Since > we'll only be concerned about reading from our driver, we'll only concern > ourselves explicitly with the read routine. We will "fake" the open > and close routines for the time being, so that they will always succeed. Note, > that this can cause problems, simply because they'll be able to open > dozens of "foo" devices. However, we'll add some error checking to our first > read routine to keep from selecting invalid minor numbers. Note, once we > have a working open and close routine, we'll be able to move the error > checking there, and out of the read routine completely. so why put it there now? It's a bad habit to start.. get them used to having an open().. > This is because the > kernel does a certain amount of error checking ahead of time, and won't allow > reads or writes to be performed on not-open minor numbers (more on this later). > > One note on minor numbers. MINOR numbers are used to keep track of seperate > entities within the same device driver. For instance, for serial devices > (the sio driver), the first com port will have a minor number 0, the next, > a minor number of 1, the next, a minor number of 2, and so on. This is how > one logical device will be kept descrete from the others, even though their > major numbers are the same. For our first device driver, we'll support only > one minor device (minor number 0). usually the NFOO number can be used to select how many minors will be acceptable :) you should also mention that interpretation of the minor numbers is TOTALLY up the the driver. > > /* Define our routines in the proper format for a device-driver */ > static d_read_t fooread; It's defined this wa for teh devsw entry.. this is really part of the block that declares the devsw struct and should be kept together with that. > > One note on device drivers I've failed to touch on that suddenly has come > to me. Device driver "names", in our case, "foo", can be anywhere from 1-4 > characters. nonsence.. it can be > 4.. there is no real limit.. it just fits in better if it's kept small as it gets concatinated with things all over the place. >Most systems do not allow numbers (for reasons that will > become obvious later). Once defined, all references to that device driver > will use that name. > > ======= > /* Define our routines in the proper format for a device-driver */ > static d_read_t fooread; > > /* Define the cdewsw structure for our driver */ > static struct cdevsw foo_cdevsw = > { > nullopen, nullclose, fooread, nxwrite, > nxioctl, nxstop, nxreset, nxdevtotty, > nxselect, nxmmap, NULL, "foo", NULL, -1 > }; ====== the above is one logical code block.. > > > > The next routine we'll write is the init routine, which is responsible for > "starting up" our device driver. It can be as simple as stating the driver > is loaded, or it can be very complex. In our case, since we're doing a > pseudo-device driver, and not a regular character device driver, we won't > have probe and attach routines to do the startup work, so we'll have to do > it ourselves in the init routine. Our init routine will look something like: > > static void fooinit(void *unused) > { > dev_t dev; > printf("foo0: Foo test character device driver installed and ready.\n"); > printf("foo0: Configured for 1 device\n"); > dev = makedev(CDEV_MAJOR,0); > cdevsw_add(&dev, &foo_cdevsw, NULL); DEVFS code goes here too :) > } > > This routine basically will print out two lines of notification on boot up, > and then get the device set up in the kernel. > > The last thing we need to do is tell the kernel (from inside the driver) what > routine has to be executed in order to get everything started, as well as give > it a "priority" for loading. As an example of "priority", you can look > at the Ethernet cards defined in the LINT kernel config file. You'll note > it says not to change the order, in order to keep them from > screwing up each other's probes... This is similar. Unless you have a very > good reason to change it, I recommend to use the default priority. With this > in mind, the following default line should work 99% of the time: > > SYSINIT(foodev, SI_SUB_DRIVERS, SI_ORDER_MIDDLE+CDEV_MAJOR, &fooinit, NULL); > > What does it all do? Even I don't know ;) But, it works, so use it. the linker gathers all SYSINIT entries in all linked files into an array at boot time the entries are sorted (a bubble sort in kern_main.c from memory) and the functions pointed to are run in the order specified. this is a specific use of LINKER SETS there are others in the kernel used for other reasons. e.g adding protocol table entries etc. I THINK they are in kernel.h > #include "foo.h" > #if NFOO > 0 ^^^^^^^^^^^^^^^ this is not needed becasue if NFOO were 0 the whole foo.c file wouldn't be in the Makefile it's historical cruft.. (for hysterical raisons?) > /* Include the header files that we'll need */ > Now, thats it for the driver. The next step is to go in to > /usr/src/sys/i386/conf. I modified majors.i386 so that character device > 20 was called "foo". This was not required, but if I had other developers > working on this system, I'd want them to know I used 20 for my device > driver, and its no longer available for them to use until I'm done with it, > and change it back. > > Secondly, you'll need to edit files.i386. Add the line actually if it's not i386 specific it should be in /sys/conf/files > > dev/foo/foo.c optional foo device-driver > > someplace in the file. I usually do it about 25-30 lines down, > where the similar looking lines start. Placement in the file is not critical, > I just like to keep things looking consistent. > > Almost there. Just a couple of more things. Next, create your kernel config > file (getting real close now). Add the line: > > pseudo-device foo 1 > > someplace to the file. This will let the kernel know you need the foo driver, > and it will tell the driver it wants one device (more on this later when > we support multiple devices). >
Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?33110665.167EB0E7>