Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions 08_irq/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Homework Interrupt handling

## Overwiew

The BBB board has four LEDs located next to the RESET and POWER buttons. Also, the board has one user button located next to the uSD socket.

On-board LESs

* LED0 (D2): `GPIO1_21` "Heartbeat"
* LED1 (D3): `GPIO1_22` "uSD access"
* LED2 (D4): `GPIO1_23` "Active"
* LED3 (D5): `GPIO1_24` "eMMC access"


The user button (`GPIO2_8`) may be used at boot stage to select eMMC or uSD boot and is not used in Limux kernel.
The uSD and MMC access LEDs are not used in nfs boot mode, so we can also use them.

In this homework we use legacy GPIO acces as simpler one because the main goal is use of timers and interrupts.
See the `onboard_io_demo` directory which contain a very simple module that tests access to the button and LEDs. Build and run it.

Note that the uSD and eMMC LEDs are not used in the nfs boot mode, but are already requested by the correspondent drivers. If we call `gpio_request ()` for these GPIOs, we get an error.
So, we do not use `gpio_request ()` and `gpio_free ()` here, but control these LEDs (___dirty!___).
Use request / free if you want to run a free GPIO aimed at the connectors on the board.

## Homework tasks
1. Implement a `gpio_poll` module which uses periodic jiffies-based timer to poll the button state
* Check the button state in 20 ms period. Use a simple debouncing method by comparing current and previous button state. If button state does not change, use it as a filtered state. On LED1 if the button is pressed and off otherwise.
* If the filtered state changes, issue a pulse on LED3 output for 1 ms. Use high resolution timer.
2. implement a `gpio_irq` module which uses interrupts to detect button state changes.
* Enable hardware debouncing on the button GPIO line (see Documentation/driver-api/gpio/legacy.rst).
* Use a threaded interrupt, enable both edges.
* Hardware handler must toggle LED1, increment 32-bit button state change counter and wake the threaded part.
* The `thread_fn` must read the counter and print its state.
Note: Such access to variable from two code streams is a subject to race condition which will be covered later. The access is safe here as we have one writer and one reader and 32-bit access is atomic.
* Add module parameter `simulate_busy` which enables simulation of long-time work in the `thread_fn`(sleep for several seconds). Use LED3 to indicate busy state. Print a message after the sleep ends.
* Press the button several times during the sleep, check module output.
* Try to remove the module just after button is pressed.
156 changes: 156 additions & 0 deletions 08_irq/irq/gpio_irq.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
// SPDX-License-Identifier: MIT and GPL
#include <linux/module.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/kernel.h>
#include <linux/gpio.h>
#include <linux/timer.h>
#include <linux/interrupt.h>
#include <linux/jiffies.h>
#include <linux/delay.h>

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Anton Kotsiubailo");
MODULE_DESCRIPTION("A Button/LED test driver for the BBB");
MODULE_VERSION("0.1");

#define BUTTON_DEBOUNCE 2000
#define MY_GPIO_INT_NAME "my_button_int"
#define MY_DEV_NAME "my_device"
#define LOW false

static unsigned int gpioLED1 = 48;
static unsigned int gpioLED2 = 49;
static unsigned int gpioButton = 117;
static bool ledOn;
static int led_gpio = -1;
static int button_gpio = -1;
static uint32_t counter;
static int button_irq;

static uint simulate_busy;
module_param(simulate_busy, uint, 0);

irqreturn_t button_threaded_irq(int data, void *arg)
{
if (simulate_busy == 0) {
pr_info("GL Button IRQ counter: %u\n", counter);

} else {
int busy = 20;
const int busy_time = BUTTON_DEBOUNCE / busy;

while (busy--) {
ledOn = !ledOn;
gpio_set_value(gpioLED1, ledOn);
mdelay(busy_time);
}
pr_info("GL Button IRQ counter: %u\n", counter);
}

return IRQ_HANDLED;
}

irqreturn_t button_handler(int data, void *arg)
{
ledOn = !ledOn;
gpio_set_value(gpioLED1, ledOn);
pr_info("GPIO_TEST: Interrupt! (button state is %d)\n",
gpio_get_value(gpioButton));
counter++;
return IRQ_WAKE_THREAD;
}

static int led_gpio_init(int gpio)
{
int rc;

rc = gpio_direction_output(gpio, 0);
if (rc)
return rc;

led_gpio = gpio;
return 0;
}

static int button_gpio_init(int gpio)
{
int rc;
rc = gpio_request(gpio, "Onboard user button");
if (rc)
goto err_register;

rc = gpio_direction_input(gpio);
if (rc)
goto err_input;

button_gpio = gpio;
pr_info("Init GPIO%d OK\n", button_gpio);

return gpio_set_debounce(gpio, BUTTON_DEBOUNCE);

err_input:
gpio_free(gpio);
err_register:
return rc;
}

int __init mod_init(void)
{
pr_info("GL Simulation busy is %s\n", simulate_busy ? "ON" : "OFF");
int res;
res = led_gpio_init(gpioLED1);
if (res != 0) {
pr_err("LED1 was not initialised\n");
return -EINVAL;
}

res = led_gpio_init(gpioLED2);
if (res != 0) {
pr_err("LED2 was not initialised\n");
return -EINVAL;
}

res = button_gpio_init(gpioButton);
if (res != 0) {
pr_err("Button was not initialised\n");
return -EINVAL;
}

button_irq = gpio_to_irq(gpioButton);

if (button_irq < 0) {
pr_err("'gpio_to_irq' err :(\n");
return button_irq;
}

res = request_threaded_irq(button_irq, button_handler,
button_threaded_irq,
IRQF_TRIGGER_RISING, MY_GPIO_INT_NAME,
MY_DEV_NAME);

if (res < 0) {
pr_err("'request_treaded_irq' err :(\n");
return res;
}

pr_info("'gpio_irq' module initialized\n");

return 0;
}

