-
Notifications
You must be signed in to change notification settings - Fork 0
led module
Door de opleiding Elektrotechniek van de Hogeschool Rotterdam is een Arch Linux image ontwikkeld die gebruikt kan worden op een DE1-SoC bord van Terasic. Hoe je deze Linux distributie kunt starten op het DE1-SoC bord vind je hier.
Op deze wiki-pagina wordt uitgelegd hoe je een character device kunt implementeren als een Linux kernel module waardoor je de leds kunt aansturen door naar het bestand /dev/leds te schrijven.
Linux deelt programma's op tussen kernel-space en user space. Zie figuur 1.

Kernel space is het geheugengebied waar de kernel van het besturingssysteem draait en die heeft volledige toegang tot de hardware en systeembronnen. Het is een beveiligde omgeving die niet direct toegankelijk is voor user space programma's.
User space programma's draaien niet in de kernel maar maken gebruiken van verschillende interface-lagen die de kernel aanbiedt om indirect met hardware te communiceren. Zo'n user space programma kan wel nog steeds root rechten nodig hebben om bepaalde acties uit te kunnen voeren.
De code in de kernel is uit te breiden met kernel modules.
Een kernel module draait in kernel space, heeft direct toegang tot de hardware en kan geïnstalleerd worden in de kernel door een superuser (root) met behulp van het insmod commando.
Met het rmmod commando kan een kernel module weer uit de kernel verwijderd worden.
Extra uitleg over kernel modules met een eenvoudig voorbeeld vind je op de wiki-pagina hello kernel module.
Om vanuit user space programma's gebruikt te kunnen maken van onze kernel module zonder superuser rechten, kunnen we onze module een character device aan laten maken.
Door onze driver een character device aan te laten maken wordt er onder de /dev map een virtuele file aangemaakt waar een user space programma of terminal naar kan schrijven en lezen.
De kernel module ziet deze schrijf- en leesacties en kan hierop reageren.
In de kernel module led_module.c wordt het character device genaamd /dev/leds gekoppeld aan de leds van je systeem.
Met als gevolg dat je leds vanuit user space aan kunt sturen door te schrijven naar de 'file' /dev/leds
De module led_module.c maakt een character device genaamd /dev/leds aan.
Je kunt deze kernel module als volgt compileren en installeren:
$ cd ~/CSC10_Development/led_driver
$ make
$ sudo insmod led_module.koMet het commando ls -l /dev/leds kun je zien dat het character device is aangemaakt.
Je kunt nu een karakter naar het device schrijven met het commando echo en de ASCII-code van dit karakter zal op de leds verschijnen.
# Zet de waarde 15 (0x0F ) op de leds:
$ echo -n -e "\x0F" >/dev/ledsDe optie -e van het echo-commando zorgt ervoor dat de escape-sequentie \x0F wordt
geïnterpreteerd en -n zorgt ervoor dat er geen nieuwe regelkarakter wordt toegevoegd.
Vanuit de kernel module worden kernel messages gestuurd om te later zien welke
operaties uitgevoerd worden.
Je kunt de laatste 5 kernel messages bekijken met het
commando dmesg:
$ dmesg -H | tail -5
[Nov22 20:27] MYLEDDEV: Create character device
[ +19.546603] MYLEDDEV: Device open
[ +0.000080] MYLEDDEV: Device write
[ +0.000010] MYLEDDEV: Data written 0x0F
[ +0.000023] MYLEDDEV: Device closeOmdat je de leds nu aan kunt sturen door simpelweg naar een bestand te schrijven
(zonder superuser rechten), kun je dit ook vanuit een C-programma doen.
Het C-programma genaamd leds.c dat laat zien hoe je dit kunt doen:
# include <stdio.h>
# include <unistd.h>
int main () {
FILE * fp = fopen ("/dev/leds", "w");
if (fp != NULL) {
int i;
for (i = 0; i < 17; i++) {
printf("Send 0x%02X to the leds\n", i);
fputc(i, fp);
fflush(fp);
sleep(1);
}
fclose(fp);
} else {
printf("Error: can not open /dev/leds\n");
}
return 0;
}De functie fopen opent het character device /dev/leds als een bestand voor schrijven ("w"). Met de functie fputc wordt een karakter naar het device geschreven.
De functie fflush zorgt ervoor dat de buffer wordt geleegd, zodat de waarde direct naar de leds wordt gestuurd.
Met de functie fclose wordt het bestand weer gesloten.
Al de bovenstaande functies zijn gedeclareerd in stdio.h.
Na het versturen van een karakter wordt steeds 1 seconde gewacht door de functie sleep aan te roepen, die gedeclareerd is in unistd.h.
Dit programma kun je als volgt compileren en uitvoeren:
$ gcc leds.c -o leds
$ ./leds
Send 0x00 to the leds
Send 0x01 to the leds
Send 0x02 to the leds
Send 0x03 to the leds
Send 0x04 to the leds
...Het C-programma genaamd leds_try_read.c probeert te lezen van het character device /dev/leds.
Dit programma kun je als volgt compileren en uitvoeren:
$ gcc leds_try_read.c -o leds_try_read
$ ./leds_try_read
Error: can not open /dev/leds
Error message: Permission deniedJe ziet dat het character device /dev/leds niet geopend kan worden om te lezen ("r").
Hieronder wordt de code in led_module.c eerst globaal en daarna stap voor stap toegelicht.
We beginnen onderaan in het bestand.
De macro module_platform_driver registreert de platform driver genaamd MYLEDDEV bij de Linux-kernel.
De struct mijn_module_driver bevat pointers naar functies die worden aangeroepen
als de driver wordt geladen (init_handler) en verwijderd (clean_handler).
Deze functies worden aangeroepen door de kernel wanneer een device wordt gevonden in
de devicetree die overeenkomt met de driver (op basis van het compatible veld).
In de functie init_handler wordt het character device /dev/leds aangemaakt en
wordt het adres van de PIO-module opgeslagen in de globale pointer PIO_ptr.
In de functie clean_handler wordt het character device weer verwijderd.
De functies my_dev_open, my_dev_release en my_dev_write worden aangeroepen als een user space programma het character device opent, sluit, of beschrijft.
Deze koppelingen worden gemaakt in de struct my_dev_fops.
Er zijn nog meer koppelingen mogelijk, zoals lezen (.read), maar deze zijn in deze character device niet geïmplementeerd.
#define DEVNAME "MYLEDDEV"
static volatile unsigned int *PIO_ptr;
static dev_t dev = 0;
static int dev_major = 0;
static struct class *my_dev_class = NULL;
static struct cdev my_dev_cdev;
static const struct file_operations my_dev_fops = {
.open = my_dev_open,
.release = my_dev_release,
.write = my_dev_write
};
static const struct of_device_id mijn_module_id[] ={
{.compatible = "leds"},
{}
};
static struct platform_driver mijn_module_driver = {
.driver = {
.name = DEVNAME,
.owner = THIS_MODULE,
.of_match_table = of_match_ptr(mijn_module_id),
},
.probe = init_handler,
.remove_new = clean_handler
};Zie hier voor een uitleg waarom .remove_new in plaats van .remove gebruikt is.
static int init_handler(struct platform_device *pdev)
{
printk(KERN_INFO DEVNAME ": Create character device\n");
// allocate a range of char device numbers
// in this case only one minor number
// the major number will be assigned dynamically
int err = alloc_chrdev_region(&dev, 0, 1, "leds");
if (err < 0) {
printk(KERN_ALERT DEVNAME ": ERROR: no major device number available\n");
return err;
}
// extract the assigned major device number
dev_major = MAJOR(dev);
// create device class
// class leds is a reserved class name and can not be used. Therefore we use my_leds
my_dev_class = class_create("my_leds");
if (IS_ERR(my_dev_class)) {
printk(KERN_ALERT DEVNAME ": ERROR: class can not be created for device\n");
err = PTR_ERR(my_dev_class);
goto cleanup_chrdev_region;
}
// my_dev_uevent is called when a device is added to this class
my_dev_class->dev_uevent = my_dev_uevent;
// init new device
cdev_init(&my_dev_cdev, &my_dev_fops);
my_dev_cdev.owner = THIS_MODULE;
// create device in /sys/devices/virtual/my_leds/leds
err = cdev_add(&my_dev_cdev, MKDEV(dev_major, 0), 1);
if (err < 0) {
printk(KERN_ALERT DEVNAME ": ERROR: device number can not be added for device\n");
goto cleanup_class;
}
// create device node /dev/leds
struct device *my_dev = device_create(my_dev_class, NULL, MKDEV(dev_major, 0), NULL, "leds");
if (IS_ERR(my_dev)) {
printk(KERN_ALERT DEVNAME ": ERROR: device can not be created for driver\n");
err = PTR_ERR(my_dev);
goto cleanup_cdev;
}
// map the PIO device registers into virtual memory
void *mem_ptr = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(mem_ptr)) {
printk(KERN_ALERT DEVNAME ": ERROR: no base address found for PIO device\n");
err = PTR_ERR(mem_ptr);
goto cleanup_device;
}
PIO_ptr = mem_ptr;
return 0;
// cleanup on errors
cleanup_device:
device_destroy(my_dev_class, MKDEV(dev_major, 0));
cleanup_cdev:
cdev_del(&my_dev_cdev);
cleanup_class:
class_destroy(my_dev_class);
cleanup_chrdev_region:
unregister_chrdev_region(dev, 1);
return err;
}static void clean_handler(struct platform_device *pdev)
{
printk(KERN_INFO DEVNAME ": Destroy character device\n");
device_destroy(my_dev_class, MKDEV(dev_major, 0));
cdev_del(&my_dev_cdev);
class_destroy(my_dev_class);
unregister_chrdev_region(dev, 1);
}static int my_dev_uevent(const struct device *dev, struct kobj_uevent_env *env)
{
int ret = add_uevent_var(env, "DEVMODE=%#o", 0666);
if (ret < 0) {
printk(KERN_ALERT DEVNAME ": ERROR: add_uevent_var failed\n");
return ret;
}
return 0;
}static int my_dev_open(struct inode *inode, struct file *file)
{
printk(KERN_INFO DEVNAME ": Device open\n");
if ((file->f_flags & O_WRONLY) != O_WRONLY) {
printk(KERN_ALERT DEVNAME ": ERROR: Device opened not in write only mode\n");
return -EACCES;
}
return 0;
}static int my_dev_release(struct inode *inode, struct file *file)
{
printk(KERN_INFO DEVNAME ": Device close\n");
return 0;
}static ssize_t my_dev_write(struct file *file, const char __user *buf, size_t count, loff_t *offset)
{
char buffer[128];
size_t maxdatalen = sizeof buffer;
printk(KERN_INFO DEVNAME ": Device write\n");
if (count < maxdatalen) {
maxdatalen = count;
}
unsigned long ret = copy_from_user(buffer, buf, maxdatalen);
if (ret) {
printk(KERN_ALERT DEVNAME ": ERROR: copy_from_user failed\n");
return ret;
}
size_t i;
for (i = 0; i < maxdatalen; i++) {
int value = (int)buffer[i];
printk(KERN_INFO DEVNAME ": Data written 0x%02X\n", value);
*PIO_ptr = value;
}
return maxdatalen;
}[1] T. Gayet, Linux Kernel Programming: Developing kernel architecture and device drivers for character, block, USB, and network interfaces. BPB Publications, 2025.