diff --git a/.ci/non-working b/.ci/non-working index 3164823..6f2c90e 100644 --- a/.ci/non-working +++ b/.ci/non-working @@ -3,3 +3,4 @@ bh_threaded intrpt vkbd syscall-steal +led diff --git a/examples/Makefile b/examples/Makefile index 01ab18b..df05b03 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 0000000..3e6cb82 --- /dev/null +++ b/examples/led.c @@ -0,0 +1,193 @@ +/* + * led.c - Using GPIO to control the LED on/off + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#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, 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) +{ + return 0; +} + +static int device_release(struct inode *inode, struct file *file) +{ + return 0; +} + +static ssize_t device_write(struct file *file, const char __user *buffer, + size_t length, loff_t *offset) +{ + 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("Failed to register character device, error: %d\n", 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"); diff --git a/lkmpg.tex b/lkmpg.tex index 1f5f668..cbfbe95 100644 --- a/lkmpg.tex +++ b/lkmpg.tex @@ -1816,6 +1816,65 @@ While you have seen lots of stuff that can be used to aid debugging here, there 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 a GPIO device driver, 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 input with \cpp|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 anode of the LED is connected to GPIO4, and the cathode is connected to GND. +For more details about the Raspberry Pi pin assignments, refer to \href{https://pinout.xyz/}{Raspberry Pi Pinout}. +The materials used include a Raspberry Pi 5, an LED, jumper 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.