Created
December 14, 2015 15:42
-
-
Save rday/c796e3307c4087ae3539 to your computer and use it in GitHub Desktop.
Kernel module to detect button presses and record the duration
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* Monitor GPIO for button press | |
* https://www.kernel.org/doc/Documentation/gpio/gpio.txt | |
* drivers/gpio/gpiolib.c | |
* | |
* This is a simple kernel module to detect the duration of a button press on a Raspberry Pi 2. | |
* The button press durations are stored in a kfifo. A character device can be read, and the | |
* next available button press will be sent to user space. Only one process can read from | |
* the character device at a time. | |
* | |
* The BCM2709 doesn't recognize HIGH and LOW GPIO interrupts, only RISING and FALLING. | |
* This is problematic since we will get several interrupts per button press and release. | |
* We decide to only recognize the initial RISING interrupt and FALLING interrupt. The | |
* subsequent interrupts are ignored. | |
* | |
* Todo: | |
* - In a real world scenario, an interrupt should be triggered from servo feedback or some | |
* other hardware mechanism indicating a pour, not just a button press. | |
* - We'd also want to make this much more configurable and build a proper interface from | |
* userspace. | |
* - The character device should support polling. | |
*/ | |
#include <linux/module.h> | |
#include <linux/kernel.h> | |
#include <linux/device.h> | |
#include <linux/gpio.h> | |
#include <linux/interrupt.h> | |
#include <linux/spinlock.h> | |
#include <linux/ktime.h> | |
#include <linux/fs.h> | |
#include <asm/uaccess.h> | |
#include <linux/kfifo.h> | |
#include <linux/mutex.h> | |
#define DEVICE_NAME "gpio_monitor" | |
#define CLASS_NAME "gpiomonitor" | |
static unsigned int gpio_btn = 18; | |
static unsigned int irq_number = 0; | |
static spinlock_t gpio_press_time_lock; | |
static ktime_t gpio_press_time; | |
static struct class* char_class = NULL; | |
static struct device* char_device = NULL; | |
static int major_number; | |
static struct kfifo gpio_press_fifo; | |
static DEFINE_MUTEX(gpio_monitor_mutex); | |
/* | |
* Validate the access mode of the open, and lock the device so only | |
* one process can get data at a time. | |
*/ | |
static int dev_open(struct inode *i, struct file *f) | |
{ | |
/* Our sample device does not allow write access */ | |
if ( ((f->f_flags & O_ACCMODE) == O_WRONLY) | |
|| ((f->f_flags & O_ACCMODE) == O_RDWR) ) { | |
printk(KERN_INFO "GPIO Monitor: Write access not supported\n"); | |
return -EACCES; | |
} | |
// We only allow one user at a time | |
if (!mutex_trylock(&gpio_monitor_mutex)) { | |
printk(KERN_INFO "GPIO Monitor: Device already in use\n"); | |
return -EBUSY; | |
} | |
printk(KERN_INFO "GPIO Monitor: device opened\n"); | |
return 0; | |
} | |
/* | |
* Now that the user process has released the device, others can use it. | |
*/ | |
static int dev_release(struct inode *i, struct file *f) | |
{ | |
mutex_unlock(&gpio_monitor_mutex); | |
printk(KERN_INFO "GPIO Monitor: device released\n"); | |
return 0; | |
} | |
/* | |
* We don't support writes | |
*/ | |
static ssize_t dev_write(struct file *f, const char *buf, size_t len, loff_t *off) | |
{ | |
return -EINVAL; | |
} | |
/* | |
* This is a very basic read function. We should be able to poll this device and | |
* copy more than one button press at a time, but this is just an example | |
*/ | |
static ssize_t dev_read(struct file *filep, char *buf, size_t len, loff_t *offset) | |
{ | |
int err = 0; | |
unsigned int copied; | |
if (kfifo_is_empty(&gpio_press_fifo)) { | |
return 0; | |
} | |
err = kfifo_to_user(&gpio_press_fifo, buf, sizeof(unsigned int), &copied); | |
return copied; | |
} | |
/** | |
* This handler will be called every time we detect a rise or fall on the GPIO | |
*/ | |
static irq_handler_t gpio_pressed(unsigned int irq, void *dev_id, struct pt_regs *regs){ | |
uint8_t value; | |
unsigned int duration; | |
spin_lock(&gpio_press_time_lock); | |
value = gpio_get_value(gpio_btn); | |
if (value==0) { | |
// If no time has elapsed, we probably already cleared on a FALLING | |
// interrupt. So finish the handler. | |
if (ktime_to_ms(gpio_press_time) == 0) { | |
goto finished; | |
} | |
duration = ktime_to_ms(ktime_sub(ktime_get(), gpio_press_time)); | |
if (duration==0) { | |
goto finished; | |
} | |
gpio_press_time = ktime_set(0, 0); | |
kfifo_in(&gpio_press_fifo, &duration, sizeof(unsigned int)); | |
printk(KERN_INFO "GPIO_MONITOR: Detected button release, duration of %u\n", duration); | |
} else { | |
// If a time is already set, we already received a RISING interrupt. | |
// So we can finish the handler. | |
if (ktime_to_ms(gpio_press_time) > 0) { | |
goto finished; | |
} | |
printk(KERN_INFO "GPIO_MONITOR: Detected button press\n"); | |
gpio_press_time = ktime_get(); | |
} | |
finished: | |
spin_unlock(&gpio_press_time_lock); | |
return (irq_handler_t) IRQ_HANDLED; // Announce that the IRQ has been handled correctly | |
} | |
static struct file_operations fops = | |
{ | |
.open = dev_open, | |
.read = dev_read, | |
.write = dev_write, | |
.release = dev_release, | |
}; | |
int register_chardev(void) | |
{ | |
major_number = register_chrdev(0, DEVICE_NAME, &fops); | |
if (major_number<0){ | |
printk(KERN_ALERT "GPIO Monitor: failed to register a major number\n"); | |
return major_number; | |
} | |
char_class = class_create(THIS_MODULE, CLASS_NAME); | |
if (IS_ERR(char_class)){ // Check for error and clean up if there is | |
unregister_chrdev(major_number, DEVICE_NAME); | |
printk(KERN_ALERT "Failed to register device class\n"); | |
return PTR_ERR(char_class); // Correct way to return an error on a pointer | |
} | |
char_device = device_create(char_class, NULL, MKDEV(major_number, 0), NULL, DEVICE_NAME); | |
if (IS_ERR(char_device)){ // Clean up if there is an error | |
class_destroy(char_class); // Repeated code but the alternative is goto statements | |
unregister_chrdev(major_number, DEVICE_NAME); | |
printk(KERN_ALERT "Failed to create the device\n"); | |
return PTR_ERR(char_device); | |
} | |
return 0; | |
} | |
int init_module(void) | |
{ | |
int result = 0; | |
printk(KERN_INFO "GPIO Monitor loading...\n"); | |
if (register_chardev() < 0) { | |
return -1; | |
} | |
if (kfifo_alloc(&gpio_press_fifo, 128, GFP_KERNEL)) { | |
printk(KERN_ERR "Could not allocate FIFO\n"); | |
return -ENOMEM; | |
} | |
mutex_init(&gpio_monitor_mutex); | |
printk(KERN_INFO "GPIO Monitor: Major number %d\n", major_number); | |
if (!gpio_is_valid(gpio_btn)) { | |
printk(KERN_INFO "Invalid GPIO %d\n", gpio_btn); | |
return -ENODEV; | |
} | |
gpio_direction_input(gpio_btn); | |
gpio_press_time = ktime_set(0, 0); | |
printk(KERN_INFO "GPIO Monitor loaded.\n"); | |
if (gpio_get_value(gpio_btn) == 1) { | |
printk(KERN_INFO "Initialized in middle of button press"); | |
} | |
irq_number = gpio_to_irq(gpio_btn); | |
// BCM2709 doesn't support high and low gpio interrupts, only rising and falling | |
result = request_irq(irq_number, | |
(irq_handler_t) gpio_pressed, | |
IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, | |
"gpio_pressed", | |
NULL); | |
printk(KERN_INFO "Pressed interrupt request resulted in %d\n", result); | |
if (result<0) { | |
return result; | |
} | |
return 0; | |
} | |
void cleanup_module(void) | |
{ | |
free_irq(irq_number, NULL); | |
gpio_free(gpio_btn); | |
kfifo_free(&gpio_press_fifo); | |
device_destroy(char_class, MKDEV(major_number, 0)); | |
class_unregister(char_class); | |
class_destroy(char_class); | |
unregister_chrdev(major_number, DEVICE_NAME); | |
printk(KERN_INFO "GPIO Monitor unloaded.\n"); | |
} | |
MODULE_LICENSE("GPL"); | |
/***************************** | |
// This is the user space program which will read the character | |
// device to find new button presses. | |
#include <stdio.h> | |
#include <stdlib.h> | |
#include <errno.h> | |
#include <fcntl.h> | |
#include <string.h> | |
int main(){ | |
int ret, fd; | |
unsigned int press_time; | |
fd = open("/dev/gpio_monitor", O_RDONLY); // Open the device with read/write access | |
if (fd < 0){ | |
perror("Failed to open the device..."); | |
return errno; | |
} | |
ret = read(fd, &press_time, sizeof(unsigned int)); | |
if (ret <= 0) { | |
perror("No data available for reading"); | |
return errno; | |
} | |
printf("Button press of duration: %d %d\n", press_time, ret); | |
return 0; | |
} | |
*/ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment