Generic GPIO Management in Linux

Introduction GPIO (General Purpose Input/Output) is a fundamental interface in embedded systems and Linux-based platforms. Linux provides multiple methods to control GPIOs, including the deprecated /sys/class/gpio/ interface and the modern libgpiod (GPIO character device) API. This document provides a comprehensive guide to managing GPIOs in Linux. GPIO Interfaces in Linux Linux provides three primary ways to manage GPIOs: Legacy Sysfs Interface (/sys/class/gpio/) - Deprecated but still present on some systems. GPIO Character Device (/dev/gpiochipX) - The recommended approach using libgpiod. Direct Kernel Access - Through kernel drivers or device tree configurations. 1. Sysfs GPIO Interface (Deprecated) The sysfs-based interface was historically used to control GPIOs but has been marked as deprecated in favor of gpiod. If still available, it can be accessed via /sys/class/gpio/. ...

February 19, 2025 · 2 min

Character Device Management in Kernel Drivers

Overview Character devices allow byte-by-byte communication between user-space applications and kernel drivers. They are commonly used for devices like serial ports, sensors, and custom hardware interfaces. The Linux kernel provides mechanisms for registering, managing, and interacting with character devices via a device file in /dev. Registering a Character Device To register a character device, the driver needs to: 1. Allocate a Major and Minor Number: Each character device is identified by a major number (device type) and a minor number (specific device). The major number indicates the driver associated with the device, while the minor number is used to differentiate between multiple devices handled by the same driver. If major and minor numbers are repeated, it can cause conflicts and lead to incorrect device identification. To avoid this, the kernel provides alloc_chrdev_region, a function to dynamically allocate major and minor numbers, ensuring uniqueness. These numbers are used in the /dev directory to associate device files with their corresponding drivers. Use alloc_chrdev_region to dynamically allocate a major number. dev_t dev; int result; // kernel/fs/char_dev.c // int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name) result = alloc_chrdev_region(&dev, 0, 1, "my_char_device"); if (result < 0) { pr_err("Failed to allocate major number\n"); return result; } pr_info("Device registered with major %d, minor %d\n", MAJOR(dev), MINOR(dev)); 2. Initialize and Register the Device: Define a cdev structure and initialize it with file operations. Use cdev_add to register the device with the kernel. struct cdev my_cdev; cdev_init(&my_cdev, &my_fops); my_cdev.owner = THIS_MODULE; result = cdev_add(&my_cdev, dev, 1); if (result < 0) { pr_err("Failed to add cdev\n"); unregister_chrdev_region(dev, 1); return result; } 3. Create a Device File (Optional): Creating a device file in /dev is optional because character devices can be accessed directly using their major and minor numbers through system calls or user-space libraries, bypassing the need for a device file. However, creating a file in /dev makes interaction more user-friendly by providing a standard interface. To interact with a character device without creating a device file, you can use system calls like mknod to create a temporary device node or interact with the device directly using its major and minor numbers programmatically. Use class_create and device_create to automatically create a device file in /dev. struct class *my_class; my_class = class_create(THIS_MODULE, "my_device_class"); if (IS_ERR(my_class)) { pr_err("Failed to create class\n"); cdev_del(&my_cdev); unregister_chrdev_region(dev, 1); return PTR_ERR(my_class); } device_create(my_class, NULL, dev, NULL, "my_char_device"); File Operations Character devices are controlled through a set of file operations defined in a struct file_operations. These operations determine how the device responds to system calls like open, read, write, and ioctl. ...

January 24, 2025 · 4 min

IOCTL in Kernel Device Drivers

