From a64cd81f37e394b61faf688a0787a551e3003e2b Mon Sep 17 00:00:00 2001 From: Jeremy90307 Date: Wed, 18 Dec 2024 20:10:31 +0800 Subject: [PATCH] Add an LED driver example using GPIO Add an LED driver example using GPIO and add related 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 | 175 ++++++++++++++++++++++++++++++++++++++++++++++ lkmpg.tex | 56 +++++++++++++++ 4 files changed, 233 insertions(+) create mode 100644 examples/led.c diff --git a/.ci/non-working b/.ci/non-working index 31648232..487b8888 100644 --- a/.ci/non-working +++ b/.ci/non-working @@ -3,3 +3,4 @@ bh_threaded intrpt vkbd syscall-steal +led \ No newline at end of file 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..e99f8ce3 --- /dev/null +++ b/examples/led.c @@ -0,0 +1,175 @@ +/* + * 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" + +/* The major device number. We can not rely on dynamic registration + * any more. + */ +#define MAJOR_NUM 100 +#define BUF_LEN 2 + +static char control_signal[BUF_LEN]; +static unsigned long device_buffer_size = 0; + +static struct class *cls; +static struct device *dev; + +/* Define GPIOs for LEDs. + * TODO: Change the numbers for the GPIO on your board. + */ +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); + + try_module_get(THIS_MODULE); + return SUCCESS; +} + +static int device_release(struct inode *inode, struct file *file) +{ + pr_info("device_release(%p,%p)\n", inode, file); + + module_put(THIS_MODULE); + 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: + printk("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 = { + .write = device_write, + .open = device_open, + .release = device_release, /* a.k.a. close */ +}; + +/* Initialize the module - Register the character device */ +static int __init led_init(void) +{ + int ret = 0; + + /* Register the character device (at least try) */ + ret = register_chrdev(MAJOR_NUM, DEVICE_NAME, &fops); + + /* Negative values signify an error */ + if (ret < 0) { + pr_alert("%s failed with %d\n", + "Sorry, registering the character device ", ret); + return ret; + } + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 4, 0) + cls = class_create(DEVICE_NAME); +#else + cls = class_create(THIS_MODULE, DEVICE_NAME); +#endif + if (IS_ERR(cls)) { + pr_err("Failed to create class for device\n"); + ret = PTR_ERR(cls); + goto fail1; + } + + dev = device_create(cls, NULL, MKDEV(MAJOR_NUM, 0), NULL, DEVICE_NAME); + if (IS_ERR(dev)) { + pr_err("Failed to create the device file\n"); + ret = PTR_ERR(dev); + goto fail2; + } + + 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 fail3; + } + + 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 fail4; + } + + return 0; + +fail4: + gpio_free(leds[0].gpio); + +fail3: + device_destroy(cls, MKDEV(MAJOR_NUM, 0)); + +fail2: + class_destroy(cls); + +fail1: + unregister_chrdev(MAJOR_NUM, DEVICE_NAME); + + return ret; +} + +/* Cleanup - unregister the appropriate file from /proc */ +static void __exit led_exit(void) +{ + gpio_set_value(leds[0].gpio, 0); + gpio_free(leds[0].gpio); + + device_destroy(cls, MKDEV(MAJOR_NUM, 0)); + class_destroy(cls); + unregister_chrdev(MAJOR_NUM, DEVICE_NAME); +} + +module_init(led_init); +module_exit(led_exit); + +MODULE_LICENSE("GPL"); diff --git a/lkmpg.tex b/lkmpg.tex index 1f5f6685..55cf4044 100644 --- a/lkmpg.tex +++ b/lkmpg.tex @@ -1816,6 +1816,61 @@ \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 in 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Ω resistor. + +\samplec{examples/led.c} + +Make and install the module: +\begin{codebash} +make +sudo insmod led.ko +\end{codebash} + +Changes the permissions of a file: +\begin{codebash} +sudo chmod 666 > /dev/gpio_led +\end{codebash} + +Switch on the LED: +\begin{codebash} +echo '1' > /dev/gpio_led +\end{codebash} + +Switch off the LED: +\begin{codebash} +echo '0' > /dev/gpio_led +\end{codebash} + +Finally, remove the test 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. @@ -2173,4 +2228,5 @@ \section{Where To Go From Here?} Pull requests are greatly appreciated. Happy hacking! + \end{document}