I have recently started learning about the Linux kernel with the help of the book 'Linux Device Drivers 3rd Edition'. I wanted to make a little home project with a raspberry pi 4 model B and a breadboard by implementing the game Simon with the help of a kernel module to turn LED lights on and off by handling button presses.
This is the code for my driver currently simon.c
-
#include "simon.h"
#include <linux/fs.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <asm/io.h>
#include <linux/interrupt.h>
#include <linux/gpio.h>
#include <linux/init.h>
dev_t devno = 0;
static struct cdev *led_dev;
static unsigned int *gpio_registers = NULL;
unsigned int irq_number;
static void gpio_set_input(unsigned int pin);
static void gpio_set_output(unsigned int pin);
static void gpio_on(unsigned int pin, enum gpio_mode);
static void gpio_off(unsigned int pin);
static int simon_open(struct inode *inode, struct file *file);
static int simon_release(struct inode *inode, struct file *file);
static ssize_t simon_read(struct file *filp, char __user *buf, size_t len,loff_t * off);
static ssize_t simon_write(struct file *filp, const char *buf, size_t len, loff_t * off);
static irqreturn_t handle_press(int irq, void *dev_id);
static irqreturn_t handle_press(int irq, void *dev_id) {
pr_info("Button was pressed\n");
return IRQ_HANDLED;
};
static void gpio_set_input(unsigned int pin) {
/*
* There are 5 function select registers on the bcm2711 which start at 0xfe200000, each one defines the operation of 10 gpio pins.
* Inside each register each pin is represented by 3 bits, which define the functionallity of the pin. 000 is input, 001 is output.
* This means that each register has 30 bits for pin functionality and 2 bits at the end that are reserved.
* To set the current pin to input, we need to set the relevant bits on the register to 000, which is input.
* For example, if we want to set pin 4 to output, it would be represented in the GPFSEL0 register and in bits 12-14.
* So we need to zero out the current bits. 7 << 3 * 4 is 111000000000000 in binary. If you NOT that value then it turns into 000111111111111.
* If we AND the GPFSEL0 register with this value then bits 12-14 will be zeroed.
*/
unsigned int gpio_fsel_register_index = pin / 10;
unsigned int fsel_bitpos = pin % 10;
unsigned int *gpio_fsel_register = gpio_registers + gpio_fsel_register_index;
/* Set all of the fsel bits of the pin to 0 - 000 is input */
*gpio_fsel_register &= ~(7 << (fsel_bitpos * 3));
pr_info("Set GPIO pin %u to INPUT\n", pin);
};
static void gpio_set_output(unsigned int pin) {
/*
* There are 5 function select registers on the bcm2711 which start at 0xfe200000, each one defines the operation of 10 gpio pins.
* Inside each register each pin is represented by 3 bits, which define the functionallity of the pin. 000 is input, 001 is output.
* This means that each register has 30 bits for pin functionality and 2 bits at the end that are reserved.
* To set the current pin to output, we bit shift 7 by fsel_bitpos * 3 times.
* For example, if we want to set pin 4 to output, it would be represented in the GPFSEL0 register and in bits 12-14.
* So first we need to zero out the current bits. 7 << 3 * 4 is 111000000000000 in binary. If you NOT that value then it turns into 000111111111111.
* If we AND the GPFSEL0 register with this value then bits 12-14 will be zeroed.
* 1 << 4 * 3 is 1000000000000
* If we OR the fsel_register with this value then bits 12-14 will be 001, which is the value for output.
*/
unsigned int gpio_fsel_register_index = pin / 10;
unsigned int fsel_bitpos = pin % 10;
unsigned int *gpio_fsel_register = gpio_registers + gpio_fsel_register_index;
/* Set all of the fsel bits of the pin to 0 */
*gpio_fsel_register &= ~(7 << (fsel_bitpos * 3));
/* Set the pin function to output */
*gpio_fsel_register |= (1 << (fsel_bitpos * 3));
};
/* Set gpio pin on */
static void gpio_on(unsigned int pin, enum gpio_mode mode) {
/*
* The register that turns the pin on is the GPSET0 register which is at offset 0x1c from the base address.
* GPSET0 controls gpio pins 0-31.
* To turn on a pin we need to set the bit of the same position to 1.
* For example to turn on pin 4, we need to set the bit at position 4 in GPSET0 to 1.
* 1 << 4 is 1000
* If we OR the GPSET0 register with this value then bit 4 will be set and the GPIO pin 4 will turn on.
*/
unsigned int *gpio_on_register = (unsigned int *)((char*)gpio_registers + GPIO_ON_REGISTER_OFFSET);
/* Set the pin correct function */
if (mode == GPIO_INPUT) {
gpio_set_input(pin);
} else {
gpio_set_output(pin);
}
/* Turn gpio pin on */
*gpio_on_register |= (1 << pin);
pr_info("Turned on GPIO pin %u\n", pin);
};
/* Set gpio pin off */
static void gpio_off(unsigned int pin) {
unsigned int *gpio_off_register = (unsigned int*)((char*)gpio_registers + GPIO_OFF_REGISTER_OFFSET);
*gpio_off_register |= (1 << pin);
};
static int simon_open(struct inode *inode, struct file *file) {
return 0;
};
static int simon_release(struct inode *inode, struct file *file) {
return 0;
};
static ssize_t simon_read(struct file *filp, char __user *buf, size_t len,loff_t * off) {
return len;
};
static ssize_t simon_write(struct file *filp, const char *buf, size_t len, loff_t * off) {
char input[MAX_USER_INPUT_LEN];
unsigned action, gpio_pin;
if (len > MAX_USER_INPUT_LEN) {
pr_err("Too much data to be written\n");
return -EINVAL;
}
if (copy_from_user(input, buf, len)) {
pr_err("Failed to read from user buffer\n");
return -EFAULT;
}
if (sscanf(input, "%d,%d", &gpio_pin, &action) != 2 ){
pr_err("Invalid command format\n");
return -EINVAL;
}
if (action != 0 && action != 1) {
pr_err("Invalid action - %d. 0 for off and 1 for On.\n", action);
return -EINVAL;
}
if (gpio_pin < 0 || gpio_pin > 21) {
pr_err("Invalid GPIO pin - %d\n", gpio_pin);
return -EINVAL;
}
switch (action) {
case ON_ACTION:
gpio_on(gpio_pin, GPIO_OUTPUT);
break;
case OFF_ACTION:
gpio_off(gpio_pin);
break;
}
return len;
};
static struct file_operations led_fops =
{
.owner = THIS_MODULE,
.read = simon_read,
.write = simon_write,
.open = simon_open,
.release = simon_release,
};
static int simon_setup_cdev(dev_t *devno, struct cdev *cdev) {
int retval = 0;
if (alloc_chrdev_region(devno, 0, 1, "simon") < 0) {
pr_err("Cannot allocate device number\n");
return -EFAULT;
}
led_dev = kmalloc(sizeof(struct cdev), GFP_KERNEL);
if (led_dev == NULL) {
retval = -ENOMEM;
goto out;
};
memset(led_dev, 0, sizeof(struct cdev));
cdev_init(led_dev, &led_fops);
if (cdev_add(led_dev, *devno, 1) < 0) {
pr_err("Failed to add char device, exiting\n");
retval = EFAULT;
goto out;
}
out:
return retval;
}
static void simon_cleanup(void) {
unregister_chrdev_region(devno, 1);
if (led_dev != NULL) {
cdev_del(led_dev);
kfree(led_dev);
}
if (gpio_registers != NULL) {
iounmap(gpio_registers);
}
free_irq(irq_number, NULL);
pr_info("Unregistered device %d-%d\n", MAJOR(devno), MINOR(devno));
}
static int __init simon_init(void) {
int retval = 0;
gpio_registers = (int *)ioremap(BCM2711_GPIO_BASE_ADDRESS, PAGE_SIZE);
if (gpio_registers == NULL) {
retval = -EFAULT;
goto error;
}
irq_number = gpio_to_irq(16);
gpio_on(16, GPIO_INPUT);
retval = request_irq(irq_number, handle_press, IRQF_TRIGGER_RISING, "simon_handler", NULL);
if (retval) {
pr_err("Failed to register IRQ handler\n");
}
retval = simon_setup_cdev(&devno, led_dev);
if (retval) {
goto error;
}
pr_info("Registered device %d-%d\n", MAJOR(devno), MINOR(devno));
return retval;
error:
simon_cleanup();
return retval;
};
module_init(simon_init);
module_exit(simon_cleanup);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("*********");
MODULE_DESCRIPTION("Simple Linux driver to control LEDs and buttons through GPIO pins");
MODULE_VERSION("1.0");
The code for simon.h
-
#define MAX_USER_INPUT_LEN 4
#define OFF_ACTION 0
#define ON_ACTION 1
#define BCM2711_GPIO_BASE_ADDRESS 0xfe200000
#define GPIO_ON_REGISTER_OFFSET 0x1c
#define GPIO_OFF_REGISTER_OFFSET 0x28
enum gpio_mode {
GPIO_INPUT,
GPIO_OUTPUT,
};
The current behavior is the following -
I load the driver and create a char device file in /dev/
. When the device file is written to, it expects the following format - ',<off(0)/on(1)>'. For example, writing '16,1' to the file will result in GPIO pin 16 to be set to output and turned on. Writing '16,0' will turn GPIO pin 16 off.
As you can see in the simon_init()
function, I am hardcoding GPIO pin 16 and setting it as input, and registering an IRQ handler for the corresponding interrupt number.
I had connected a button to pin 16 and when I press the button, 'Button was pressed' is logged. When I look at /proc/interrupts
, I see the following -
70: 12 0 0 0 pinctrl-bcm2835 16 Edge simon_handler
Meaning the IRQ number is 70, even though I didn't see an IRQ number 70 in the '6.2.4. VideoCore interrupts' section of the BCM2711 datasheet.
My question is, how can I implement the function gpio_to_irq
as in simon_init()
?
I have tried reading the BCM2711 datasheet, specifically the section about GPIOs and interrupts, but I'm pretty new to this and couldn't really understand what gpio_to_irq
does under the hood. From the behavior, I could guess that it somehow dynamically allocates an IRQ number and assigns it to pin 16, but that is just a guess. I had also tried looking a bit in the kernel source code but couldn't really make sense of things there either.
If anyone could give some insight as to what goes on behind this function, or perhaps give some useful referrals to any documentation that does, I would appreciate immensely. Thanks!