云计算百科
云计算领域专业知识百科平台

DAY73 IMX6ULL Linux Key Driver Practice: A Complete Analysis from GPIO Polling to Device Tree Interr

IMX6ULL Linux Key Driver Practice: A Complete Analysis from GPIO Polling to Device Tree Interrupts + Wait Queues

In embedded Linux development, key drivers are a classic case for understanding character devices, Platform bus, interrupt mechanisms, and blocking I/O. Based on the IMX6ULL development board, this article integrates the core highlights of three blog posts, using “driver version evolution” as the main thread to comprehensively dissect the optimization path of key drivers—from basic GPIO polling to standard device tree interrupts + wait queues. It balances theoretical depth, code details, and practical implementation, helping developers master the core design philosophy of Linux drivers.

I. Core Technology Stack and Design Philosophy

1.1 Core Technology System

TechnologyDescription
Device Tree (DTS/DTB) Uniformly describes hardware resources like key GPIOs and interrupts, decoupling drivers from hardware via the compatible property.
GPIO Subsystem Encapsulates low-level register operations, providing standardized APIs like gpio_request and gpio_get_value to simplify hardware operations.
Interrupt Handling Uses “event triggering” instead of polling, triggering interrupts on key press/release to reduce CPU usage (request_irq + ISR).
Wait Queue Implements “sleep-wake” mechanism, releasing CPU when the application waits for key events and waking up after interrupts, achieving efficient blocking I/O.
Platform Bus Automatically matches device tree nodes with drivers via the compatible property, standardizing driver initialization (probe function).

1.2 Driver Evolution Design Philosophy

From “functional implementation” to “high performance + high portability,” the evolution of the three versions perfectly illustrates the Linux driver design principle of “low coupling, high cohesion”:

VersionCore ImplementationCPU UsagePortabilityKey Highlights
V1 Pure GPIO Polling High Medium Minimal logic, ideal for understanding GPIO operations.
V2 GPIO-to-Interrupt + Wait Queue Low Medium Introduces blocking I/O, solving CPU usage issues.
V3 Device Tree Interrupt + Wait Queue Low High Fully decouples hardware details, standard Linux practice.

II. Device Tree (DTS) Writing: Standardizing Hardware Description

The device tree serves as the “bridge” between drivers and hardware, uniformly describing key GPIO and interrupt resources without hardcoding hardware details in the driver.

2.1 DTS Node Example (Added to IMX6ULL Device Tree File)

ptkey {
compatible = "pt-key"; // Matches driver's `of_device_id`, the core matching property.
ptkey-gpio = <&gpio1 4 GPIO_ACTIVE_LOW>; // Key GPIO (GPIO1_IO04, active low).
interrupt-parent = <&gpio1>; // Interrupt parent controller (GPIO1).
interrupts = <4 IRQ_TYPE_EDGE_FALLING>; // Interrupt number 4, falling-edge triggered (key press).
};

  • &gpio1: References the IMX6ULL’s GPIO1 controller node.
  • GPIO_ACTIVE_LOW: Indicates the GPIO is active low (key press pulls GPIO low).
  • interrupts: Defines interrupt trigger mode; IRQ_TYPE_EDGE_FALLING means falling-edge triggered.

