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