ioctl Implementation in Kernel Device Drivers Overview ioctl (Input/Output Control) is a powerful system call in Linux used to perform device-specific operations that are not covered by standard system calls like read, write, or open. It allows user-space applications to interact with kernel-space drivers for device-specific configurations and data exchanges. How ioctl Works 1. User-Space Interaction: A user-space application invokes ioctl using the following prototype: int ioctl(int fd, unsigned long cmd, void *arg); fd: File descriptor for the device. cmd: Command defining the operation. arg: Pointer to the data or argument passed between user-space and kernel-space. 2. Driver-Side Handling: The ioctl system call is routed to the driver by the kernel. The driver implements a specific unlocked_ioctl or compat_ioctl callback in the file_operations structure. 3. Data Flow: Arguments passed via arg can be pointers to user-space data, requiring the driver to use helper functions like copy_from_user and copy_to_user for secure data transfer. Steps to Implement ioctl in a Kernel Driver 1. Define ioctl Commands: Use macros to define command numbers, typically with the _IO, _IOR, _IOW, and _IOWR macros provided in <linux/ioctl.h>. #define MY_IOCTL_BASE 'M' #define IOCTL_CMD_GET _IOR(MY_IOCTL_BASE, 1, int) #define IOCTL_CMD_SET _IOW(MY_IOCTL_BASE, 2, int) _IOR: Read data from the kernel. _IOW: Write data to the kernel. _IOWR: Read and write data. _IO: Command without data. 2. Implement ioctl Callback: Define the unlocked_ioctl function in the driver. Handle commands appropriately based on cmd. static long my_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { int value; switch (cmd) { case IOCTL_CMD_GET: value = 1234; // Example value if (copy_to_user((int __user *)arg, &value, sizeof(value))) return -EFAULT; break; case IOCTL_CMD_SET: if (copy_from_user(&value, (int __user *)arg, sizeof(value))) return -EFAULT; pr_info("Value set by user: %d\n", value); break; default: return -ENOTTY; // Command not supported } return 0; } 3. Integrate into file_operations: Register the ioctl handler in the file_operations structure. static const struct file_operations my_fops = { .owner = THIS_MODULE, .open = my_open, .release = my_release, .unlocked_ioctl = my_ioctl, }; 4. Test the ioctl Implementation: Write a user-space application to interact with the driver. #include <stdio.h> #include <fcntl.h> #include <unistd.h> #include <sys/ioctl.h> #define MY_IOCTL_BASE 'M' #define IOCTL_CMD_GET _IOR(MY_IOCTL_BASE, 1, int) #define IOCTL_CMD_SET _IOW(MY_IOCTL_BASE, 2, int) int main() { int fd, value = 42; fd = open("/dev/my_device", O_RDWR); if (fd < 0) { perror("Failed to open device"); return -1; } if (ioctl(fd, IOCTL_CMD_SET, &value) < 0) { perror("ioctl SET failed"); } if (ioctl(fd, IOCTL_CMD_GET, &value) < 0) { perror("ioctl GET failed"); } else { printf("Value from device: %d\n", value); } close(fd); return 0; } Best Practices for ioctl Use Explicit Command Definitions: Follow a consistent naming convention for command macros. Secure User-Kernel Data Transfer: Always validate pointers and sizes. Use copy_from_user and copy_to_user for safe data exchange. Error Handling: Return appropriate error codes for unsupported commands or invalid inputs. Limit ioctl Usage: Avoid using ioctl for operations that can be implemented using read or write. Magic Number: Ensure it’s unique (check Documentation/ioctl/ioctl-number.txt in kernel sources). Atomicity: Use locks if hardware operations are not atomic. Cross-Platform: Handle 32/64-bit compatibility with compat_ioctl if needed. Real-World Example: Custom ARM Board For a custom ARM board, you might need an ioctl to configure hardware parameters like GPIO modes or clock frequencies. ...

January 24, 2025 · 4 min

Kernel Log Level

Number Macro Log Level Description Equivalent 0 pr_emerg Emergency System is unusable. KERN_EMERG 1 pr_alert Alert Action must be taken immediately. KERN_ALERT 2 pr_crit Critical Critical conditions. KERN_CRIT 3 pr_err Error Error conditions. KERN_ERR 4 pr_warn Warning Warning conditions. KERN_WARNING 5 pr_notice Notice Normal but significant condition. KERN_NOTICE 6 pr_info Informational Informational messages. KERN_INFO 7 pr_debug Debug Debugging messages. KERN_DEBUG The number corresponds to the log level used by the Linux kernel, with lower numbers indicating higher severity. For example, if the log level is set to 4 (Warning), only messages from pr_emerg to pr_warn will appear in the system logs. Default log level is generally set to 6.

November 20, 2024 · 1 min