void __exit mod_cleanup(void)
{
gpio_set_value(gpioLED1, LOW);
gpio_set_value(gpioLED2, LOW);

gpio_free(gpioLED1);
gpio_free(gpioLED2);
gpio_free(gpioButton);

free_irq(button_irq, MY_DEV_NAME);
pr_info("'gpio_poll' module released\n");
}

module_init(mod_init);
module_exit(mod_cleanup);
20 changes: 20 additions & 0 deletions 08_irq/irq/logs.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@

/tmp # insmod gpio_irq.ko simulate_busy=1
[ 6796.648146] GL Simulation busy is ON
[ 6796.651992] Init GPIO117 OK
[ 6796.655529] 'gpio_irq' module initialized
/tmp # [ 6801.463983] GPIO_TEST: Interrupt! (button state is 1)
[ 6802.424350] sched: RT throttling activated
[ 6803.468157] GL Button IRQ counter: 1
[ 6804.190531] GPIO_TEST: Interrupt! (button state is 1)
[ 6806.190317] GL Button IRQ counter: 2
[ 6809.980411] GPIO_TEST: Interrupt! (button state is 1)
[ 6811.980174] GL Button IRQ counter: 3
[ 6819.040412] GPIO_TEST: Interrupt! (button state is 1)
[ 6821.040177] GL Button IRQ counter: 4
[ 6822.554597] GPIO_TEST: Interrupt! (button state is 1)
[ 6824.554354] GL Button IRQ counter: 5
[ 6827.026338] GPIO_TEST: Interrupt! (button state is 1)
[ 6829.026121] GL Button IRQ counter: 6
[ 6831.546294] GPIO_TEST: Interrupt! (button state is 1)
[ 6833.546082] GL Button IRQ counter: 7
21 changes: 21 additions & 0 deletions 08_irq/onboard_io_demo/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
ifneq ($(KERNELRELEASE),)

# Kbuild part of makefile
obj-m := onboard_io.o

else

# kernel sources

KDIR ?= $(HOME)/repos/linux-stable

default:
$(MAKE) -C $(KDIR) M="$$PWD"

clean:
$(MAKE) -C $(KDIR) M="$$PWD" $@

%.i %.s : %.c
$(MAKE) -C $(KDIR) M=$$PWD $@

endif
139 changes: 139 additions & 0 deletions 08_irq/onboard_io_demo/onboard_io.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
/* SPDX-License-Identifier: GPL-2.0 */

/*
* BBB On-board IO demo.
* Just on led at init and off at exit. Select LED to control by button state.
*
*/

#define pr_fmt(fmt) KBUILD_MODNAME ": %s: " fmt , __func__

#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/gpio.h>

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Oleksandr Redchuk (at GL training courses)");
MODULE_DESCRIPTION("BBB Onboard IO Demo");
MODULE_VERSION("0.1");

#define GPIO_NUMBER(port, bit) (32 * (port) + (bit))

/* On-board LESs
* 0: D2 GPIO1_21 heartbeat
* 1: D3 GPIO1_22 uSD access
* 2: D4 GPIO1_23 active
* 3: D5 GPIO1_24 eMMC access
*
* Note that uSD and eMMC LEDs are not used in nfs boot mode, but they are already requested
* by correspondent drivers.
* So that, we don't use gpio_request()/gpio_free() here, but control these LEds (dirty!).
* Use request/free if you want to control free GPIO routed to board connectors.
*/

#define LED_SD GPIO_NUMBER(1, 22)
#define LED_MMC GPIO_NUMBER(1, 24)

/* On-board button.
*
* LCD/HDMI interface must be disabled.
*/
#define BUTTON GPIO_NUMBER(2, 8)

static int led_gpio = -1;
static int button_gpio = -1;

static int led_gpio_init(int gpio)
{
int rc;

rc = gpio_direction_output(gpio, 0);
if (rc)
return rc;

led_gpio = gpio;
return 0;
}

static int button_gpio_init(int gpio)
{
int rc;

rc = gpio_request(gpio, "Onboard user button");
if (rc)
goto err_register;

rc = gpio_direction_input(gpio);
if (rc)
goto err_input;

button_gpio = gpio;
pr_info("Init GPIO%d OK\n", button_gpio);
return 0;

err_input:
gpio_free(gpio);
err_register:
return rc;
}

static void button_gpio_deinit(void)
{
if (button_gpio >= 0) {
gpio_free(button_gpio);
pr_info("Deinit GPIO%d\n", button_gpio);
}
}

/* Module entry/exit points */
static int __init onboard_io_init(void)
{
int rc;
int gpio;
int button_state;

rc = button_gpio_init(BUTTON);
if (rc) {
pr_err("Can't set GPIO%d for button\n", BUTTON);
goto err_button;
}

button_state = gpio_get_value(button_gpio);

gpio = button_state ? LED_MMC : LED_SD;
if (rc) {
pr_err("Can't set GPIO%d for output\n", gpio);
goto err_button;
}


rc = led_gpio_init(gpio);
if (rc) {
pr_err("Can't set GPIO%d for output\n", gpio);
goto err_led;
}

gpio_set_value(led_gpio, 1);
pr_info("LED at GPIO%d ON\n", led_gpio);

return 0;

err_led:
button_gpio_deinit();
err_button:
return rc;
}

static void __exit onboard_io_exit(void)
{
if (led_gpio >= 0) {
gpio_set_value(led_gpio, 0);
pr_info("LED at GPIO%d OFF\n", led_gpio);
}
button_gpio_deinit();
}

module_init(onboard_io_init);
module_exit(onboard_io_exit);

Loading