next up previous
Next: Writing ioctl Callbacks Up: FUSD: A Linux Framework Previous: Basic Device Creation

Subsections

Using Information in fusd_file_info

We mentioned in the previous sections that the first argument to every callback is a pointer to a fusd_file_info structure. This structure contains information that can be useful to driver implementers in deciding how to respond to a system call request.

The fields of fusd_file_info structures fall into several categories:

Important note: the value of the fusd_file_info pointer itself has no meaning. Repeated requests on the same file descriptor will not generate callbacks with identical fusd_file_info pointer values, as would be the case with an in-kernel driver. In other words, drivers must use data that it stores in the private_data field to provide continuity between successive system calls on a file descriptor. The pointer itself is ephemeral; the data to which it points is persistent.

Program 3 shows an example of how a driver might make use of the data in the fusd_file_info structure. Much of the driver is identical to helloworld.c. However, instead of printing a static greeting, this new program generates a custom message each time the device file is read, as seen on line 25. The message contains the PID of the user process that requested the read (file->pid).


\begin{Program}
% latex2html id marker 412\listinginput[5]{1}{uid-filter.c.exa...
... {\tt fusd\_file\_info} such
as UID and PID of the calling process}\end{Program}

In addition, Program 3's open callback does not return 0 (success) unconditionally as it did in Program 1. Instead, it checks (on line 7) to make sure the UID of the process trying to read from the device (file->uid) matches the UID under which the driver itself is running (getuid()). If they don't match, -EPERM is returned. In other words, only the user who ran the driver is allowed to read from the device that it creates. If any other user--including root!--tries to open it, a ``Permission denied'' error will be generated.

Registration of Multiple Devices, and Passing Data to Callbacks

Device drivers frequently expose several different ``flavors'' of a device. For example, a single magnetic tape drive will often have many different device files in /dev. Each device file represents a different combination of options such as rewind/no-rewind, or compressed/uncompressed. However, they access the same physical tape drive.

Traditionally, the device file's minor number was used to communicate the desired options with device drivers. But, since devfs dynamically (and unpredictably) generates both major and minor numbers every time a device is registered, a different technique was developed. When using devfs, drivers are allowed to associate a value (of type void *) with each device they register. This facility takes the place of the minor number.

The devfs solution is also used by FUSD. The mysterious third argument to fusd_register that we mentioned in Section 4.1 is an arbitrary piece of data that can be passed to FUSD when a device is registered. Later, when a callback is activated, the contents of that argument are available in the device_info member of the fusd_file_info structure.

Program 4 shows an example of this technique, inspired by Alessandro Rubini's similar devfs tutorial published in Linux Magazine. It creates a number of devices in the /dev/drums directory, each of which is useful for generating a different kind of ``sound''--/dev/drums/bam, /dev/drums/boom, and so on. Reading from any of these devices will return a string equal to the device's name.


\begin{Program}
% latex2html id marker 438\listinginput[5]{1}{drums.c.example}...
... to {\tt fusd\_register} and
retrieving it from {\tt device\_info}}\end{Program}

The first thing to notice about drums.c is that it registers more than one FUSD device. In the loop starting in line 31, it calls fusd_register() once for every device named in drums_strings on line 1. When fusd_run() is called, it automatically watches every device the driver registered, and activates the callbacks associated with each device as needed. Although drums.c uses the same set of callbacks for every device it registers (as can be seen on line 33), each device could have different callbacks if desired. (Not shown is the initialization of drums_fops, which assigns drums_read to be the read callback.)

If drums_read is called for all 6 types of drums, how does it know which device it's supposed to be servicing when it gets called? The answer is in the third argument of fusd_register(), which we were previously ignoring. Whatever value is passed to fusd_register() will be passed back to the callback in the device_info field of the fusd_file_info structure. The name of the drum sound is passed to fusd_register on line 33, and later retrieved by the driver on line 12.

Although this example uses a string as its device_info, the pointer can be used for anything--a mode number, a pointer to a configuration structure, and so on.

The difference between device_info and private_data

As we mentioned in Section 5, the fusd_file_info structure has two seemingly similar fields, both of which can be used by drivers to store their own data: device_info and private_data. However, there is an important difference between them:

In short, device_info is used to differentiate devices. private_data is used to differentiate users of those devices.

Program 5, drums2.c, illustrates the difference between device_info and private_data. Like the original drums.c, it creates a bunch of devices in /dev/drums/, each of which ``plays'' a different sound. However, it also does something new: keeps track of how many times each device has been opened. Every read to any drum gives you the name of its sound as well as your unique ``user number''. And, instead of returning just a single line (as drums.c did), it will keep generating more ``sound'' every time a read() system call arrives.


\begin{Program}
% latex2html id marker 494\listinginput[5]{1}{drums2.c.example...
...on{drums2.c: Using both {\tt device\_info} and {\tt private\_data}}\end{Program}

The trick is that we want to keep users separate from each other. For example, user one might type:

% more /dev/drums/bam
You are user 1 to hear a drum go 'bam'!
You are user 1 to hear a drum go 'bam'!
You are user 1 to hear a drum go 'bam'!
...

Meanwhile, another user in a different shell might type the same command at the same time, and get different results:

% more /dev/drums/bam
You are user 2 to hear a drum go 'bam'!
You are user 2 to hear a drum go 'bam'!
You are user 2 to hear a drum go 'bam'!
...

The idea is that no matter how long those two users go on reading their devices, the driver always generates a message that is specific to that user. The two users' data are not intermingled.

To implement this, Program 5 introduces a new drum_info structure (lines 1-4), which keeps track of both the drum's name, and the number of time each drum device has been opened. An instance of this structure, drums, is initialized on lines 4-8. Note that the call to fusd_register (line 45) now passes a pointer to a drum_info structure. (This drum_info * pointer is shared by every instance of a client that opens a particular type of drum.)

Each time a drum device is opened, its drum_info structure is retrieved from device_info (line 15). Then, on line 18, the num_users field is incremented and the new user number is stored in fusd_file_info's private_data field. To reiterate our earlier point: device_info contains information global to all users of a device, while private_data has information specific to a particular user of the device.

It's also worthwhile to note that when we increment num_users on line 18, a simple num_users++ is correct. If this was a driver inside the kernel, we'd have to use something like atomic_inc() because a plain i++ is not atomic. Such a non-atomic statement will result in a race condition on SMP platforms, if an interrupt handler also touches num_users, or in some future Linux kernel that is preemptive. Since this FUSD driver is just a plain, single-threaded user-space application, good old ++ still works.


next up previous
Next: Writing ioctl Callbacks Up: FUSD: A Linux Framework Previous: Basic Device Creation