mirror of
https://github.com/bashrc/LKMPG.git
synced 2018-06-11 03:06:54 +02:00
Working interrupt example
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -2982,132 +2982,167 @@ Under Linux, hardware interrupts are called IRQ's (InterruptRe quests)[fn:16]. T
|
||||
|
||||
When the CPU receives an interrupt, it stops whatever it's doing (unless it's processing a more important interrupt, in which case it will deal with this one only when the more important one is done), saves certain parameters on the stack and calls the interrupt handler. This means that certain things are not allowed in the interrupt handler itself, because the system is in an unknown state. The solution to this problem is for the interrupt handler to do what needs to be done immediately, usually read something from the hardware or send something to the hardware, and then schedule the handling of the new information at a later time (this is called the "bottom half") and return. The kernel is then guaranteed to call the bottom half as soon as possible -- and when it does, everything allowed in kernel modules will be allowed.
|
||||
|
||||
The way to implement this is to call request_irq() to get your interrupt handler called when the relevant IRQ is received. [fn:17]This function receives the IRQ number, the name of the function, flags, a name for /proc/interrupts and a parameter to pass to the interrupt handler. Usually there is a certain number of IRQs available. How many IRQs there are is hardware-dependent. The flags can include SA_SHIRQ to indicate you're willing to share the IRQ with other interrupt handlers (usually because a number of hardware devices sit on the same IRQ) and SA_INTERRUPT to indicate this is a fast interrupt. This function will only succeed if there isn't already a handler on this IRQ, or if you're both willing to share.
|
||||
The way to implement this is to call *request_irq()* to get your interrupt handler called when the relevant IRQ is received. [fn:17]This function receives the IRQ number, the name of the function, flags, a name for /proc/interrupts and a parameter to pass to the interrupt handler. Usually there is a certain number of IRQs available. How many IRQs there are is hardware-dependent. The flags can include SA_SHIRQ to indicate you're willing to share the IRQ with other interrupt handlers (usually because a number of hardware devices sit on the same IRQ) and SA_INTERRUPT to indicate this is a fast interrupt. This function will only succeed if there isn't already a handler on this IRQ, or if you're both willing to share.
|
||||
|
||||
Then, from within the interrupt handler, we communicate with the hardware and then use queue_work() mark_bh(BH_IMMEDIATE) to schedule the bottom half.
|
||||
|
||||
** Keyboards on the Intel Architecture
|
||||
The rest of this chapter is completely Intel specific. If you're not running on an Intel platform, it will not work. Don't even try to compile the code here. Move right along, nothing to see.
|
||||
** Detecting button presses
|
||||
Many popular single board computers, such as Raspberry Pis or Beagleboards, have a bunch of GPIO pins. Attaching buttons to those and then having a button press do something is a classic case in which you might need to use interrupts so that instead of having the CPU waste time and battery power polling for a change in input state it's better for the input to trigger the CPU to then run a particular handling function.
|
||||
|
||||
I had a problem with writing the sample code for this chapter. On one hand, for an example to be useful it has to run on everybody's computer with meaningful results. On the other hand, the kernel already includes device drivers for all of the common devices, and those device drivers won't coexist with what I'm going to write. The solution I've found was to write something for the keyboard interrupt, and disable the regular keyboard interrupt handler first. Since it is defined as a static symbol in the kernel source files (specifically, drivers/char/keyboard.c), there is no way to restore it. Before insmod'ing this code, do on another terminal sleep 120; reboot if you value your file system.
|
||||
|
||||
This code binds itself to IRQ 1, which is the IRQ of the keyboard controlled under Intel architectures. Then, when it receives a keyboard interrupt, it reads the keyboard's status (that's the purpose of the inb(0x64)) and the scan code, which is the value returned by the keyboard. Then, as soon as the kernel thinks it's feasible, it runs got_char which gives the code of the key
|
||||
used (the first seven bits of the scan code) and whether it has been pressed (if the 8th bit is zero) or released (if it's one).
|
||||
Here's an example where buttons are connected to GPIO numbers 17 and 18 and an LED is connected to GPIO 4. You can change those numbers to whatever is appropriate for your board.
|
||||
|
||||
#+BEGIN_SRC c :file intrpt.c
|
||||
/*
|
||||
* intrpt.c - An interrupt handler.
|
||||
* intrpt.c - Handling GPIO with interrupts
|
||||
*
|
||||
* Copyright (C) 2001 by Peter Jay Salzman
|
||||
* Copyright (C) 2017 by Bob Mottram
|
||||
* Based upon the Rpi example by Stefan Wendler (devnull@kaltpost.de)
|
||||
* from:
|
||||
* https://github.com/wendlers/rpi-kmod-samples
|
||||
*
|
||||
* Press one button to turn on a LED and another to turn it off
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/gpio.h>
|
||||
#include <linux/interrupt.h>
|
||||
|
||||
static int button_irqs[] = { -1, -1 };
|
||||
|
||||
/* Define GPIOs for LEDs.
|
||||
Change the numbers for the GPIO on your board. */
|
||||
static struct gpio leds[] = {
|
||||
{ 4, GPIOF_OUT_INIT_LOW, "LED 1" }
|
||||
};
|
||||
|
||||
/* Define GPIOs for BUTTONS
|
||||
Change the numbers for the GPIO on your board. */
|
||||
static struct gpio buttons[] = {
|
||||
{ 17, GPIOF_IN, "LED 1 ON BUTTON" },
|
||||
{ 18, GPIOF_IN, "LED 1 OFF BUTTON" }
|
||||
};
|
||||
|
||||
/*
|
||||
* The necessary header files
|
||||
* interrupt function triggered when a button is pressed
|
||||
*/
|
||||
|
||||
/*
|
||||
* Standard in kernel modules
|
||||
*/
|
||||
#include <linux/kernel.h> /* We're doing kernel work */
|
||||
#include <linux/module.h> /* Specifically, a module */
|
||||
#include <linux/sched.h>
|
||||
#include <linux/workqueue.h>
|
||||
#include <linux/interrupt.h> /* We want an interrupt */
|
||||
#include <asm/io.h>
|
||||
|
||||
#define MY_WORK_QUEUE_NAME "WQsched.c"
|
||||
|
||||
static struct workqueue_struct *my_workqueue;
|
||||
|
||||
/*
|
||||
* This will get called by the kernel as soon as it's safe
|
||||
* to do everything normally allowed by kernel modules.
|
||||
*/
|
||||
static void got_char(void *scancode)
|
||||
static irqreturn_t button_isr(int irq, void *data)
|
||||
{
|
||||
printk(KERN_INFO "Scan Code %x %s.\n",
|
||||
(int)*((char *)scancode) & 0x7F,
|
||||
*((char *)scancode) & 0x80 ? "Released" : "Pressed");
|
||||
}
|
||||
|
||||
/*
|
||||
* This function services keyboard interrupts. It reads the relevant
|
||||
* information from the keyboard and then puts the non time critical
|
||||
* part into the work queue. This will be run when the kernel considers it safe.
|
||||
*/
|
||||
irqreturn_t irq_handler(int irq, void *dev_id, struct pt_regs *regs)
|
||||
{
|
||||
/*
|
||||
* This variables are static because they need to be
|
||||
* accessible (through pointers) to the bottom half routine.
|
||||
*/
|
||||
static int initialised = 0;
|
||||
static unsigned char scancode;
|
||||
static struct work_struct task;
|
||||
unsigned char status;
|
||||
|
||||
/*
|
||||
* Read keyboard status
|
||||
*/
|
||||
status = inb(0x64);
|
||||
scancode = inb(0x60);
|
||||
|
||||
if (initialised == 0) {
|
||||
INIT_WORK(&task, got_char, &scancode);
|
||||
initialised = 1;
|
||||
} else {
|
||||
PREPARE_WORK(&task, got_char, &scancode);
|
||||
}
|
||||
|
||||
queue_work(my_workqueue, &task);
|
||||
/* first button */
|
||||
if (irq == button_irqs[0] && !gpio_get_value(leds[0].gpio))
|
||||
gpio_set_value(leds[0].gpio, 1);
|
||||
/* second button */
|
||||
else if(irq == button_irqs[1] && gpio_get_value(leds[0].gpio))
|
||||
gpio_set_value(leds[0].gpio, 0);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
/*
|
||||
* Initialize the module - register the IRQ handler
|
||||
*/
|
||||
int init_module()
|
||||
{
|
||||
my_workqueue = create_workqueue(MY_WORK_QUEUE_NAME);
|
||||
int ret = 0;
|
||||
|
||||
/*
|
||||
* Since the keyboard handler won't co-exist with another handler,
|
||||
* such as us, we have to disable it (free its IRQ) before we do
|
||||
* anything. Since we don't know where it is, there's no way to
|
||||
* reinstate it later - so the computer will have to be rebooted
|
||||
* when we're done.
|
||||
*/
|
||||
free_irq(1, NULL);
|
||||
printk(KERN_INFO "%s\n", __func__);
|
||||
|
||||
/*
|
||||
* Request IRQ 1, the keyboard IRQ, to go to our irq_handler.
|
||||
* SA_SHIRQ means we're willing to have othe handlers on this IRQ.
|
||||
* SA_INTERRUPT can be used to make the handler into a fast interrupt.
|
||||
*/
|
||||
return request_irq(1, /* The number of the keyboard IRQ on PCs */
|
||||
irq_handler, /* our handler */
|
||||
SA_SHIRQ, "test_keyboard_irq_handler",
|
||||
(void *)(irq_handler));
|
||||
/* register LED gpios */
|
||||
ret = gpio_request_array(leds, ARRAY_SIZE(leds));
|
||||
|
||||
if (ret) {
|
||||
printk(KERN_ERR "Unable to request GPIOs for LEDs: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* register BUTTON gpios */
|
||||
ret = gpio_request_array(buttons, ARRAY_SIZE(buttons));
|
||||
|
||||
if (ret) {
|
||||
printk(KERN_ERR "Unable to request GPIOs for BUTTONs: %d\n", ret);
|
||||
goto fail1;
|
||||
}
|
||||
|
||||
printk(KERN_INFO "Current button1 value: %d\n",
|
||||
gpio_get_value(buttons[0].gpio));
|
||||
|
||||
ret = gpio_to_irq(buttons[0].gpio);
|
||||
|
||||
if (ret < 0) {
|
||||
printk(KERN_ERR "Unable to request IRQ: %d\n", ret);
|
||||
goto fail2;
|
||||
}
|
||||
|
||||
button_irqs[0] = ret;
|
||||
|
||||
printk(KERN_INFO "Successfully requested BUTTON1 IRQ # %d\n",
|
||||
button_irqs[0]);
|
||||
|
||||
ret = request_irq(button_irqs[0], button_isr,
|
||||
IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
|
||||
"gpiomod#button1", NULL);
|
||||
|
||||
if (ret) {
|
||||
printk(KERN_ERR "Unable to request IRQ: %d\n", ret);
|
||||
goto fail2;
|
||||
}
|
||||
|
||||
|
||||
ret = gpio_to_irq(buttons[1].gpio);
|
||||
|
||||
if (ret < 0) {
|
||||
printk(KERN_ERR "Unable to request IRQ: %d\n", ret);
|
||||
goto fail2;
|
||||
}
|
||||
|
||||
button_irqs[1] = ret;
|
||||
|
||||
printk(KERN_INFO "Successfully requested BUTTON2 IRQ # %d\n",
|
||||
button_irqs[1]);
|
||||
|
||||
ret = request_irq(button_irqs[1], button_isr,
|
||||
IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
|
||||
"gpiomod#button2", NULL);
|
||||
|
||||
if (ret) {
|
||||
printk(KERN_ERR "Unable to request IRQ: %d\n", ret);
|
||||
goto fail3;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
/* cleanup what has been setup so far */
|
||||
fail3:
|
||||
free_irq(button_irqs[0], NULL);
|
||||
|
||||
fail2:
|
||||
gpio_free_array(buttons, ARRAY_SIZE(leds));
|
||||
|
||||
fail1:
|
||||
gpio_free_array(leds, ARRAY_SIZE(leds));
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Cleanup
|
||||
*/
|
||||
void cleanup_module()
|
||||
{
|
||||
/*
|
||||
* This is only here for completeness. It's totally irrelevant, since
|
||||
* we don't have a way to restore the normal keyboard interrupt so the
|
||||
* computer is completely useless and has to be rebooted.
|
||||
*/
|
||||
free_irq(1, NULL);
|
||||
int i;
|
||||
|
||||
printk(KERN_INFO "%s\n", __func__);
|
||||
|
||||
/* free irqs */
|
||||
free_irq(button_irqs[0], NULL);
|
||||
free_irq(button_irqs[1], NULL);
|
||||
|
||||
/* turn all LEDs off */
|
||||
for (i = 0; i < ARRAY_SIZE(leds); i++)
|
||||
gpio_set_value(leds[i].gpio, 0);
|
||||
|
||||
/* unregister */
|
||||
gpio_free_array(leds, ARRAY_SIZE(leds));
|
||||
gpio_free_array(buttons, ARRAY_SIZE(buttons));
|
||||
}
|
||||
|
||||
/*
|
||||
* some work_queue related functions are just available to GPL licensed Modules
|
||||
*/
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_AUTHOR("Bob Mottram");
|
||||
MODULE_DESCRIPTION("Handle some GPIO interrupts");
|
||||
#+END_SRC
|
||||
|
||||
* Common Pitfalls
|
||||
|
||||
@@ -17,6 +17,7 @@ obj-m += kbleds.o
|
||||
obj-m += sched.o
|
||||
obj-m += chardev2.o
|
||||
obj-m += syscall.o
|
||||
obj-m += intrpt.o
|
||||
|
||||
all:
|
||||
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
|
||||
|
||||
@@ -1,113 +1,151 @@
|
||||
/*
|
||||
* intrpt.c - An interrupt handler.
|
||||
* intrpt.c - Handling GPIO with interrupts
|
||||
*
|
||||
* Copyright (C) 2001 by Peter Jay Salzman
|
||||
* Copyright (C) 2017 by Bob Mottram
|
||||
* Based upon the Rpi example by Stefan Wendler (devnull@kaltpost.de)
|
||||
* from:
|
||||
* https://github.com/wendlers/rpi-kmod-samples
|
||||
*
|
||||
* Press one button to turn on a LED and another to turn it off
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/gpio.h>
|
||||
#include <linux/interrupt.h>
|
||||
|
||||
static int button_irqs[] = { -1, -1 };
|
||||
|
||||
/* Define GPIOs for LEDs.
|
||||
Change the numbers for the GPIO on your board. */
|
||||
static struct gpio leds[] = {
|
||||
{ 4, GPIOF_OUT_INIT_LOW, "LED 1" }
|
||||
};
|
||||
|
||||
/* Define GPIOs for BUTTONS
|
||||
Change the numbers for the GPIO on your board. */
|
||||
static struct gpio buttons[] = {
|
||||
{ 17, GPIOF_IN, "LED 1 ON BUTTON" },
|
||||
{ 18, GPIOF_IN, "LED 1 OFF BUTTON" }
|
||||
};
|
||||
|
||||
/*
|
||||
* The necessary header files
|
||||
* interrupt function triggered when a button is pressed
|
||||
*/
|
||||
|
||||
/*
|
||||
* Standard in kernel modules
|
||||
*/
|
||||
#include <linux/kernel.h> /* We're doing kernel work */
|
||||
#include <linux/module.h> /* Specifically, a module */
|
||||
#include <linux/sched.h>
|
||||
#include <linux/workqueue.h>
|
||||
#include <linux/interrupt.h> /* We want an interrupt */
|
||||
#include <asm/io.h>
|
||||
|
||||
#define MY_WORK_QUEUE_NAME "WQsched.c"
|
||||
|
||||
static struct workqueue_struct *my_workqueue;
|
||||
|
||||
/*
|
||||
* This will get called by the kernel as soon as it's safe
|
||||
* to do everything normally allowed by kernel modules.
|
||||
*/
|
||||
static void got_char(void *scancode)
|
||||
static irqreturn_t button_isr(int irq, void *data)
|
||||
{
|
||||
printk(KERN_INFO "Scan Code %x %s.\n",
|
||||
(int)*((char *)scancode) & 0x7F,
|
||||
*((char *)scancode) & 0x80 ? "Released" : "Pressed");
|
||||
}
|
||||
|
||||
/*
|
||||
* This function services keyboard interrupts. It reads the relevant
|
||||
* information from the keyboard and then puts the non time critical
|
||||
* part into the work queue. This will be run when the kernel considers it safe.
|
||||
*/
|
||||
irqreturn_t irq_handler(int irq, void *dev_id, struct pt_regs *regs)
|
||||
{
|
||||
/*
|
||||
* This variables are static because they need to be
|
||||
* accessible (through pointers) to the bottom half routine.
|
||||
*/
|
||||
static int initialised = 0;
|
||||
static unsigned char scancode;
|
||||
static struct work_struct task;
|
||||
unsigned char status;
|
||||
|
||||
/*
|
||||
* Read keyboard status
|
||||
*/
|
||||
status = inb(0x64);
|
||||
scancode = inb(0x60);
|
||||
|
||||
if (initialised == 0) {
|
||||
INIT_WORK(&task, got_char, &scancode);
|
||||
initialised = 1;
|
||||
} else {
|
||||
PREPARE_WORK(&task, got_char, &scancode);
|
||||
}
|
||||
|
||||
queue_work(my_workqueue, &task);
|
||||
/* first button */
|
||||
if (irq == button_irqs[0] && !gpio_get_value(leds[0].gpio))
|
||||
gpio_set_value(leds[0].gpio, 1);
|
||||
/* second button */
|
||||
else if(irq == button_irqs[1] && gpio_get_value(leds[0].gpio))
|
||||
gpio_set_value(leds[0].gpio, 0);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
/*
|
||||
* Initialize the module - register the IRQ handler
|
||||
*/
|
||||
int init_module()
|
||||
{
|
||||
my_workqueue = create_workqueue(MY_WORK_QUEUE_NAME);
|
||||
int ret = 0;
|
||||
|
||||
/*
|
||||
* Since the keyboard handler won't co-exist with another handler,
|
||||
* such as us, we have to disable it (free its IRQ) before we do
|
||||
* anything. Since we don't know where it is, there's no way to
|
||||
* reinstate it later - so the computer will have to be rebooted
|
||||
* when we're done.
|
||||
*/
|
||||
free_irq(1, NULL);
|
||||
printk(KERN_INFO "%s\n", __func__);
|
||||
|
||||
/*
|
||||
* Request IRQ 1, the keyboard IRQ, to go to our irq_handler.
|
||||
* SA_SHIRQ means we're willing to have othe handlers on this IRQ.
|
||||
* SA_INTERRUPT can be used to make the handler into a fast interrupt.
|
||||
*/
|
||||
return request_irq(1, /* The number of the keyboard IRQ on PCs */
|
||||
irq_handler, /* our handler */
|
||||
SA_SHIRQ, "test_keyboard_irq_handler",
|
||||
(void *)(irq_handler));
|
||||
/* register LED gpios */
|
||||
ret = gpio_request_array(leds, ARRAY_SIZE(leds));
|
||||
|
||||
if (ret) {
|
||||
printk(KERN_ERR "Unable to request GPIOs for LEDs: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* register BUTTON gpios */
|
||||
ret = gpio_request_array(buttons, ARRAY_SIZE(buttons));
|
||||
|
||||
if (ret) {
|
||||
printk(KERN_ERR "Unable to request GPIOs for BUTTONs: %d\n", ret);
|
||||
goto fail1;
|
||||
}
|
||||
|
||||
printk(KERN_INFO "Current button1 value: %d\n",
|
||||
gpio_get_value(buttons[0].gpio));
|
||||
|
||||
ret = gpio_to_irq(buttons[0].gpio);
|
||||
|
||||
if (ret < 0) {
|
||||
printk(KERN_ERR "Unable to request IRQ: %d\n", ret);
|
||||
goto fail2;
|
||||
}
|
||||
|
||||
button_irqs[0] = ret;
|
||||
|
||||
printk(KERN_INFO "Successfully requested BUTTON1 IRQ # %d\n",
|
||||
button_irqs[0]);
|
||||
|
||||
ret = request_irq(button_irqs[0], button_isr,
|
||||
IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
|
||||
"gpiomod#button1", NULL);
|
||||
|
||||
if (ret) {
|
||||
printk(KERN_ERR "Unable to request IRQ: %d\n", ret);
|
||||
goto fail2;
|
||||
}
|
||||
|
||||
|
||||
ret = gpio_to_irq(buttons[1].gpio);
|
||||
|
||||
if (ret < 0) {
|
||||
printk(KERN_ERR "Unable to request IRQ: %d\n", ret);
|
||||
goto fail2;
|
||||
}
|
||||
|
||||
button_irqs[1] = ret;
|
||||
|
||||
printk(KERN_INFO "Successfully requested BUTTON2 IRQ # %d\n",
|
||||
button_irqs[1]);
|
||||
|
||||
ret = request_irq(button_irqs[1], button_isr,
|
||||
IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
|
||||
"gpiomod#button2", NULL);
|
||||
|
||||
if (ret) {
|
||||
printk(KERN_ERR "Unable to request IRQ: %d\n", ret);
|
||||
goto fail3;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
/* cleanup what has been setup so far */
|
||||
fail3:
|
||||
free_irq(button_irqs[0], NULL);
|
||||
|
||||
fail2:
|
||||
gpio_free_array(buttons, ARRAY_SIZE(leds));
|
||||
|
||||
fail1:
|
||||
gpio_free_array(leds, ARRAY_SIZE(leds));
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Cleanup
|
||||
*/
|
||||
void cleanup_module()
|
||||
{
|
||||
/*
|
||||
* This is only here for completeness. It's totally irrelevant, since
|
||||
* we don't have a way to restore the normal keyboard interrupt so the
|
||||
* computer is completely useless and has to be rebooted.
|
||||
*/
|
||||
free_irq(1, NULL);
|
||||
int i;
|
||||
|
||||
printk(KERN_INFO "%s\n", __func__);
|
||||
|
||||
/* free irqs */
|
||||
free_irq(button_irqs[0], NULL);
|
||||
free_irq(button_irqs[1], NULL);
|
||||
|
||||
/* turn all LEDs off */
|
||||
for (i = 0; i < ARRAY_SIZE(leds); i++)
|
||||
gpio_set_value(leds[i].gpio, 0);
|
||||
|
||||
/* unregister */
|
||||
gpio_free_array(leds, ARRAY_SIZE(leds));
|
||||
gpio_free_array(buttons, ARRAY_SIZE(buttons));
|
||||
}
|
||||
|
||||
/*
|
||||
* some work_queue related functions are just available to GPL licensed Modules
|
||||
*/
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_AUTHOR("Bob Mottram");
|
||||
MODULE_DESCRIPTION("Handle some GPIO interrupts");
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 25 KiB |
Reference in New Issue
Block a user