2.2 Device Tree Matching Mechanism

  • The driver declares supported compatible properties via the of_device_id structure.
  • During kernel startup, the device tree node is automatically matched with the driver’s compatible property.
  • Upon successful matching, the Platform bus triggers the driver’s probe function to complete initialization.
  • III. Driver Code Analysis: Detailed Evolution of Three Versions

    3.1 V1: Pure GPIO Polling Driver (key.c)

    The most basic implementation, polling GPIO levels to determine key status, ideal for beginners to understand the GPIO subsystem.

    Core Code

    #define DEV_NAME "key"
    static int key_gpio;

    // Read key status (1=not pressed, 0=pressed).
    static inline int get_key_status(void) {
    return gpio_get_value(key_gpio); // GPIO subsystem API, reads level.
    }

    // `read` function: Returns key status when the application calls `read`.
    static ssize_t read(struct file *file, char __user *buf, size_t size, loff_t *loff) {
    int status = get_key_status();
    // Copies data from kernel space to user space.
    if (copy_to_user(buf, &status, sizeof(status)))
    return EFAULT;
    return sizeof(status);
    }

    // `probe` function: Core driver initialization flow.
    static int probe(struct platform_device *pdev) {
    struct device_node *pdts;
    int ret = misc_register(&misc_dev); // Registers a miscellaneous device, auto-creates `/dev/key`.
    if (ret) goto err_misc_register;

    pdts = of_find_node_by_path("/ptkey"); // Finds the device tree node.
    key_gpio = of_get_named_gpio(pdts, "ptkey-gpio", 0); // Gets GPIO number from the node.
    ret = gpio_request(key_gpio, "key"); // Requests GPIO resources to avoid conflicts.
    gpio_direction_input(key_gpio); // Configures GPIO as input mode.

    printk("V1: GPIO polling driver initialized.\\n");
    return 0;

    // Error handling: Releases allocated resources to avoid memory leaks.
    err_misc_register:
    misc_deregister(&misc_dev);
    printk("Driver initialization failed, ret=%d\\n", ret);
    return ret;
    }

    Workflow and Drawbacks
    • Workflow: Application calls read in a while(1) loop → driver reads GPIO level → returns status.
    • Drawbacks: High CPU usage (up to 99%), inability to capture transient key actions, poor real-time performance.

    3.2 V2: Interrupt + Wait Queue (key_irq.c)

    Core optimization: Replaces polling with interrupts and implements “sleep-wake” via wait queues, solving CPU usage issues.

    3.2.1 Core Mechanism: Wait Queue

    Wait queues are the core of Linux blocking I/O, acting as a “process waiting room”:

    • When no key event occurs, the application sleeps, releasing CPU.
    • When a key triggers an interrupt, the driver wakes the sleeping process for efficient event response.
    3.2.2 Core Code Modifications

    // Defines wait queue and condition variable.
    static wait_queue_head_t wq; // Wait queue head.
    static int condition = 0; // Event readiness flag (0=not ready, 1=ready).
    static int key_irq; // Interrupt number.

    // Interrupt service function (top half): Triggered on key press.
    static irqreturn_t key_irq_handler(int irq, void *dev) {
    int arg = *(int *)dev;
    if (100 != arg) return IRQ_NONE; // Validates private data to avoid false triggers.

    condition = 1; // Marks event as ready.
    wake_up_interruptible(&wq); // Wakes up the waiting application process.
    return IRQ_HANDLED; // Informs the kernel the interrupt is handled.
    }

    // Modified `read` function: Implements blocking I/O.
    static ssize_t read(struct file *file, char __user *buf, size_t size, loff_t *loff) {
    condition = 0;
    // Sleeps if condition is unmet (0% CPU usage).
    wait_event_interruptible(wq, condition);

    int status = 1; // Marks key press event.
    copy_to_user(buf, &status, sizeof(status));
    return sizeof(status);
    }

    // `probe` function: Adds interrupt initialization.
    static int probe(struct platform_device *pdev) {
    // (GPIO initialization omitted, same as V1.)
    key_irq = gpio_to_irq(key_gpio); // Converts GPIO to interrupt number (depends on GPIO-IRQ binding).

    // Registers interrupt: interrupt number, ISR, trigger mode, name, private data.
    ret = request_irq(key_irq, key_irq_handler,
    IRQF_TRIGGER_FALLING, "key0_irq", &arg);
    init_waitqueue_head(&wq); // Initializes wait queue.

    printk("V2: Interrupt + wait queue driver initialized.\\n");
    return 0;
    }

    Core Advantages
    • 0% CPU usage when the application is blocked, responding only on key press.
    • Timely interrupt response, capturing transient key actions with improved real-time performance.
    • Retains miscellaneous device features, auto-creating /dev/key without manual mknod.

    3.3 V3: Interrupt Parsing via Device Tree (key_irq_sub.c)

    The gpio_to_irq approach in V2 still relies on “fixed GPIO-interrupt number binding.” V3 resolves interrupt numbers directly from the device tree, fully decoupling hardware details—the standard Linux implementation.

    Key Improvement: Interrupt Number Resolution

    // V2 approach: Depends on GPIO-interrupt binding
    // key_irq = gpio_to_irq(key_gpio);

    // V3 approach: Parses interrupt number from device tree (recommended)
    key_irq = irq_of_parse_and_map(pdts, 0); // pdts: device tree node pointer, 0: interrupt index
    if (key_irq < 0) {
    printk("Failed to parse interrupt number\\n");
    return EINVAL;
    }

    Core Advantages
    • No dependency on fixed GPIO-interrupt binding; hardware changes only require device tree updates.
    • Driver code is generic and portable to other Linux platforms supporting device trees.
    • Fully aligns with Linux device driver models, adhering to the “hardware-description-and-driver-logic-separation” philosophy.

    3.4 Unified Platform Driver Framework

    All three versions are built on the standard Platform driver framework, ensuring scalability and compatibility:

    // Device tree match table: Matches DTS node's 'compatible' property
    static struct of_device_id key_table[] = {
    {.compatible = "pt-key"},
    {} // Sentinel
    };

    // Platform driver structure
    static struct platform_driver pdrv = {
    .probe = probe, // Executed on successful match
    .remove = remove, // Executed on driver unload
    .driver = {
    .name = DEV_NAME,
    .of_match_table = key_table, // Device tree match table
    }
    };

    // Driver entry/exit (simplified via macros)
    module_platform_driver(pdrv);
    MODULE_LICENSE("GPL"); // Open-source license declaration

    IV. Application: Efficient Blocking I/O

    The application avoids polling by using read to block until key events occur, ensuring simplicity and efficiency.

    4.1 Application Code

    #include <stdio.h>
    #include <fcntl.h>
    #include <unistd.h>

    int main(int argc, const char *argv[]) {
    int fd = open("/dev/key", O_RDWR);
    if (fd < 0) {
    perror("Failed to open /dev/key");
    return 1;
    }

    int status = 0;
    while (1) {
    // Blocks until key event; process sleeps when idle
    int ret = read(fd, &status, sizeof(status));
    printf("Key pressed! ret=%d, status=%d\\n", ret, status);
    }

    close(fd);
    return 0;
    }

    4.2 Performance Comparison

    VersionCPU UsageResponse Behavior
    V1 ~99% Polling, potential missed events
    V2/V3 0% (sleep) Interrupt-triggered, real-time

    V. Practical Verification: Compilation to Execution

    5.1 Compilation Setup

    (1) Driver Module Compilation (Makefile)

    # Select driver version (choose one)
    obj-m += key.o # V1: GPIO polling
    # obj-m += key_irq.o # V2: Interrupt + wait queue
    # obj-m += key_irq_sub.o # V3: Device tree interrupt parsing
    KERNELDIR ?= /home/linux/IMX6ULL/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek
    PWD := $(shell pwd)

    all:
    $(MAKE) -C $(KERNELDIR) M=$(PWD) modules CROSS_COMPILE=arm-linux-gnueabihf- ARCH=arm

    clean:
    $(MAKE) -C $(KERNELDIR) M=$(PWD) clean

    Run: make to generate .ko driver module.

    (2) Application Compilation

    arm-linux-gnueabihf-gcc key_app.c -o key_app

    (3) Device Tree Compilation

    make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- dtbs

    Generates updated device tree (e.g., imx6ull-14×14-evk.dtb).

    5.2 Driver Loading and Validation (V3 Recommended)

  • Copy imx6ull-14×14-evk.dtb, key_irq_sub.ko, and key_app to the board.
  • Power on the board and load the new device tree.
  • Load the driver module:insmod key_irq_sub.ko
    ls /dev/key # Verify device node creation
  • Run the application:./key_app
  • Test: Press the key; app prints “Key pressed! ret=4, status=1”.
  • Check kernel logs:dmesg | grep -E "probe|irq"
    Expected output:key platform_driver_register success
    ######################### key_driver probe
    irq = 123 dev = 100 # Interrupt number and private data
  • Unload the driver:rmmod key_irq_sub.ko
  • VI. Troubleshooting Guide

    6.1 Driver Match Failure (probe Not Executed)

    • compatible mismatch: Ensure of_device_id in the driver exactly matches the DTS node.
    • Incorrect DTS node path: Verify of_find_node_by_path("/ptkey") matches the DTS path.
    • Device tree not updated: Confirm the compiled DTB file is flashed.

    6.2 Interrupt Registration Failure (request_irq Returns Negative)

    • Incorrect interrupt number: Check irq_of_parse_and_map return value; validate DTS interrupt configuration.
    • Interrupt conflict: Use cat /proc/interrupts to check occupied interrupts.
    • Trigger mode error: Ensure the interrupt trigger matches hardware (e.g., falling edge for keys).

    6.3 Application Not Sleeping (100% CPU Usage)

    • Uninitialized wait queue: Call init_waitqueue_head(&wq).
    • ISR not waking queue: Ensure condition=1 and wake_up_interruptible(&wq) are called.
    • Condition variable not reset: Set condition=0 in read before waiting.

    6.4 Unresponsive Key

    • GPIO misconfiguration: Confirm gpio_direction_input sets GPIO as input.
    • Hardware wiring error: GPIO should be high when idle, low when pressed.
    • Incorrect interrupt trigger: For key-release events, use IRQF_TRIGGER_RISING.

    VII. Key Takeaways

    7.1 Essential API Reference

    API FunctionPurpose
    of_find_node_by_path Locates device tree node by path
    of_get_named_gpio Retrieves GPIO number from DTS node
    gpio_request Reserves GPIO resources
    gpio_to_irq Converts GPIO to interrupt (V2)
    irq_of_parse_and_map Parses interrupt from DTS (V3 recommended)
    request_irq Registers interrupt handler
    init_waitqueue_head Initializes wait queue
    wait_event_interruptible Puts process to sleep until condition
    wake_up_interruptible Wakes sleeping process

    7.2 Design Principles

  • Decoupling: Device tree describes hardware; drivers focus on logic.
  • Efficiency: Interrupts replace polling; blocking I/O maximizes CPU utilization.
  • Standardization: Follows Linux driver models (Platform bus, misc devices) for compatibility.
  • VIII. Conclusion

    This guide demonstrates the evolution of a Linux key driver from “basic functionality” to “high-performance + high portability”:

    • V1: Introduces GPIO subsystem basics.
    • V2: Core interrupt and wait queue mechanisms to eliminate CPU waste.
    • V3: Device tree standardization for full hardware-driver decoupling.

    This methodology applies to all Linux input devices (e.g., touch keys, encoders, IR receivers) and forms the foundation for advanced driver development (e.g., input subsystems, touchscreen drivers).

    赞(0)
    未经允许不得转载:网硕互联帮助中心 » DAY73 IMX6ULL Linux Key Driver Practice: A Complete Analysis from GPIO Polling to Device Tree Interr
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!