automatically toggle a GPIO output between 0 and 1. Moreover, the user application will be able to decide the frequency of the
toggling.
First, we modify the file_operations structure by adding an IOCTL entry.
struct file_operations mygpio_Fops = {
.owner = THIS_MODULE,
.open = mygpio_open,
.release = mygpio_release,
.write = mygpio_write,
.read = mygpio_read,
.ioctl = mygpio_ioctl,
};
Here, mygpio_ioctl is our implementation of the IOCTL system call:
int mygpio_ioctl(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg){ switch(cmd) { case GPIO_START_TIMER: printk(KERN_ALERT "START TIMER.\n"); mytimer_add(inode); break; case GPIO_STOP_TIMER: printk(KERN_ALERT "STOP TIMER.\n"); mytimer_del(); break; case GPIO_SET_FREQ: printk(KERN_ALERT "SET FREQ to %lu.\n",arg); tdelay = HZ*arg/1000; break; default: printk(KERN_ALERT "Invalid IOCTL command %d.\n",cmd); return -EINVAL; } return 0; }
The symbolic names of the commands involved are defined in a separate header file ioctlnum.h . The associated values are
computed using a special 'magic number', as described in [LDD, p. 138]. We let 'x' be our magic number, since this value is not
used by any drivers listed in Documentation/ioctl-number.txt.
ioctlnum.h :
#ifndef IOCTLNUM_H_ #define IOCTLNUM_H_ #include <linux/ioctl.h> //Use 'x' as magic number. //Apparently unused by other drivers, cf. Documentation/ioctl-number.txt #define GPIO_IOC_MAGIC 'x' #define GPIO_START_TIMER _IO(GPIO_IOC_MAGIC, 1) #define GPIO_STOP_TIMER _IO(GPIO_IOC_MAGIC, 2) #define GPIO_SET_FREQ _IOW(GPIO_IOC_MAGIC, 3, int) #define GPIO_IOC_MAX 4 #endif
The implementation of the three commands GPIO_START_TIMER, GPIO_STOP_TIMER and GPIO_SET_FREQ is described
below:
GPIO_START_TIMER: Using a kernel timer, the toggling of an opened GPIO can be carried out at regular intervals. Recall
that at any moment, the current value of the internal kernel counter is stored in the read-only variable jiffies (defined
in linux/jiffies.h). A kernel timer can be configured by filling up a timer_list with 1) the jiffies value
when the timer is expected to run, 2) the function to be called when the timer goes off and 3) the arguments passed to that
function. Then, a call to add_timer adds the future event (the function call) to the list of events maintained by the
kernel, c.f. [LDD, p. 199].
int mytimer_add(struct inode *inode){ unsigned long j = jiffies; //Avoid data race on flag_timer using a Mutex. if ( !down_trylock(&timer_mutex) ) goto busy; if (flag_timer != 0) goto busy; flag_timer = 1; mytimer_list.function = mytimer_fn; mytimer_list.data = MINOR(inode->i_rdev); mytimer_list.expires = j + tdelay; add_timer(&mytimer_list); return 0; busy: return -EBUSY; }
When (approximately) tdelay HZ have elapsed, the function mytimer_fn is called by the kernel. The MINOR value of
the GPIO being toggled is passed as an argument.
void mytimer_fn(unsigned long arg){ gpio_set_value(gpio[arg].num,value); ++value; value %=2; mytimer_list.expires += tdelay; add_timer(&mytimer_list); }
After having updated the expires field of mytimer_list, a new invocation of add_timer notifies the kernel that
mytimer_fn must be called again when a delay of tdelay HZ has elapsed.
GPIO_STOP_TIMER: The user application may stop a running kernel timer by issuing the IOCTL GPIO_STOP_TIMER command.
When del_timer is invoked, the next scheduled call to mytimer_fn is canceled, thereby breaking the periodic renewal
of the kernel timer.
int mytimer_del(){ //Avoid data race on flag_timer using a Mutex. if ( !down_trylock(&timer_mutex) ) goto busy; if (flag_timer == 0) goto busy; del_timer(&mytimer_list); flag_timer = 0; return 0; busy: return -EBUSY; }
To ensure that only one kernel timer is running at the time, we use a special flag, flag_timer, which is set to 1
when an IOCTL GPIO_START_TIMER command is issued. A subsequent IOCTL GPIO_STOP_TIMER command clears the flag. Note that a
data race on flag_timer may occur when two different user threads try to initiate the GPIO toggling concurrently. To avoid
this situation, we protect mytimer_add and mytimer_del with a mutex, timer_mutex.
GPIO_SET_FREQ: The frequency with which mytimer_fn is called, after a successful IOCTL GPIO_START_TIMER
command, is determined by the value of tdelay. A delay of arg milliseconds is equivalent to HZ*arg/1000 HZs.
Links to the driver, the header and the sample application.
Ingen kommentarer:
Send en kommentar