Understanding GPIO Access in Linux: Why gpio_set_value_cansleep() Fixed the Warning
When working with GPIOs (General-Purpose Input/Output pins) in Linux, developers often encounter warnings about invalid contexts for sleeping. These warnings can be confusing, especially when using mechanisms like workqueues that are supposed to run in sleepable contexts. In this article, we’ll dissect a real-world example involving a Raspberry Pi GPIO interrupt handler, a workqueue, and a critical warning caused by improper GPIO access. We’ll explain why replacing gpio_set_value() with gpio_set_value_cansleep() resolved the issue and how to avoid similar pitfalls.
The Problem: A Mysterious Kernel Warning
Consider a kernel module that toggles an LED when a GPIO button is pressed. The module uses a workqueue to defer the LED toggling from an interrupt handler (atomic context) to a process context (sleepable). Here’s the key code snippet:
// Original work handler (caused a warning)
static void gpio_work_handler(struct work_struct *work) {
led_state = !led_state;
gpio_set_value(GPIO_LED_PIN, led_state); // WARNING HERE
printk(KERN_INFO "LED toggled to %s\n", led_state ? "ON" : "OFF");
}
When the button is pressed, the kernel logs a warning:
[ 582.523153] gpiochip pinctrl-rp1 GPIO16 interrupt triggered!
[ 582.523180] WARNING: CPU: 0 PID: 11 at drivers/gpio/gpiolib.c:3281 gpiod_set_raw_value+0x94/0xe0
The warning points to gpiod_set_raw_value(), which is called by gpio_set_value(). But why does this happen in a workqueue handler, which runs in process context?
Root Cause: Fast vs. Slow GPIO Controllers
The warning arises because the GPIO being accessed is connected to a slow GPIO controller, such as an I2C or SPI-based GPIO expander. Linux categorizes GPIO controllers into two types:
Directly memory-mapped (e.g., SoC GPIOs).
Operations are atomic and non-blocking.
Use gpio_set_value().
Accessed via buses like I2C or SPI (e.g., PCA953x, MCP230xx).
Operations may require waiting for bus transactions (sleeping).
Use gpio_set_value_cansleep().
Why gpio_set_value() Triggered a Warning
The Fix: gpio_set_value_cansleep()
The corrected work handler uses gpio_set_value_cansleep():
// Fixed work handler
static void gpio_work_handler(struct work_struct *work) {
led_state = !led_state;
gpio_set_value_cansleep(GPIO_LED_PIN, led_state); // SAFE FOR SLOW GPIOs
printk(KERN_INFO "LED toggled to %s\n", led_state ? "ON" : "OFF");
}
How It Works:
Key Takeaways
Recommended by LinkedIn
1. Know Your GPIO Hardware
if (gpiod_cansleep(gpio_desc)) {
// Use _cansleep() variant
}
struct gpio_chip *gpio =xxx;
gpio->can_sleep = 1;
/**
* gpiod_cansleep() - report whether gpio value access may sleep
* @desc: gpio to check
*
*/
int gpiod_cansleep(const struct gpio_desc *desc)
{
VALIDATE_DESC(desc);
return desc->gdev->chip->can_sleep;
}
2. Match Functions to Context
3.Workqueues Are Not Always "Safe" by Default
Debugging Tips
Use gpioinfo from the gpiod toolkit:
sudo gpioinfo
gpiochip0 - 54 lines:
line 0: "ID_SDA" unused input active-high
gpiochip1 - 8 lines:
line 0: "pca9555" "led0" output active-high [used]
Here, gpiochip1 is likely an I2C-based expander requiring _cansleep().
Warnings like WARNING: CPU: 0 ... gpiod_set_raw_value+0x94/0xe0 indicate a sleep in an invalid context. Search for the GPIO number and controller in the log.
Why This Matters
Using the wrong GPIO API can lead to:
Conclusion
The transition from gpio_set_value() to gpio_set_value_cansleep() in the example highlights a critical aspect of Linux GPIO programming: hardware awareness. By understanding the underlying GPIO controller type and selecting the appropriate API, developers ensure safe and reliable operation. Whether you’re toggling LEDs or interfacing with complex sensors, always:
This approach not only silences kernel warnings but also builds robust drivers ready for real-world deployment
Appendix:
Source Code adds the gpiod_cansleep checking and uses a GPIO descriptor approach rather than referencing the pin directly.
#include <linux/module.h>
#include <linux/gpio.h>
#include <linux/interrupt.h>
#include <linux/workqueue.h>
#include <linux/gpio/consumer.h>
#define GPIO_BUTTON_PIN 587
#define GPIO_LED_PIN 625
static int irq_number;
static int led_state = 0; // Track LED state
static struct workqueue_struct *gpio_wq;
static struct work_struct gpio_work;
static struct gpio_desc *button_desc = NULL;
static struct gpio_desc *led_desc = NULL;
// Work function that will run in process context
static void gpio_work_handler(struct work_struct *work) {
// Toggle LED state
led_state = !led_state;
// Check if the LED GPIO is sleepable and use appropriate function
if (gpiod_cansleep(led_desc)) {
gpiod_set_value_cansleep(led_desc, led_state);
printk(KERN_INFO "Used gpiod_set_value_cansleep for LED\n");
} else {
gpiod_set_value(led_desc, led_state);
printk(KERN_INFO "Used gpiod_set_value for LED\n");
}
printk(KERN_INFO "LED toggled to %s\n", led_state ? "ON" : "OFF");
}
// Interrupt handler (top half)
static irqreturn_t gpio_irq_handler(int irq, void *dev_id) {
printk(KERN_INFO "gpiochip pinctrl-rp1 GPIO16 interrupt triggered!\n");
// Schedule the work to run in process context
queue_work(gpio_wq, &gpio_work);
return IRQ_HANDLED;
}
// Module initialization
static int __init gpio_irq_init(void) {
int ret;
printk(KERN_INFO "---------------------------------- gpio_irq_init start\n");
// Create workqueue
gpio_wq = create_singlethread_workqueue("gpio_workqueue");
if (!gpio_wq) {
printk(KERN_ERR "Failed to create workqueue\n");
return -ENOMEM;
}
// Initialize work
INIT_WORK(&gpio_work, gpio_work_handler);
// Get GPIO descriptors
button_desc = gpio_to_desc(GPIO_BUTTON_PIN);
if (!button_desc) {
printk(KERN_ERR "Failed to get descriptor for GPIO%d\n", GPIO_BUTTON_PIN);
destroy_workqueue(gpio_wq);
return -EINVAL;
}
printk(KERN_INFO "Got descriptor for GPIO%d\n", GPIO_BUTTON_PIN);
// Check if button GPIO is sleepable
if (gpiod_cansleep(button_desc)) {
printk(KERN_INFO "Button GPIO%d is sleepable\n", GPIO_BUTTON_PIN);
} else {
printk(KERN_INFO "Button GPIO%d is not sleepable\n", GPIO_BUTTON_PIN);
}
// Configure as input
ret = gpiod_direction_input(button_desc);
if (ret) {
printk(KERN_ERR "Failed to set GPIO%d as input\n", GPIO_BUTTON_PIN);
destroy_workqueue(gpio_wq);
return ret;
}
printk(KERN_INFO "Set GPIO%d as input\n", GPIO_BUTTON_PIN);
// Get LED GPIO descriptor
led_desc = gpio_to_desc(GPIO_LED_PIN);
if (!led_desc) {
printk(KERN_ERR "Failed to get descriptor for GPIO%d (LED)\n", GPIO_LED_PIN);
destroy_workqueue(gpio_wq);
return -EINVAL;
}
printk(KERN_INFO "Got descriptor for GPIO%d (LED)\n", GPIO_LED_PIN);
// Check if LED GPIO is sleepable
if (gpiod_cansleep(led_desc)) {
printk(KERN_INFO "LED GPIO%d is sleepable\n", GPIO_LED_PIN);
} else {
printk(KERN_INFO "LED GPIO%d is not sleepable\n", GPIO_LED_PIN);
}
// Configure LED as output
ret = gpiod_direction_output(led_desc, 0); // Initialize to OFF
if (ret) {
printk(KERN_ERR "Failed to set GPIO%d as output for LED\n", GPIO_LED_PIN);
destroy_workqueue(gpio_wq);
return ret;
}
printk(KERN_INFO "Set GPIO%d as output for LED\n", GPIO_LED_PIN);
// Map GPIO to IRQ number
irq_number = gpiod_to_irq(button_desc);
if (irq_number < 0) {
printk(KERN_ERR "Failed to map GPIO to IRQ\n");
destroy_workqueue(gpio_wq);
return irq_number;
}
printk(KERN_INFO "Map GPIO%d to IRQ%d\n", GPIO_BUTTON_PIN, irq_number);
// Request IRQ (rising edge trigger)
ret = request_irq(irq_number, gpio_irq_handler,
IRQF_TRIGGER_RISING, "gpio16_irq", NULL);
if (ret) {
printk(KERN_ERR "Failed to request IRQ %d\n", irq_number);
destroy_workqueue(gpio_wq);
return ret;
}
printk(KERN_INFO "Request IRQ%d\n", irq_number);
printk(KERN_INFO "---------------------------------- gpio_irq_init end\n");
return 0;
}
// Module cleanup
static void __exit gpio_irq_exit(void) {
printk(KERN_INFO "---------------------------------- gpio_irq_exit start\n");
free_irq(irq_number, NULL);
destroy_workqueue(gpio_wq);
printk(KERN_INFO "---------------------------------- gpio_irq_exit end\n");
}
module_init(gpio_irq_init);
module_exit(gpio_irq_exit);
MODULE_LICENSE("GPL");