Skip to content

Commit 166d389

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

File tree

4 files changed

+260
-0
lines changed

4 files changed

+260
-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

+203
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
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+
/* called when somebody tries to write into our device file. */
60+
static ssize_t device_write(struct file *file, const char __user *buffer,
61+
size_t length, loff_t *offset)
62+
{
63+
pr_info("device_write(%p,%p,%ld)", file, buffer, length);
64+
65+
device_buffer_size = min(BUF_LEN, length);
66+
67+
if (copy_from_user(control_signal, buffer, device_buffer_size)) {
68+
return -EFAULT;
69+
}
70+
71+
/* Determine the received signal to decide the LED on/off state. */
72+
switch (control_signal[0]) {
73+
case '0':
74+
gpio_set_value(leds[0].gpio, 0);
75+
pr_info("LED OFF");
76+
break;
77+
case '1':
78+
gpio_set_value(leds[0].gpio, 1);
79+
pr_info("LED ON");
80+
break;
81+
default:
82+
pr_warn("Invalid value!\n");
83+
break;
84+
}
85+
86+
*offset += device_buffer_size;
87+
88+
/* Again, return the number of input characters used. */
89+
return device_buffer_size;
90+
}
91+
92+
static struct file_operations fops = {
93+
.owner = THIS_MODULE,
94+
.write = device_write,
95+
.open = device_open,
96+
.release = device_release,
97+
};
98+
99+
/* Initialize the module - Register the character device */
100+
static int __init led_init(void)
101+
{
102+
int ret = 0;
103+
104+
/* Determine whether dynamic allocation of the device number is needed. */
105+
if (led_device.major_num) {
106+
led_device.dev_num = MKDEV(led_device.major_num, led_device.minor_num);
107+
ret =
108+
register_chrdev_region(led_device.dev_num, DEVICE_CNT, DEVICE_NAME);
109+
} else {
110+
ret = alloc_chrdev_region(&led_device.dev_num, 0, DEVICE_CNT,
111+
DEVICE_NAME);
112+
}
113+
114+
/* Negative values signify an error */
115+
if (ret < 0) {
116+
pr_alert("%s failed with %d\n",
117+
"Sorry, registering the character device ", ret);
118+
return ret;
119+
}
120+
121+
pr_info("Major = %d, Minor = %d\n", MAJOR(led_device.dev_num),
122+
MINOR(led_device.dev_num));
123+
124+
/* Prevents module unloading while operations are in use */
125+
led_device.cdev.owner = THIS_MODULE;
126+
127+
cdev_init(&led_device.cdev, &fops);
128+
ret = cdev_add(&led_device.cdev, led_device.dev_num, 1);
129+
if (ret) {
130+
pr_err("Failed to add the device to the system\n");
131+
goto fail1;
132+
}
133+
134+
#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 4, 0)
135+
led_device.cls = class_create(DEVICE_NAME);
136+
#else
137+
led_device.cls = class_create(THIS_MODULE, DEVICE_NAME);
138+
#endif
139+
if (IS_ERR(led_device.cls)) {
140+
pr_err("Failed to create class for device\n");
141+
ret = PTR_ERR(led_device.cls);
142+
goto fail2;
143+
}
144+
145+
led_device.dev = device_create(led_device.cls, NULL, led_device.dev_num,
146+
NULL, DEVICE_NAME);
147+
if (IS_ERR(led_device.dev)) {
148+
pr_err("Failed to create the device file\n");
149+
ret = PTR_ERR(led_device.dev);
150+
goto fail3;
151+
}
152+
153+
pr_info("Device created on /dev/%s\n", DEVICE_NAME);
154+
155+
ret = gpio_request(leds[0].gpio, leds[0].label);
156+
157+
if (ret) {
158+
pr_err("Unable to request GPIOs for LEDs: %d\n", ret);
159+
goto fail4;
160+
}
161+
162+
ret = gpio_direction_output(leds[0].gpio, leds[0].flags);
163+
164+
if (ret) {
165+
pr_err("Failed to set GPIO %d direction\n", leds[0].gpio);
166+
goto fail5;
167+
}
168+
169+
return 0;
170+
171+
fail5:
172+
gpio_free(leds[0].gpio);
173+
174+
fail4:
175+
device_destroy(led_device.cls, led_device.dev_num);
176+
177+
fail3:
178+
class_destroy(led_device.cls);
179+
180+
fail2:
181+
cdev_del(&led_device.cdev);
182+
183+
fail1:
184+
unregister_chrdev_region(led_device.dev_num, DEVICE_CNT);
185+
186+
return ret;
187+
}
188+
189+
static void __exit led_exit(void)
190+
{
191+
gpio_set_value(leds[0].gpio, 0);
192+
gpio_free(leds[0].gpio);
193+
194+
device_destroy(led_device.cls, led_device.dev_num);
195+
class_destroy(led_device.cls);
196+
cdev_del(&led_device.cdev);
197+
unregister_chrdev_region(led_device.dev_num, DEVICE_CNT);
198+
}
199+
200+
module_init(led_init);
201+
module_exit(led_exit);
202+
203+
MODULE_LICENSE("GPL");

lkmpg.tex

+55
Original file line numberDiff line numberDiff line change
@@ -1816,6 +1816,61 @@ \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. 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).
1824+
1825+
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.
1826+
1827+
\begin{codebash}
1828+
cat /sys/kernel/debug/gpio
1829+
\end{codebash}
1830+
1831+
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+.
1832+
1833+
When using GPIO, you must set it as either output with \cpp|gpio_direction_output()| or \cpp|input with gpio_direction_input()|.
1834+
1835+
\begin{itemize}
1836+
\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.
1837+
\item when the GPIO is set as input, you can use \cpp|gpio_get_value()| to read whether the voltage is high or low.
1838+
\end{itemize}
1839+
1840+
\subsection{Control the LED's on/off state}
1841+
\label{sec:gpio_led}
1842+
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.
1843+
1844+
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.
1845+
1846+
\samplec{examples/led.c}
1847+
1848+
Make and install the module:
1849+
\begin{codebash}
1850+
make
1851+
sudo insmod led.ko
1852+
\end{codebash}
1853+
1854+
Changes the permissions of a file:
1855+
\begin{codebash}
1856+
sudo chmod 666 > /dev/gpio_led
1857+
\end{codebash}
1858+
1859+
Switch on the LED:
1860+
\begin{codebash}
1861+
echo '1' > /dev/gpio_led
1862+
\end{codebash}
1863+
1864+
Switch off the LED:
1865+
\begin{codebash}
1866+
echo '0' > /dev/gpio_led
1867+
\end{codebash}
1868+
1869+
Finally, remove the test module:
1870+
\begin{codebash}
1871+
sudo rmmod led
1872+
\end{codebash}
1873+
18191874
\section{Scheduling Tasks}
18201875
\label{sec:scheduling_tasks}
18211876
There are two main ways of running tasks: tasklets and work queues.

0 commit comments

Comments
 (0)