Character Device Drivers
Initialization
Besides functions defined by the file_operations structure, there is at least one other function that you will have to write, the foo_init() function. You will have to change chr_dev_init() in drivers/char/mem.c to call your foo_init() function.foo_init() should first call register_chrdev() to register itself and avoid device number contention. register_chrdev() takes three arguments:
- int major
- This is the major number which the driver wishes to allocate.
- char *name
- This is the symbolic name of the driver. This is used, among other things, to report the driver's name in the /proc filesystem.
- struct file_operations *f_ops
- This is the address of your file_operations structure.
- Returns:
- 0 if no other character device has registered
with the same major number.
non-0 if the call fails, presumably because another character device has already allocated that major number.
Interrupts vs. Polling
In a polling driver, the foo_read() and foo_write() functions are pretty easy to write. Here is an example of foo_write():static int foo_write(struct inode * inode, struct file * file, char * buf, int count)
{
unsigned int minor = MINOR(inode->i_rdev);
char ret;
while (count > 0) {
ret = foo_write_byte(minor);
if (ret < 0) {
foo_handle_error(WRITE, ret, minor);
continue;
}
buf++ = ret; count--
}
return count;
}
foo_write_byte() and foo_handle_error() are either
functions defined elsewhere in foo.c or pseudocode. WRITE would
be a constant or #define.
It should be clear from this example how to code the
foo_read() function as well.
Interrupt-driven drivers are a little more difficult. Here is an example of a foo_write() that is interrupt-driven:
static int foo_write(struct inode * inode, struct file * file, char * buf, int count)
{
unsigned int minor = MINOR(inode->i_rdev);
unsigned long copy_size;
unsigned long total_bytes_written = 0;
unsigned long bytes_written;
struct foo_struct *foo = &foo_table[minor];
do {
copy_size = (count <= FOO_BUFFER_SIZE ? count : FOO_BUFFER_SIZE);
memcpy_fromfs(foo->foo_buffer, buf, copy_size);
while (copy_size) {
/* initiate interrupts */
if (some_error_has_occured) {
/* handle error condition */
}
current->timeout = jiffies + FOO_INTERRUPT_TIMEOUT;
/* set timeout in case an interrupt has been missed */
interruptible_sleep_on(&foo->foo_wait_queue);
bytes_written = foo->bytes_xfered;
foo->bytes_written = 0;
if (current->signal & ~current->blocked) {
if (total_bytes_written + bytes_written)
return total_bytes_written + bytes_written;
else
return -EINTR; /* nothing was written, system
call was interrupted, try again */
}
}
total_bytes_written += bytes_written;
buf += bytes_written;
count -= bytes_written;
} while (count > 0);
return total_bytes_written;
}
static void foo_interrupt(int irq)
{
struct foo_struct *foo = &foo_table[foo_irq[irq]];
/* Here, do whatever actions ought to be taken on an interrupt.
Look at a flag in foo_table to know whether you ought to be
reading or writing. */
/* Increment foo->bytes_xfered by however many characters were
read or written */
if (buffer too full/empty)
wake_up_interruptible(&foo->foo_wait_queue);
}
Again, a foo_read() function is written
analagously. foo_table[] is an array of structures,
each of which has several members, some of which are
foo_wait_queue and bytes_xfered, which can be
used for both reading and writing. foo_irq[] is an
array of 16 integers, and is used for looking up which entry in
foo_table[] is associated with the irq
generated and reported to the foo_interrupt()
function.
To tell the interrupt-handling code to call foo_interrupt(), you need to use either request_irq() or irqaction(). This is either done when foo_open() is called, or if you want to keep things simple, when foo_init() is called. request_irq() is the simpler of the two, and works rather like an old-style signal handler. It takes two arguments: the first is the number of the irq you are requesting, and the second is a pointer to your interrupt handler, which must take an integer argument (the irq that was generated) and have a return type of void. request_irq() returns -EINVAL if irq > 15 or if the pointer to the interrupt handler is NULL, -EBUSY if that interrupt has already been taken, or 0 on success.
irqaction() works rather like the user-level sigaction(), and in fact reuses the sigaction structure. The sa_restorer() field of the sigaction structure is not used, but everything else is the same. See the entry for irqaction() in Supporting Functions, for further information about irqaction().
No comments:
Post a Comment