Skip to content

Commit 9c56420

Browse files
committed
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
1 parent 3cb12d6 commit 9c56420

File tree

4 files changed

+259
-0
lines changed

4 files changed

+259
-0
lines changed

.ci/non-working

+1
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@ bh_threaded
33
intrpt
44
vkbd
55
syscall-steal
6+
led

examples/Makefile

+1
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ obj-m += ioctl.o
3131
obj-m += vinput.o
3232
obj-m += vkbd.o
3333
obj-m += static_key.o
34+
obj-m += led.o
3435

3536
PWD := $(CURDIR)
3637

examples/led.c

+199
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
/*
2+
* led.c - Using GPIO to control the LED on/off
3+
*/
4+
5+
#include <linux/cdev.h>
6+
#include <linux/delay.h>
7+
#include <linux/device.h>
8+
#include <linux/fs.h>
9+
#include <linux/gpio.h>
10+
#include <linux/init.h>
11+
#include <linux/module.h>
12+
#include <linux/printk.h>
13+
#include <linux/types.h>
14+
#include <linux/uaccess.h>
15+
#include <linux/version.h>
16+
17+
#include <asm/errno.h>
18+
19+
#define SUCCESS 0
20+
#define DEVICE_NAME "gpio_led"
21+
#define DEVICE_CNT 1
22+
#define BUF_LEN 2
23+
24+
static char control_signal[BUF_LEN];
25+
static unsigned long device_buffer_size = 0;
26+
27+
struct LED_dev {
28+
dev_t dev_num;
29+
int major_num;
30+
int minor_num;
31+
struct cdev cdev;
32+
struct class *cls;
33+
struct device *dev;
34+
};
35+
36+
static struct LED_dev led_device;
37+
38+
/* Define GPIOs for LEDs.
39+
* TODO: According to the requirements, search /sys/kernel/debug/gpio to
40+
* find the corresponding GPIO location.
41+
*/
42+
static struct gpio leds[] = { { 4, GPIOF_OUT_INIT_LOW, "LED 1" } };
43+
44+
/* This is called whenever a process attempts to open the device file */
45+
static int device_open(struct inode *inode, struct file *file)
46+
{
47+
pr_info("device_open(%p)\n", file);
48+
49+
return SUCCESS;
50+
}
51+
52+
static int device_release(struct inode *inode, struct file *file)
53+
{
54+
pr_info("device_release(%p,%p)\n", inode, file);
55+
56+
return SUCCESS;
57+
}
58+
59+
static ssize_t device_write(struct file *file, const char __user *buffer,
60+
size_t length, loff_t *offset)
61+
{
62+
device_buffer_size = min(BUF_LEN, length);
63+
64+
if (copy_from_user(control_signal, buffer, device_buffer_size)) {
65+
return -EFAULT;
66+
}
67+
68+
/* Determine the received signal to decide the LED on/off state. */
69+
switch (control_signal[0]) {
70+
case '0':
71+
gpio_set_value(leds[0].gpio, 0);
72+
pr_info("LED OFF");
73+
break;
74+
case '1':
75+
gpio_set_value(leds[0].gpio, 1);
76+
pr_info("LED ON");
77+
break;
78+
default:
79+
pr_warn("Invalid value!\n");
80+
break;
81+
}
82+
83+
*offset += device_buffer_size;
84+
85+
/* Again, return the number of input characters used. */
86+
return device_buffer_size;
87+
}
88+
89+
static struct file_operations fops = {
90+
.owner = THIS_MODULE,
91+
.write = device_write,
92+
.open = device_open,
93+
.release = device_release,
94+
};
95+
96+
/* Initialize the module - Register the character device */
97+
static int __init led_init(void)
98+
{
99+
int ret = 0;
100+
101+
/* Determine whether dynamic allocation of the device number is needed. */
102+
if (led_device.major_num) {
103+
led_device.dev_num = MKDEV(led_device.major_num, led_device.minor_num);
104+
ret =
105+
register_chrdev_region(led_device.dev_num, DEVICE_CNT, DEVICE_NAME);
106+
} else {
107+
ret = alloc_chrdev_region(&led_device.dev_num, 0, DEVICE_CNT,
108+
DEVICE_NAME);
109+
}
110+
111+
/* Negative values signify an error */
112+
if (ret < 0) {
113+
pr_alert("Failed to register character device, error: %d\n", ret);
114+
return ret;
115+
}
116+
117+
pr_info("Major = %d, Minor = %d\n", MAJOR(led_device.dev_num),
118+
MINOR(led_device.dev_num));
119+
120+
/* Prevents module unloading while operations are in use */
121+
led_device.cdev.owner = THIS_MODULE;
122+
123+
cdev_init(&led_device.cdev, &fops);
124+
ret = cdev_add(&led_device.cdev, led_device.dev_num, 1);
125+
if (ret) {
126+
pr_err("Failed to add the device to the system\n");
127+
goto fail1;
128+
}
129+
130+
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 4, 0)
131+
led_device.cls = class_create(DEVICE_NAME);
132+
#else
133+
led_device.cls = class_create(THIS_MODULE, DEVICE_NAME);
134+
#endif
135+
if (IS_ERR(led_device.cls)) {
136+
pr_err("Failed to create class for device\n");
137+
ret = PTR_ERR(led_device.cls);
138+
goto fail2;
139+
}
140+
141+
led_device.dev = device_create(led_device.cls, NULL, led_device.dev_num,
142+
NULL, DEVICE_NAME);
143+
if (IS_ERR(led_device.dev)) {
144+
pr_err("Failed to create the device file\n");
145+
ret = PTR_ERR(led_device.dev);
146+
goto fail3;
147+
}
148+
149+
pr_info("Device created on /dev/%s\n", DEVICE_NAME);
150+
151+
ret = gpio_request(leds[0].gpio, leds[0].label);
152+
153+
if (ret) {
154+
pr_err("Unable to request GPIOs for LEDs: %d\n", ret);
155+
goto fail4;
156+
}
157+
158+
ret = gpio_direction_output(leds[0].gpio, leds[0].flags);
159+
160+
if (ret) {
161+
pr_err("Failed to set GPIO %d direction\n", leds[0].gpio);
162+
goto fail5;
163+
}
164+
165+
return 0;
166+
167+
fail5:
168+
gpio_free(leds[0].gpio);
169+
170+
fail4:
171+
device_destroy(led_device.cls, led_device.dev_num);
172+
173+
fail3:
174+
class_destroy(led_device.cls);
175+
176+
fail2:
177+
cdev_del(&led_device.cdev);
178+
179+
fail1:
180+
unregister_chrdev_region(led_device.dev_num, DEVICE_CNT);
181+
182+
return ret;
183+
}
184+
185+
static void __exit led_exit(void)
186+
{
187+
gpio_set_value(leds[0].gpio, 0);
188+
gpio_free(leds[0].gpio);
189+
190+
device_destroy(led_device.cls, led_device.dev_num);
191+
class_destroy(led_device.cls);
192+
cdev_del(&led_device.cdev);
193+
unregister_chrdev_region(led_device.dev_num, DEVICE_CNT);
194+
}
195+
196+
module_init(led_init);
197+
module_exit(led_exit);
198+
199+
MODULE_LICENSE("GPL");

