ce12213 created page: Code authored by Christian Eckl's avatar Christian Eckl
## 2.2 sevenseg.c
### 2.2.1 Header-Files und Definition von Makros
Damit alle Funktionen der im Code verwendeten Funktionen funktionieren müssen einige Header-Files eingebunden werden:
```
#include <linux/init.h>
#include <linux/module.h>
#include <linux/gpio.h>
#include <linux/fs.h>
#include <asm/uaccess.h>
#include <linux/slab.h>
#include <linux/kernel.h>
```
- linux/init.h : Kernel-Modul Headerfile
- linux/module.h : Kernel-Modul Headerfile
- linux/gpio.h : Headerfile für die GPIO Funktionen
- linux/fs.h : Headerfile für die file_operations des Chardevice
- asm/uaccsess.h : Headerfile für die Kommunikation zwischen Kernel-Space ud User-Space
- linux/slab.h : Headerfile für die Speicherreservierung
- linux/kernel.h : Headerfile für die KERN_INFO Ausgabe
```
#define A1 17 // Pin 17 wird durch A1 ersetzt (7SEG a)
#define A2 27 // Pin 27 wird durch A2 ersetzt (7SEG b)
#define A3 22 // Pin 12 wird durch A3 ersetzt (7SEG c)
#define A4 5 // Pin 5 wird durch A4 ersetzt (7SEG d)
#define A5 6 // Pin 6 wird durch A5 ersetzt (7SEG e)
#define A6 13 // Pin 13 wird durch A6 ersetzt (7SEG f)
#define A7 19 // Pin 19 wird durch A7 ersetzt (7SEG g)
#define A8 26 // Pin 26 wird durch A8 ersetzt (7SEG dot)
#define SUCCESS 0
#define DEVICE_NAME "sevenseg" // Device-Name
#define BUF_LEN 1 /* Maximale Länge der Nachricht vom Treiber */
```
### 2.2.2 Lizensierung und Modul Dokumentation
```c
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Christian Eckl <christian.eckl@stud.th-deg.de>");
MODULE_DESCRIPTION("7-Segment Driver");
MODULE_SUPPORTED_DEVICE("Raspberry Pi 3 Model B V1.2");
MODULE_VERSION("0.1");
```
Das Makro `MODULE_LICENSE("GPL");` und die Lizenz-ID "GPL" definiert die Lizenz als `[GNU Public License v2 or later]`.
`MODULE_AUTHOR, MODULE_DESCRIPTION, MODULE_SUPPORTED_DEVICE und MODULE_VERSION ` sind Makros für die Modulinformationen welche über den Befehl `modinfo sevenseg.ko`
ausgelesen werden könnnen.
```
filename: /home/pi/Sevenseg/sevenseg.ko
version: 0.1
description: 7-Segment Driver
author: Christian Eckl
license: GPL
srcversion: 8A2C4B94DEC237218B6EC49
depends:
vermagic: 4.9.62-v7+ SMP mod_unload modversions ARMv7 p2v8
```
### 2.2.3 GPIO-Implementierung
Um die 7-Segemntanzeige an einem Raspberry Pi zu betreiben müssen die GPIO-Pins (engl. GPIO – general purpose input/output) über den Treiber angesprochen und verwaltet werden die sgeschieht im folgenden Abschnitt.
Als erstes müssen die Pins initialisiert werden:
```c
void seven_gpio_init(void) {
printk(KERN_INFO "SEVENSEG: starting gpio...");
gpio_request(A1, "A1");
gpio_request(A2, "A2");
gpio_request(A3, "A3");
gpio_request(A4, "A4");
gpio_request(A5, "A5");
gpio_request(A6, "A6");
gpio_request(A7, "A7");
gpio_request(A8, "A8");
gpio_direction_output(A1, 0);
gpio_direction_output(A2, 0);
gpio_direction_output(A3, 0);
gpio_direction_output(A4, 0);
gpio_direction_output(A5, 0);
gpio_direction_output(A6, 0);
gpio_direction_output(A7, 0);
gpio_direction_output(A8, 0);
printk(KERN_INFO "SEVENSEG: starting gpio done.");
}
```
Die GPIO-Pins müssen zuerst für das Modul reserviert werden, dies wird mit `gpio_request(unsigned int gpio, const char *label)` erreicht.
Um mit den Pins arbeiten zu können muss noch festgelegt werden, dass es Output-Pins sind was, mit `gpio_direction_output(unsigned int gpio, int value)` bewerkstelligt wird.
Wenn man das Modul entladen will müssen die GPIO-Pins wieder freigegeben werden was im folgenden Codeabschnitt durch `gpio_free(const int gpio)` realisiert wurde:
```c
void seven_gpio_exit(void) { //Beim Rausladen des LKM werden die Pins wieder freigegeben (gpio_free)
printk(KERN_INFO "SEVENSEG: stopping gpio...");
gpio_free(A1);
gpio_free(A2);
gpio_free(A3);
gpio_free(A4);
gpio_free(A5);
gpio_free(A6);
gpio_free(A7);
gpio_free(A8);
printk(KERN_INFO "SEVENSEG: stopping gpio done.");
}
```
Um Zeichen auf der 7-Segment Anzeige ausgeben zu können müssen für jedes Zeichen verschiedene Pins angesprochen und auf `HIGH = 1` oder `LOW = 0` gesetzt werden:
```c
void seven_status(int display) { //Switch-case fuer die Anzeigemoeglichkeiten auf der 7 Segment-Anzeige
switch (display) {
case 0 :
gpio_set_value(A1, 0);
gpio_set_value(A2, 0);
gpio_set_value(A3, 0);
gpio_set_value(A4, 0);
gpio_set_value(A5, 0); // 7-Segment zeigt 0 an
gpio_set_value(A6, 0);
gpio_set_value(A7, 0);
gpio_set_value(A8, 0);
break;
case 1:
gpio_set_value(A1, 1);
gpio_set_value(A2, 1);
gpio_set_value(A3, 1);
gpio_set_value(A4, 1);
gpio_set_value(A5, 1); // 7-Segment zeigt 1 an
gpio_set_value(A6, 1);
gpio_set_value(A7, 1);
gpio_set_value(A8, 1);
break;
case 2:
gpio_set_value(A1, 0);
gpio_set_value(A2, 0);
gpio_set_value(A3, 0);
gpio_set_value(A4, 0);
gpio_set_value(A5, 0); // 7-Segment zeigt 2 an
gpio_set_value(A6, 0);
gpio_set_value(A7, 0);
gpio_set_value(A8, 0);
break;
case 3:
gpio_set_value(A1, 0);
gpio_set_value(A2, 0);
gpio_set_value(A3, 0);
gpio_set_value(A4, 0);
gpio_set_value(A5, 0); // 7-Segment zeigt 3 an
gpio_set_value(A6, 0);
gpio_set_value(A7, 0);
gpio_set_value(A8, 0);
break;
case 4:
gpio_set_value(A1, 0);
gpio_set_value(A2, 0);
gpio_set_value(A3, 0);
gpio_set_value(A4, 0);
gpio_set_value(A5, 0); // 7-Segment zeigt 4 an
gpio_set_value(A6, 0);
gpio_set_value(A7, 0);
gpio_set_value(A8, 0);
break;
case 5:
gpio_set_value(A1, 0);
gpio_set_value(A2, 0);
gpio_set_value(A3, 0);
gpio_set_value(A4, 0);
gpio_set_value(A5, 0); // 7-Segment zeigt 5 an
gpio_set_value(A6, 0);
gpio_set_value(A7, 0);
gpio_set_value(A8, 0);
break;
case 6:
gpio_set_value(A1, 0);
gpio_set_value(A2, 0);
gpio_set_value(A3, 0);
gpio_set_value(A4, 0);
gpio_set_value(A5, 0); // 7-Segment zeigt 6 an
gpio_set_value(A6, 0);
gpio_set_value(A7, 0);
gpio_set_value(A8, 0);
break;
case 7:
gpio_set_value(A1, 0);
gpio_set_value(A2, 0);
gpio_set_value(A3, 0);
gpio_set_value(A4, 0);
gpio_set_value(A5, 0); // 7-Segment zeigt 7 an
gpio_set_value(A6, 0);
gpio_set_value(A7, 0);
gpio_set_value(A8, 0);
break;
case 8:
gpio_set_value(A1, 0);
gpio_set_value(A2, 0);
gpio_set_value(A3, 0);
gpio_set_value(A4, 0);
gpio_set_value(A5, 0); // 7-Segment zeigt 8 an
gpio_set_value(A6, 0);
gpio_set_value(A7, 0);
gpio_set_value(A8, 0);
break;
case 9:
gpio_set_value(A1, 0);
gpio_set_value(A2, 0);
gpio_set_value(A3, 0);
gpio_set_value(A4, 0);
gpio_set_value(A5, 0); // 7-Segment zeigt 9 an
gpio_set_value(A6, 0);
gpio_set_value(A7, 0);
gpio_set_value(A8, 0);
break;
default:
gpio_set_value(A1, 1);
gpio_set_value(A2, 1);
gpio_set_value(A3, 1);
gpio_set_value(A4, 1);
gpio_set_value(A5, 1); //default alle Pins auf LOW gesetzt
gpio_set_value(A6, 1);
gpio_set_value(A7, 1);
gpio_set_value(A8, 1);
break;
}
```
Nachdem die Funktion mit der gewünschten Ausgabe `seven_status(int display)` aufgerufen wurde, werden in der switch-case-Funktion die GPIO-Pins angesprochen
und mit `gpio_set_value(unsigned int gpio, int value)` auf HIGH oder LOW gesetzt.
Quelle: [GPIO in the kernel] (https://lwn.net/Articles/532714/)
### 2.2.4 Character Device File
Um später mit dem Treiber im Kernel-Space kommunizieren zu können, wird ein Character Device Driver benötigt, der im Kernel-Space eine Node-Datei erstellt, auf die der User
mit `& echo "display" >> /dev/sevenseg` schreiben kann und das Modul die Eingabe verarbeitet.
Als erstes wird hierzu folgende globale Variablen und Strukturfunktionen:
```c
/* Globale Variablen für die Chracter-Device-Schnittstelle */
static ssize_t seven_write(struct file *, const char *, size_t, loff_t *);
struct file_operations seven_fops = {
write : seven_write,
};
/* Globale Variablen für die Chracter-Device-Schnittstelle */
static char *msg = NULL;
static int Major;
```
Die `file_operations` sind im Header linux/fs.h definiert und enthalten Zeiger auf Funktionen, die vom Treiber definiert werden und verschiedene Operationen auf dem Gerät ausführen.
Danach wird der Treiber bzw. das Gerät registriert, dabei wird dem Device-File eine Major Nummer zugewiesen, diese gibt an welcher Treiber welche Gerätedatei verarbeitet.
Man kann seine eigene Major Nummer bestimmen muss aber darauf achten das sie noch nicht vergeben ist. Um dieses Problem zu umgehen gibt man bei der Funktion
`register_chrdev(unsigned int major, const char *name, struct file_operations *fops)` bei `unsigned int major` 0 ein und lässt sich dann durch die Rückgabe eine freie Major Nummer zuteilen
wie im folgenden Code-Abschnitt:
```c
int seven_dev_init(void)
{
Major = register_chrdev(0, DEVICE_NAME, &seven_fops);
if (Major < 0) {
printk(KERN_ALERT "SEVENSEG: Registering char device failed with %d\n", Major);
return Major;
}
printk(KERN_INFO "SEVENSEG: sevenseg driver loaded with major %d\n", Major);
printk(KERN_INFO "SEVENSEG: >> $ mknod /dev/%s c %d 0\n", DEVICE_NAME, Major); //Info welche Nodedatei mit welchen Parametern erstellt werden soll
msg = (char *)kmalloc(32, GFP_KERNEL); //Speicherreservierung fuer die Uebertragung von Kernelspace zu Userspace
if (msg != NULL)
printk("malloc allocator address: 0x%p\n", msg);
return SUCCESS;
}
```
Mit der KERN_INFO lassen wir uns hier den genauen Pfad ausgeben in der die Node Datei erstellt werden soll in unserem Fall wird mit
> $ mknod /dev/sevenseg 257 0
eine Datei mit Majornummer 257 und Minor-Nummer 0 angelegt. Die Minor Nummer wird nur verwendet wenn ein Treiber mehrere Geräte ansprechen soll.
Zu guter letzt muss noch Speicher für die Übertragung des Puffers reserviert werden da die Eingaben in die Nodedatei nur indirekt ausgelesen werden können.
In der folgenden Funktion verwenden wir die bereits erstellte Struktur der write-Funktion:
```c
static ssize_t seven_write(struct file *filep, const char *buffer, size_t len, loff_t * offset)
{
short count;
memset(msg, 0, 32);
count = copy_from_user(msg, buffer, len); // Kopieren des Strings aus dem User-Space which open and write this device
if (msg[0] == '1') {
seven_status(1);
}
else if (msg[0] == '2') {
seven_status(2);
}
else if (msg[0] == '3') {
seven_status(3);
}
else if (msg[0] == '4') {
seven_status(4);
}
else if (msg[0] == '5') {
seven_status(5);
}
else if (msg[0] == '6') {
seven_status(6);
}
else if (msg[0] == '7') {
seven_status(7);
}
else if (msg[0] == '8') {
seven_status(8);
}
else
{
seven_status(0);
}
return len;
}
```
Mit der Funktion `memset(void *s, int c, size_t n)` wird die Nachricht aus dem Arbeitsspeicher in den zuvor angelgetnen msg-char geladen und darf eine maximalen Bitlänge von 32 haben
Die Funktion `copy_from_user(void * , const void __user *, unsigned longn);` kopiert die Nachricht aus dem User-Space in den Puffer des Kernel-Space.
Danach wird über die if-Funktionen abgeglichen welchen Wert die Nachricht hat und initialisiert darüber die Funktion `seven_status(display)`.
Quellen:
[man7.org] (http://man7.org/linux/man-pages/man3/memset.3.html)
[User Space Memory Access] (https://www.fsl.cs.sunysb.edu/kernel-api/re257.html)
Wenn das Modul entladen wird wird folgende Funktion aufgerufen:
```c
void seven_dev_exit(void)
{
unregister_chrdev(Major, "sevenseg");
printk(KERN_INFO "SEVENSEG: device file closed.\n");
}
```
Hier wird durch die Funktion `unregister_chrdev(unsigned int major, const char *name)` der Treiber abgemeldet in den Parametern wird die Majornummer und der DEVICE_NAME angegeben.
Quelle: [Linux Loadable Kernel Module HOWTO] (http://tldp.org/LDP/lkmpg/2.6/html/lkmpg.html#AEN569)
### 2.2.5 Die Modulfunktionen
Zu guter letzt müssen alle Initialisierungsfunktionen über die Modulinitialiesierung aufgerufen werden damit beim Laden des Treibers alle Funktionen garantiert sind:
```c
static int __init seven_init(void) {
printk(KERN_INFO "SEVENSEG: starting...");
seven_gpio_init();
seven_dev_init();
printk(KERN_INFO "SEVENSEG: starting done.");
return 0;
}
static void __exit seven_exit(void) {
printk(KERN_INFO "SEVENSEG: stopping...");
seven_gpio_exit();
seven_dev_exit();
printk(KERN_INFO "SEVENSEG: stopping done.");
}
module_init(seven_init); //Alle Funktionen in led_init werden im Modul geladen
module_exit(seven_exit); //Alle Funktionen in led_init werden entladen
```
\ No newline at end of file