Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add an LED driver example using GPIO #290

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .ci/non-working
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ bh_threaded
intrpt
vkbd
syscall-steal
led
1 change: 1 addition & 0 deletions examples/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
203 changes: 203 additions & 0 deletions examples/led.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
/*
* led.c - Using GPIO to control the LED on/off
*/

#include <linux/cdev.h>
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/fs.h>
#include <linux/gpio.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/printk.h>
#include <linux/types.h>
#include <linux/uaccess.h>
#include <linux/version.h>

#include <asm/errno.h>

#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");
50 changes: 50 additions & 0 deletions lkmpg.tex
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down