Skip to content

Commit e061eaf

Browse files
authored
Merge pull request #290 from jeremy90307/master
Add an LED driver example using GPIO
2 parents eccaa3b + 0beffd5 commit e061eaf

File tree

4 files changed

+254
-0
lines changed

4 files changed

+254
-0
lines changed

Diff for: .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

Diff for: 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

Diff for: examples/led.c

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

Diff for: lkmpg.tex

+59
Original file line numberDiff line numberDiff line change
@@ -1816,6 +1816,65 @@ \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 a GPIO device driver, 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 input with \cpp|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 anode of the LED is connected to GPIO4, and the cathode is connected to GND.
1852+
For more details about the Raspberry Pi pin assignments, refer to \href{https://pinout.xyz/}{Raspberry Pi Pinout}.
1853+
The materials used include a Raspberry Pi 5, an LED, jumper wires, and a 220$\Omega$ resistor.
1854+
1855+
\samplec{examples/led.c}
1856+
1857+
Make and install the module:
1858+
\begin{codebash}
1859+
make
1860+
sudo insmod led.ko
1861+
\end{codebash}
1862+
1863+
Switch on the LED:
1864+
\begin{codebash}
1865+
echo "1" | sudo tee /dev/gpio_led
1866+
\end{codebash}
1867+
1868+
Switch off the LED:
1869+
\begin{codebash}
1870+
echo "0" | sudo tee /dev/gpio_led
1871+
\end{codebash}
1872+
1873+
Finally, remove the module:
1874+
\begin{codebash}
1875+
sudo rmmod led
1876+
\end{codebash}
1877+
18191878
\section{Scheduling Tasks}
18201879
\label{sec:scheduling_tasks}
18211880
There are two main ways of running tasks: tasklets and work queues.

0 commit comments

Comments
 (0)