lkmpg.tex

+58
Original file line numberDiff line numberDiff line change
@@ -1816,6 +1816,64 @@ \subsection{Flashing keyboard LEDs}
18161816
Adding debug code can change the situation enough to make the bug seem to disappear.
18171817
Thus, you should keep debug code to a minimum and make sure it does not show up in production code.
18181818

1819+
\section{GPIO}
1820+
\label{sec:gpio}
1821+
\subsection{GPIO}
1822+
\label{sec:gpio_introduction}
1823+
General Purpose Input/Output (GPIO) appears on the development board as pins.
1824+
It acts as a bridge for communication between the development board and external devices.
1825+
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).
1826+
1827+
To implement GPIO, you use the \cpp|gpio_request()| function to enable a specific GPIO pin.
1828+
After successfully enabling it, you can check that the pin is being used by looking at /sys/kernel/debug/gpio.
1829+
1830+
\begin{codebash}
1831+
cat /sys/kernel/debug/gpio
1832+
\end{codebash}
1833+
1834+
There are other ways to register GPIOs.
1835+
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.
1836+
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+.
1837+
1838+
When using GPIO, you must set it as either output with \cpp|gpio_direction_output()| or \cpp|input with gpio_direction_input()|.
1839+
1840+
\begin{itemize}
1841+
\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.
1842+
\item when the GPIO is set as input, you can use \cpp|gpio_get_value()| to read whether the voltage is high or low.
1843+
\end{itemize}
1844+
1845+
\subsection{Control the LED's on/off state}
1846+
\label{sec:gpio_led}
1847+
In Section \ref{sec:device_files}, we learned how to communicate with Device Files.
1848+
Therefore, we will further use Device Files to control the LED on and off.
1849+
1850+
In the implementation, a pull-down resistor is used.
1851+
The positive electrode of the LED is connected to GPIO4, and the negative electrode is connected to GND.
1852+
The materials used include a Raspberry Pi 5, an LED, single-core wires, and a 220$\Omega$ resistor.
1853+
1854+
\samplec{examples/led.c}
1855+
1856+
Make and install the module:
1857+
\begin{codebash}
1858+
make
1859+
sudo insmod led.ko
1860+
\end{codebash}
1861+
1862+
Switch on the LED:
1863+
\begin{codebash}
1864+
echo "1" | sudo tee /dev/gpio_led
1865+
\end{codebash}
1866+
1867+
Switch off the LED:
1868+
\begin{codebash}
1869+
echo "0" | sudo tee /dev/gpio_led
1870+
\end{codebash}
1871+
1872+
Finally, remove the module:
1873+
\begin{codebash}
1874+
sudo rmmod led
1875+
\end{codebash}
1876+
18191877
\section{Scheduling Tasks}
18201878
\label{sec:scheduling_tasks}
18211879
There are two main ways of running tasks: tasklets and work queues.

0 commit comments

Comments
 (0)