From 998113c260f9c9b7889fcdf39c8446add37390ff Mon Sep 17 00:00:00 2001 From: Jeremy90307 Date: Thu, 19 Dec 2024 15:36:54 +0800 Subject: [PATCH] Add an LED driver example using GPIO Use GPIO to control LED on/off and add related GPIO knowledge. Test detail: - Tested on Raspberry Pi 5B with Raspberry Pi OS (Debian 12, Linux version 6.12.1-v8-16k+) - Verify that LED example compiles and loads successfully - Verify that LED turns on and off sucessfully --- .ci/non-working | 1 + examples/Makefile | 1 + examples/led.c | 203 ++++++++++++++++++++++++++++++++++++++++++++++ lkmpg.tex | 50 ++++++++++++ 4 files changed, 255 insertions(+) create mode 100644 examples/led.c diff --git a/.ci/non-working b/.ci/non-working index 31648232..6f2c90eb 100644 --- a/.ci/non-working +++ b/.ci/non-working @@ -3,3 +3,4 @@ bh_threaded intrpt vkbd syscall-steal +led diff --git a/examples/Makefile b/examples/Makefile index 01ab18b7..df05b032 100644 --- a/examples/Makefile +++ b/examples/Makefile @@ -31,6 +31,7 @@ obj-m += ioctl.o obj-m += vinput.o obj-m += vkbd.o obj-m += static_key.o +obj-m += led.o PWD := $(CURDIR) diff --git a/examples/led.c b/examples/led.c new file mode 100644 index 00000000..a016c7a3 --- /dev/null +++ b/examples/led.c @@ -0,0 +1,203 @@ +/* + * led.c - Using GPIO to control the LED on/off + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define SUCCESS 0 +#define DEVICE_NAME "gpio_led" +#define DEVICE_CNT 1 +#define BUF_LEN 2 + +static char control_signal[BUF_LEN]; +static unsigned long device_buffer_size = 0; + +struct LED_dev { + dev_t dev_num; + int major_num; + int minor_num; + struct cdev cdev; + struct class *cls; + struct device *dev; +}; + +static struct LED_dev led_device; + +/* Define GPIOs for LEDs. + * TODO: According to the requirements, search /sys/kernel/debug/gpio to + * find the corresponding GPIO location. + */ +static struct gpio leds[] = { { 4, GPIOF_OUT_INIT_LOW, "LED 1" } }; + +/* This is called whenever a process attempts to open the device file */ +static int device_open(struct inode *inode, struct file *file) +{ + pr_info("device_open(%p)\n", file); + + return SUCCESS; +} + +static int device_release(struct inode *inode, struct file *file) +{ + pr_info("device_release(%p,%p)\n", inode, file); + + return SUCCESS; +} + +/* called when somebody tries to write into our device file. */ +static ssize_t device_write(struct file *file, const char __user *buffer, + size_t length, loff_t *offset) +{ + pr_info("device_write(%p,%p,%ld)", file, buffer, length); + + device_buffer_size = min(BUF_LEN, length); + + if (copy_from_user(control_signal, buffer, device_buffer_size)) { + return -EFAULT; + } + + /* Determine the received signal to decide the LED on/off state. */ + switch (control_signal[0]) { + case '0': + gpio_set_value(leds[0].gpio, 0); + pr_info("LED OFF"); + break; + case '1': + gpio_set_value(leds[0].gpio, 1); + pr_info("LED ON"); + break; + default: + pr_warn("Invalid value!\n"); + break; + } + + *offset += device_buffer_size; + + /* Again, return the number of input characters used. */ + return device_buffer_size; +} + +static struct file_operations fops = { + .owner = THIS_MODULE, + .write = device_write, + .open = device_open, + .release = device_release, +}; + +/* Initialize the module - Register the character device */ +static int __init led_init(void) +{ + int ret = 0; + + /* Determine whether dynamic allocation of the device number is needed. */ + if (led_device.major_num) { + led_device.dev_num = MKDEV(led_device.major_num, led_device.minor_num); + ret = + register_chrdev_region(led_device.dev_num, DEVICE_CNT, DEVICE_NAME); + } else { + ret = alloc_chrdev_region(&led_device.dev_num, 0, DEVICE_CNT, + DEVICE_NAME); + } + + /* Negative values signify an error */ + if (ret < 0) { + pr_alert("%s failed with %d\n", + "Sorry, registering the character device ", ret); + return ret; + } + + pr_info("Major = %d, Minor = %d\n", MAJOR(led_device.dev_num), + MINOR(led_device.dev_num)); + + /* Prevents module unloading while operations are in use */ + led_device.cdev.owner = THIS_MODULE; + + cdev_init(&led_device.cdev, &fops); + ret = cdev_add(&led_device.cdev, led_device.dev_num, 1); + if (ret) { + pr_err("Failed to add the device to the system\n"); + goto fail1; + } + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 4, 0) + led_device.cls = class_create(DEVICE_NAME); +#else + led_device.cls = class_create(THIS_MODULE, DEVICE_NAME); +#endif + if (IS_ERR(led_device.cls)) { + pr_err("Failed to create class for device\n"); + ret = PTR_ERR(led_device.cls); + goto fail2; + } + + led_device.dev = device_create(led_device.cls, NULL, led_device.dev_num, + NULL, DEVICE_NAME); + if (IS_ERR(led_device.dev)) { + pr_err("Failed to create the device file\n"); + ret = PTR_ERR(led_device.dev); + goto fail3; + } + + pr_info("Device created on /dev/%s\n", DEVICE_NAME); + + ret = gpio_request(leds[0].gpio, leds[0].label); + + if (ret) { + pr_err("Unable to request GPIOs for LEDs: %d\n", ret); + goto fail4; + } + + ret = gpio_direction_output(leds[0].gpio, leds[0].flags); + + if (ret) { + pr_err("Failed to set GPIO %d direction\n", leds[0].gpio); + goto fail5; + } + + return 0; + +fail5: + gpio_free(leds[0].gpio); + +fail4: + device_destroy(led_device.cls, led_device.dev_num); + +fail3: + class_destroy(led_device.cls); + +fail2: + cdev_del(&led_device.cdev); + +fail1: + unregister_chrdev_region(led_device.dev_num, DEVICE_CNT); + + return ret; +} + +static void __exit led_exit(void) +{ + gpio_set_value(leds[0].gpio, 0); + gpio_free(leds[0].gpio); + + device_destroy(led_device.cls, led_device.dev_num); + class_destroy(led_device.cls); + cdev_del(&led_device.cdev); + unregister_chrdev_region(led_device.dev_num, DEVICE_CNT); +} + +module_init(led_init); +module_exit(led_exit); + +MODULE_LICENSE("GPL"); diff --git a/lkmpg.tex b/lkmpg.tex index 1f5f6685..b33f0428 100644 --- a/lkmpg.tex +++ b/lkmpg.tex @@ -1816,6 +1816,56 @@ \subsection{Flashing keyboard LEDs} Adding debug code can change the situation enough to make the bug seem to disappear. Thus, you should keep debug code to a minimum and make sure it does not show up in production code. +\section{GPIO} +\label{sec:gpio} +\subsection{GPIO} +\label{sec:gpio_introduction} +General Purpose Input/Output (GPIO) appears on the development board as pins. It acts as a bridge for communication between the development board and external devices. You can think of it like a switch: users can turn it on or off (Input), and the development board can also turn it on or off (Output). + +To implement GPIO, you use the \cpp|gpio_request()| function to enable a specific GPIO pin. After successfully enabling it, you can check that the pin is being used by looking at /sys/kernel/debug/gpio. + +\begin{codebash} +cat /sys/kernel/debug/gpio +\end{codebash} + +There are other ways to register GPIOs. For example, you can use \cpp|gpio_request_one()| to register a GPIO while setting its direction (input or output) and initial state at the same time. You can also use \cpp|gpio_request_array()| to register multiple GPIOs at once. However, note that \cpp|gpio_request_array()| has been removed since Linux v6.10+. + +When using GPIO, you must set it as either output with \cpp|gpio_direction_output()| or \cpp|input with gpio_direction_input()|. + +\begin{itemize} + \item when the GPIO is set as output, you can use \cpp|gpio_set_value()| to choose to set it to high voltage or low voltage. + \item when the GPIO is set as input, you can use \cpp|gpio_get_value()| to read whether the voltage is high or low. +\end{itemize} + +\subsection{Control the LED's on/off state} +\label{sec:gpio_led} +In Section \ref{sec:device_files}, we learned how to communicate with Device Files. Therefore, we will further use Device Files to control the LED on and off. + +In the implementation, a pull-down resistor is used. The positive electrode of the LED is connected to GPIO4, and the negative electrode is connected to GND. The materials used include a Raspberry Pi 5, an LED, single-core wires, and a 220$\Omega$ resistor. + +\samplec{examples/led.c} + +Make and install the module: +\begin{codebash} +make +sudo insmod led.ko +\end{codebash} + +Switch on the LED: +\begin{codebash} +echo "1" | sudo tee /dev/gpio_led +\end{codebash} + +Switch off the LED: +\begin{codebash} +echo "0" | sudo tee /dev/gpio_led +\end{codebash} + +Finally, remove the module: +\begin{codebash} +sudo rmmod led +\end{codebash} + \section{Scheduling Tasks} \label{sec:scheduling_tasks} There are two main ways of running tasks: tasklets and work queues.