We slightly modify the device driver described in the previous post. We demonstrate how to trigger an interrupt
when the value of a GPIO port changes. As a consequence, any read() operation on one of the GPIOs will block
until new data is ready to be read.
/*
In the following program, we implement an interrupt driven device driver as described in [LDD, Chapter 3,6,10].
First, connect GPIO 130 and GPIO 134 on the devkit add-on board using the diagrams found at
http://devkit8000addon.wikispaces.com/Interfaces and http://devkit8000addon.wikispaces.com/PCB.
In VMware, open 3 terminals.
In terminal 1, establish a connection to devkit using
> ssh root@10.9.8.2
In terminal 2, do the same
> ssh root@10.9.8.2
In terminal 3, compile the module and transfer it to devkit
> cat > Makefile
obj-m := mygpio_intDOC.o
(CTRL-C)
> make -C /home/stud/source/linux-2.6.28-omap/ M=`pwd` modules
> scp mygpio_intDOC.ko root@10.9.8.2:/home/root/
Back to terminal 1, insert the module into the kernel and check that no error occurred
> insmod mygpio_intDOC.ko
> dmesg
Create two nodes, one for input, GPIO 130, and one for output, GPIO 134.
> mknod gpio0 c 62 0
> mknod gpio4 c 62 4
In terminal 2, read data from GPIO 130 using
> cat /dev/gpio0
In terminal 1, set GPIO 134 to 0 or 1 (low/high) using
> echo 0 > /dev/gpio4
> echo 1 > /dev/gpio4
> echo 0 > /dev/gpio4
> echo 1 > /dev/gpio4
> echo 1 > /dev/gpio4
> echo 0 > /dev/gpio4
> etc.
In terminal 2, the new value on GPIO 130/134 is written to the screen each time the value changes from 0
to 1 or from 1 to 0. In other terms, two successive 'echo 1 > /dev/gpio4' will not trigger the interrupt.
Finally, remove the module from the kernel by writing in terminal 1 or 2
> rmmod mygpio_intDOC.ko
> dmesg
NB: In terminal 2, 'cat /dev/gpio0' must be aborted using CTRL-C before the module is removed from the
kernel. Otherwise, rmmod will fail (check with dmesg).
*/
/*
Bibliography:
[LDD] Linux Device Drivers, 3rd Ed.
*/
#include <linux/gpio.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <asm/uaccess.h>
#include <linux/types.h>
#include <linux/interrupt.h>
#define MYGPIO_MAJOR 62
#define MYGPIO_MINOR 0
#define MYGPIO_CHS 8
#define MAXLEN 512
#define MYGPIO_MIN_IN 0
#define MYGPIO_MAX_IN 3
#define MYGPIO_MIN_OUT 4
#define MYGPIO_MAX_OUT 7
static struct cdev MyGpioDev;
struct file_operations mygpio_Fops;
static int devno;
struct gpioPort{
unsigned int num;
enum direnu { in, out } dir;
const char label[10]; // Very important to specify a size.
} gpio[] = {
{130, in, "gpio0_0"}, //Read only
{131, in, "gpio0_1"}, //Read only
{132, in, "gpio0_2"}, //Read only
{133, in, "gpio0_3"}, //Read only
{134, out, "gpio0_4"}, //Write only
{135, out, "gpio0_5"}, //Write only
{136, out, "gpio0_6"}, //Write only
{137, out, "gpio0_7"} //Write only
};
/*
We use this structure to keep track of which GPIO ports are associated to a corresponding interrupt
number.
*/
int gpio_irq[MYGPIO_CHS] = {-1};
/* Define and initialize a structure wq of type wait_queue_head_t, c.f. [LDD, p. 149]
*/
static DECLARE_WAIT_QUEUE_HEAD(wq);
static int flag = 0;
/*****************************************************************/
/************************** IRQ HANDLER **************************/
/*****************************************************************/
static irqreturn_t irqHandler(int irq, void *dev_id, struct pt_regs *regs){
printk(KERN_ALERT "IRQ handler.\n");
flag=1;
/*In this driver module, all read() operations on input GPIOs will block and the calling process will be
asked to wait in the wait queue, wq. Whenever the value of an input GPIO changes (from 0 to 1 or from 1
to 0), sleeping processes must be notified using wake_up_interruptible(), c.f. [LDD, p.149-150].
*/
wake_up_interruptible(&wq);
/*
When a sleeping process wakes up, it checks that the value of 'flag' is equal to 1. If flag==1, the
process sets the flag back to 0 and reads the value present on the input GPIO. Otherwise, the process
must sleep some more until wake_up_interruptible() is called again. In this module, a data race on
'flag' may occur, c.f. [LDD, p.149-150].
*/
return IRQ_HANDLED;
}
/*****************************************************************/
/*************************** INIT ********************************/
/*****************************************************************/
static int __init mygpio_init(void)
{
int err, i;
printk(KERN_ALERT "Mygpio Module Inserted\n");
devno = MKDEV(MYGPIO_MAJOR, MYGPIO_MINOR);
if((err=register_chrdev_region(devno, MYGPIO_CHS , "myGpio"))<0){
printk(KERN_ALERT "Can't Register Major no: %i\n", MYGPIO_MAJOR);
return err;
}
cdev_init(&MyGpioDev, &mygpio_Fops);
MyGpioDev.owner = THIS_MODULE;
for(i=0; i< MYGPIO_CHS; ++i) {
if ( (err=gpio_request(gpio[i].num, gpio[i].label))<0){
printk(KERN_ALERT "Error %d requesting GPIO %d.\n",err,i);
gpio_free(gpio[i].num);
goto fail_init;
}
}
for(i=0; i< MYGPIO_CHS; ++i) {
if ( (gpio[i].dir == in) && (err=gpio_direction_input(gpio[i].num))<0 ) {
printk(KERN_ALERT "Error %d marking direction on GPIO %d.\n",err,i);
goto fail_dir;
}
else if ( (gpio[i].dir == out) && !(err=gpio_direction_output(gpio[i].num,0))<0 ){
printk(KERN_ALERT "Error %d marking direction on GPIO %d.\n",err,i);
goto fail_dir;
}
}
err = cdev_add(&MyGpioDev, devno, MYGPIO_CHS );
if (err) {
printk (KERN_ALERT "Error %d adding MyGpio device\n", err);
goto fail_add;
}
//No errors..
return 0;
fail_add:
fail_dir:
for(i=0; i< MYGPIO_CHS; ++i) {
gpio_free(gpio[i].num);
}
fail_init:
cdev_del(&MyGpioDev);
unregister_chrdev_region(devno, MYGPIO_CHS );
return err;
}
/*****************************************************************/
/*************************** EXIT ********************************/
/*****************************************************************/
static void mygpio_exit(void)
{
int err,i;
int irq;
printk(KERN_ALERT "Removing Mygpio Module\n");
for(i=0; i < MYGPIO_CHS; ++i) {
if ( (err=gpio_direction_input(gpio[i].num)) <0 ) {
printk(KERN_ALERT "Error %d reseting GPIO %d.\n",err,i);
}
}
for(i=0; i< MYGPIO_CHS; ++i) {
gpio_free(gpio[i].num);
}
/*
The requested IRQs are freed when the module is removed from the kernel.
*/
for (i=0; i<MYGPIO_CHS; ++i) {
irq=gpio_irq[i];
if (irq > 0) {
printk(KERN_ALERT "Free IRQ %i.\n",irq);
free_irq(irq, NULL);
gpio_irq[i]=-1;
}
}
cdev_del(&MyGpioDev);
unregister_chrdev_region(devno, MYGPIO_CHS );
}
/*****************************************************************/
/*************************** OPEN ********************************/
/*****************************************************************/
int mygpio_open(struct inode *inode, struct file *filep)
{
int major, minor;
int irq;
major = MAJOR(inode->i_rdev);
minor = MINOR(inode->i_rdev);
printk("Opening MyGpio Device [major], [minor]: %i, %i\n", major, minor);
if ( major != MYGPIO_MAJOR ) {
printk(KERN_ALERT "Invalid major device number.\n");
return -EFAULT;
}
if ( minor < MYGPIO_MINOR || minor >= MYGPIO_MINOR + MYGPIO_CHS ) {
printk(KERN_ALERT "Invalid minor device number.\n");
return -EFAULT;
}
/* If gpio_irq[minor] is not equal to -1, the IRQ has already been allocated to this GPIO number.
*/
if (gpio_irq[minor] > 0)
goto irq_ok;
/*A GPIO number can be mapped to an IRQ using gpio_to_irq()
*/
if ( (irq = gpio_to_irq(gpio[minor].num))< 0 )
goto irq_fail;
/* The interrupt is requested using request_irq(), c.f. [LDD, p. 261]. Whenever the GPIO goes from low
to high (IRQF_TRIGGER_RISING) or from high to low (IRQF_TRIGGER_FALLING), the corresponding interrupt is
triggered and irqHandler is called.
*/
if( request_irq(irq, irqHandler, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING , "GPIO", NULL) )
goto irq_fail;
gpio_irq[minor] = irq;
printk(KERN_ALERT "IRQ request succedeed.\n");
irq_ok:
if (!try_module_get(mygpio_Fops.owner))
return -ENODEV;
return 0;
irq_fail:
printk(KERN_ALERT "IRQ request failed.\n");
return -EFAULT;
}
/*****************************************************************/
/*************************** RELEASE *****************************/
/*****************************************************************/
int mygpio_release(struct inode *inode, struct file *filep)
{
int minor, major;
major = MAJOR(inode->i_rdev);
minor = MINOR(inode->i_rdev);
printk("Closing MyGpio Device [major], [minor]: %i, %i\n", major, minor);
if ( major != MYGPIO_MAJOR ) {
printk(KERN_ALERT "Invalid major device number.\n");
return -EFAULT;
}
if ( minor < MYGPIO_MINOR || minor >= MYGPIO_MINOR + MYGPIO_CHS ) {
printk(KERN_ALERT "Invalid minor device number.\n");
return -EFAULT;
}
module_put(mygpio_Fops.owner);
return 0;
}
/*****************************************************************/
/*************************** WRITE *******************************/
/*****************************************************************/
ssize_t mygpio_write(struct file *filep, const char __user *ubuf,
size_t count, loff_t *f_pos)
{
int err = 0;
int minor, len, value;
char kbuf[MAXLEN];
minor = MINOR(filep->f_dentry->d_inode->i_rdev);
printk(KERN_ALERT "Writing to MyGpio [Minor] %i \n", minor);
if ( minor < MYGPIO_MIN_OUT || minor > MYGPIO_MAX_OUT) {
printk(KERN_ALERT "Invalid minor device number.\n");
return -EFAULT;
}
len = count < MAXLEN ? count : MAXLEN;
err = copy_from_user(kbuf, ubuf, len);
if (err != 0) {
printk(KERN_ALERT "copy_from_user failed with error %d.\n",err);
goto fail;
}
kbuf[len] = '\0';
printk("String from user: %s\n", kbuf);
sscanf(kbuf,"%i", &value);
value = value > 0 ? 1 : 0;
gpio_set_value(gpio[minor].num,value);
return len;
fail:
return -EFAULT;
}
/*****************************************************************/
/*************************** READ *******************************/
/*****************************************************************/
ssize_t mygpio_read(struct file *filep, char __user *buf,
size_t count, loff_t *f_pos)
{
char readBuf[MAXLEN];
int len, result=-1, minor;
int err;
minor = MINOR(filep->f_dentry->d_inode->i_rdev);
printk(KERN_ALERT "Reading from MyGpio [Minor] %i \n", minor);
if ( minor < MYGPIO_MIN_IN || minor > MYGPIO_MAX_IN ) {
printk(KERN_ALERT "Invalid minor device number.\n");
return -EFAULT;
}
/*
Wait until a write() action triggers an interrupt, c.f. irqHandler() defined above.
*/
wait_event_interruptible(wq,flag != 0);
flag = 0;
result = gpio_get_value(gpio[minor].num);
len=sprintf(readBuf,"%i", result);
len++;
err = copy_to_user(buf, readBuf, len);
if (err != 0) {
printk(KERN_ALERT "copy_to_user failed with error %d.\n",err);
goto fail;
}
return len;
fail:
return -EFAULT;
}
struct file_operations mygpio_Fops =
{
.owner = THIS_MODULE,
.open = mygpio_open,
.release = mygpio_release,
.write = mygpio_write,
.read = mygpio_read,
};
module_init(mygpio_init); //... is invoked when the module is loaded into the kernel (insmod)
module_exit(mygpio_exit); //... is invoked when the module is removed from the kernel (rmmod)
MODULE_DESCRIPTION("My GPIO Module");
MODULE_AUTHOR("Jérémy");
MODULE_LICENSE("GPL");
Ingen kommentarer:
Send en kommentar