Kampis Elektroecke

Erstellen eines eigenen Treibers für den Raspberry Pi

Ich lese mich zur Zeit etwas intensiver in die Treiberentwicklung unter Linux ein und der Raspberry Pi ist zum Entwickeln von Treibern ein idealer Kandidat. Da die Codeentwicklung auf dem Raspberry Pi aber auf Grund der geringen Rechenleistung und der SD-Karte als Speicher alles andere als ideal ist, empfiehlt es sich hier, den Code auf einer anderen Maschine zu entwickeln und dann mit einem Cross Compiler für den Raspberry Pi zu kompilieren. 

Beschaffen der Kernel-Sourcen:

Für den Kompilierprozess des Kernel-Treibers sind die Kernel-Header des verwendeten Kernels wichtig. Linux-Treiber lassen sich nur laden, wenn die Versiosnummer der Kernel-Header mit der Version des verwendeten Kernels komplett übereinstimmt. Es muss also erst einmal die Kernelversion auf dem Raspberry Pi bestimmt werden:

Anschließend können die Kernelsourcen auf den Host (bei mir eine x86/x64 Maschine mit einem Ubuntu Betriebssystem) geladen werden:

Nun beginnt die Suche nach den Sourcen für die verwendete Version im offiziellen Repository. Dazu sucht man unter Branches die Version 4.14 und wählt diese aus. Ein guter Anhaltspunkt für die aktuelle Versionsnummer ist das Makefile im obersten Verzeichnisses des Repository:

Der letzte Sublevel der Version 4.14 ist demnach 39 gewesen. Den Sublevel 34 findet man über die Releases, direkt neben den Branches.

Durch öffnen der Versionen und überprüfen der Makefiles lässt sich der Commit für den Sublevel 34 herausfinden. In diesem Fall ist der Sublevel 39 der direkte Nachfolgelevel zu 34 und somit ist der erste Commit direkt der richtige. Von diesem Commit wird nun die ID benötigt. Diese lässt sich durch Öffnen des Commits herausfinden:

Der benötigte Commit trägt in diesem Fall die ID

Mit Hilfe dieser ID können die Linux-Sourcen auf den gewünschten Stand gebracht werden.

Im Makefile des Repositorys kann die korrekte Version überprüft werden:

Der benötigte Cross-Compiler:

Für die Erstellung des Moduls wird noch ein Cross-Compiler benötigt. Dieser wird, passend zu den Kernel-Sourcen, ebenfalls bei Git angeboten.

Der Cross-Compiler ist anschließend unter tools/arm-bcm2708/gcc-linaro-arm-linux-gnueabihf-raspbian-x64 zu finden. Damit der Compiler und die Kernel-Sourcen im weiteren Verlauf eleganter verwendet werden können, trage ich den Pfad des Cross-Compilers und der Kernel-Sourcen in die .bashrc ein. Anschließend wird die geänderte .bashrc neu geladen um die Pfade in der aktuelle Terminalsession nutzbar zu haben:

Ein neuer Kernel wird benötigt…:

Aus einem (mir noch unbekannten Grund) hat der vorhandene Kernel den erzeugten Treiber trotz der korrekten Version permanent abgelehnt. Als “Ausweg” habe ich den Kernel neu erstellt und auf mein Raspberry Pi kopiert. Danach ließ sich der Treiber ohne Probleme laden.

Um den Kernel für einen Raspberry Pi 3B+ zu kompilieren, wird in das Verzeichnis linux gewechselt und die Defaultkonfiguration für den Raspberry Pi erzeugt:

Danach kann der Kernel kompiliert werden:

Dieser Vorgang dauert, je nach verwendeter Maschine, gut 15 Minuten oder länger. Am Ende purzelt das Kernelimage raus, welches in dem Verzeichnis arch/arm/boot zu finden ist. Benötigt wird in diesem Fall die Datei zImage, die nun in das /boot-Verzeichnis des Raspberry Pi kopiert werden kann:

Auf dem Raspberry Pi wird der aktuelle Kernel entfernt (oder umbenannt, je nachdem ob man diesen noch behalten möchte):

Zuletzt muss der Raspberry Pi noch neu gestartet werden, damit der Kernel geladen wird:

Erstellen des Treibers:

Nach dem Reboot ist der neue Kernel einsatzbereit und alle Vorbereitungen zur Erstellung eines Treibers für den Raspberry Pi sind abgeschlossen. Da es sich bei meiner Host-Maschine um ein Ubuntu-System handelt, erstelle ich den Treiber mit Visual Studio Code. 

Für die Erstellung eines solchen Projektes mit Visual Studio Code wird die C/C++ Erweiterung benötigt. Wenn die Erweiterung installiert ist, wird ein neuer Projektordner angelegt und dem Arbeitsplatz hinzugefügt. Über die Tastenkombination Strg + Shift + P wird das Menü Edit Configurations… geöffnet, um die Einstellungen des Compilers anzupassen:

Es wird nun eine Datei namens c_cpp_properties.json angelegt und geöffnet. Sämtliche Compilereinstellungen, wie z. B. der zu verwendende Compiler, die Include-Pfade oder die Intellisensepfade werden hier eingetragen. Da es sich bei diesem Projekt nur um ein Makefile-Projekt handeln wird, werden nur die Include- und die Intellisensepfade angepasst:

Im nächsten Schritt wird über Tastenkombination Strg + Shift + P wird das Menü Configure Task aufgerufen. Jetzt wird die task.json angelegt und geöffnet. Diese Datei verwaltet die Aufgaben, die durchgeführt werden sollen:

Hier wurden zwei Aufgaben definiert

  • Build – Führt das Makefile im Projektverzeichnis mit der Option all aus
  • Clean – Führt das Makefile im Projektverzeichnis mit der Option clean aus

Über Datei Einstellungen Tastenkombinationen lassen sich die beiden Label Build und Clean auf beliebige Tasten legen, sodass diese ganz einfach ausgeführt werden können. Dazu einfach in dem o. g. Menü die keybindings.json öffnen:

Diese Datei wird dann entsprechend angepasst:

Hier habe ich beispielhaft die Taste F6 mit dem Task Build und die Taste F7 mit dem Task Clean belegt.

Nun muss noch das Makefile erstellt werden. Dafür kann dieses Template genommen werden:

Das Template kann für jeden beliebigen Raspberry Pi Treiber verwendet werden. Alle wichtigen Pfade sind über Umgebungsvariablen abgedeckt. Einzig der rot markierte Abschnitt muss den selben Namen wie das verwendete Sourcefile sein.

Nun kann der Code für den Treiber erstellt werden. Hierzu wird eine neue Datei namens SimpleDriver.c angelegt und mit dem folgenden Inhalt gefüllt:

Die Funktion dieses Treibers ist es, ein Gerät mit dem Namen Hello anzulegen, welcher bei einem read-Systemcall einmalig den Text Hello World zurück gibt.

Über die eben belegte Taste kann der Treiber mit Hilfe des Makefiles kompiliert werden. Der fertig kompilierte Treiber wird dann auf den Raspberry Pi übertragen:

Auf dem Raspberry Pi kann der Treiber dann geladen und mit einem cat wird ein read-Systemcall ausgelöst:

Über den Befehl rmmod kann der Treiber jederzeit wieder entladen werden:

Sämtliche Debugausgaben des Treibers werden über printk in das Systemlog /var/log/syslog geschrieben und können dort eingesehen werden.

Wie immer gibt es das komplette Projekt in meinem Raspberry Pi Repository zum Download.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.