Forum: PC-Programmierung Linux: SPI Device Treiber als character device


Announcement: there is an English version of this forum on EmbDev.net. Posts you create there will be displayed on Mikrocontroller.net and EmbDev.net.
von seho85 (Gast)


Bewertung
0 lesenswert
nicht lesenswert
Hallo Zusammen,

ich bin gerade dabei meine ersten Kernel Module zu schreiben, und hätte 
einige Fragen auf die ich leider per Recherche keine Antwort finde 
konnte.

Ich weiß auch nicht ob ich hier 100% richtig bin, leider kenne ich keine 
deutschsprachiges Embedded Linux Forum :)

Da ich aber für viele Einsteigerfragen Rund um den STM32 hier gute Infos 
gefunden habe, werfe ich meine Fragen mal hier in die Runde, und hoffe 
auf jemanden der sie beantworten kann.

Was ich eigentlich vorhabe:
Ich habe ein STM32F3 Discovery Board, auf diesem läuft ein SPI-Slave, 
über einfaches Protokoll können die LEDs auf dem Discovery Board ein und 
ausgeschaltet werden. Für diese Ansteuerung möchte ich einen Kernel 
Treiber erstellen.

Prinzipiell funktioniert das ganze auch schon. Auf meinem Embedded Board 
(BeaglboneBlack), aus dem Userspace, per spidev, kann ich die LEDs auf 
dem STM Board ansteuern.

Nun habe ich mich daran gemacht das ganze als Kernel Treiber zu 
realisieren.

Ein einfaches Kernelmodul, welches beim Initialisieren ein paar LEDs auf 
dem STM Board blinken lässt war auch recht schnell erstellt.

Nun kommen wir zu meinen eigentlichen Frage:
Ich möchte das aus dem Userspace, ähnlich wie bei der Verwendung mit 
spidev, auf das Treibermodul zugegriffen werden kann.

Dafür kommt soweit ich weiß unter Linux ein "Character Device" in Frage.

Muss ich nun dafür in mein Treibermodul ein eigenes "Character Device" 
implementieren?

Oder gibt es evtl. eine andere Möglichkeit aus dem Userspace auf ein 
"SPI-Device" zuzugreifen?

Ich habe mit innerhalb des Linux Kernels den Quellcode für den MAX7301 
(gpio-max7301.c) angesehen (und ein paar weitere).

Dabei habe ich gesehen das diese Lese- und Schreibfunktionen 
bereitstellen:
1
static int max7301_spi_read(struct device *dev, unsigned int reg)...
2
static int max7301_spi_write(struct device *dev, unsigned int reg, unsigned int val)...

Leider war ich nicht in der Lage herauszufinden "wer" diese Funktionen 
den eigentlich aufruft.

Ich vermute irgendetwas aus dem Linux SPI-Treiber Subsystem, aber eine 
richtige Antwort konnte ich nicht finden.

Evtl. wäre das auch ein Punkt an dem ich ansetzen könnte.

Hat jemand dazu eine Idee?

Vielen Dank für alle Antworten im Vorraus.

Gruß,
Sebastian (seho85)

: Verschoben durch Moderator
von PittyJ (Gast)


Bewertung
0 lesenswert
nicht lesenswert
Warum muss das unbedingt ein Kernelmodul werden?
User-Space Programme lassen sich einfach kompilieren und kopieren. Auch 
per Crosscompiler Da kann auch die Linux-Version darunter mal gewechselt 
werden(neuer Kernel)
Bei Kernel-Treiber müssen diese immer mit dem Kernel übereinstimmen. Und 
auch die passenden Kernel-Header müssen beim Kompilieren vorhanden sein.
Nach einen Kernel-Upgrade geht dann evtl nichts, da die Versionsnummer 
verglichen wird.

Ich bin von Kernelmodulen weg, und mache alles im User-Space. Auch SPI 
Kommunikation.

von Clemens L. (c_l)


Bewertung
0 lesenswert
nicht lesenswert
seho85 schrieb:
> Leider war ich nicht in der Lage herauszufinden "wer" diese Funktionen
> den eigentlich aufruft.

ts->read und ts->write in gpio-max730x.c.

> Muss ich nun dafür in mein Treibermodul ein eigenes "Character Device"
> implementieren?

Nein. Siehe Documentation/spi/spi-summary.

von seho85 (Gast)


Bewertung
0 lesenswert
nicht lesenswert
Hi,

danke für die Antworten.

Irgendwie muss ich wohl was an den Augen gehabt haben...
Vielen Dank für den Hinweis.

Mir nun ist nun im groben klar wie die der Zugriff funktioniert.

Die Funktionen werden letztendlich über das Linux GPIO Subsystem 
aufgerufen.

>> Muss ich nun dafür in mein Treibermodul ein eigenes "Character Device"
>> implementieren?

>Nein. Siehe Documentation/spi/spi-summary.

Ich habe das spi-summary nochmals gelesen - Ich verstehe nur leider 
immer noch nicht wie auf mein eigenes SPI Device dann aus dem Userspace 
zugreifen kann.

Kannst du mir da bitte einen weiteren Hinweis geben?

>Bei Kernel-Treiber müssen diese immer mit dem Kernel übereinstimmen. Und
>auch die passenden Kernel-Header müssen beim Kompilieren vorhanden sein.
>Nach einen Kernel-Upgrade geht dann evtl nichts, da die Versionsnummer
>verglichen wird.

Dies ist momentan kein Problem - Kernel sowie Kernelmodul mithilfe von 
yocto erstellt. Upgrades für Kernel Module und Software kommen alle aus 
dem yocto system.

Oder übersehe ich bei dieser Annahme etwas?

Generell ist das ein Lernprojekt für mich.

Nochmals vielen Dank für die Antworten.


Gruß Sebastian

von PittyJ (Gast)


Bewertung
0 lesenswert
nicht lesenswert
Aus mehreren Funktionen zusammenkopiert, deshalb nicht kompilierbar. 
Aber als Beispiel:

#include <linux/spi/spidev.h>
_fd = open(devicefile,O_RDWR);

struct spi_ioc_transfer s_SpiTransfer[2];
unsigned char           uc_TXBuffer[10];
unsigned char           uc_RXBuffer[10];

SpiTransfer[0]->tx_buf        = (__u64) uc_TXBuffer;
SpiTransfer[0]->rx_buf        = (__u64) uc_RXBuffer;
SpiTransfer[0]->len           = ui_TransferSizeInBytes; // anzahl bytes
SpiTransfer[0]->speed_hz      = 25000000;
SpiTransfer[0]->delay_usecs   = 0;
SpiTransfer[0]->bits_per_word = 8;

// TX Daten einsetzen (Beispielhaft)
uc_TXBuffer[0] = (unsigned char)( ui_RegisterNumber>>5 & 0x7f);
uc_TXBuffer[1] = (unsigned char)( ui_RegisterNumber<<3 & 0xf8);

i_Status = ioctl(_fd,
                         SPI_IOC_MESSAGE(1),
                         &s_SpiTransfer[0]

von A. H. (ah8)


Bewertung
0 lesenswert
nicht lesenswert
Morning,

kennst Du schon dieses Buch: https://lwn.net/Kernel/LDD3/ ? Eine sehr 
gute Einführung, die genau diese Art von Fragen zu beantworten versucht. 
Das Buch gibt es meines Wissens nach auch in der deutschen Übersetzung, 
ob die kostenlos zum Download zur Verfügung steht, kann ich aber nicht 
sagen.

von seho85 (Gast)


Bewertung
0 lesenswert
nicht lesenswert
Hallo PittiJ,

über einen Aufruf in dieser Art habe ich auch schon nachgedacht.

Nur welchen konkreten "DevicePath" muss denn angeben um auf mein SPI 
Device zu zuzugreifen?

Verwende ich z.B. spidev, habe ich zumindest den Eintrag unter 
/dev/spi<major>.<cs>

Mein eigener Treiber stellt mir so etwas nicht bereit - zumindest 
solange ich nicht explizit ein "characterdevice" in den Treiber 
implementiert habe.

Vielleicht übersehe ich nur irgendetwas....

>kennst Du schon dieses Buch: https://lwn.net/Kernel/LDD3/ ? Eine sehr
>gute Einführung, die genau diese Art von Fragen zu beantworten versucht.
>Das Buch gibt es meines Wissens nach auch in der deutschen Übersetzung,
>ob die kostenlos zum Download zur Verfügung steht, kann ich aber nicht
>sagen.

Danke für den Tipp, ich werde mich dort mal hineinlesen.

Ich verwende folgendes Buch:
https://www.dpunkt.de/buecher/12176/9783864902888-linux-treiber-entwickeln.html

Leider ist das Thema SPI innerhalb dieses Buches nur kurz Umrissen, es 
wird erklärt wie man ein SPIDevice selbst erstellt, und wie man per 
"spidev" aus dem Userspace SPI Kommunikation betreiben kann.

Leider fehlt mir die Information wie man diesen SPI Device Treiber 
konsumiert.

Gruß,
Sebastian (seho85)

von Sheeva P. (sheevaplug)


Bewertung
0 lesenswert
nicht lesenswert
seho85 schrieb:
> Ich habe das spi-summary nochmals gelesen - Ich verstehe nur leider
> immer noch nicht wie auf mein eigenes SPI Device dann aus dem Userspace
> zugreifen kann.

Dein Treiber erzeugt beim Laden eine Datei (zB. /dev/myCoolSpiDevice), 
die ein character device repräsentiert. Dein Userspace-Programm öffnet 
diese Datei dann r/w, schreibt Deine Befehle hinein und liest die 
Antworten des SPI-Geräts daraus.

Als Beispielcode für das Kernelmodul kannst du den Code von spidev.ko 
benutzen. Beispielcode für Dein Userspace-Programm findest Du unter 
Documentation/spi/spidev_test.c in Deinem Kernelsource-Verzeichnis.

von seho85 (Gast)


Bewertung
0 lesenswert
nicht lesenswert
Hi,

danke für die Ausführungen. Das bestätigt auch meine Vermutung die 
langsam aufkam.

So habe ich das nun implementiert:
1
module_init -> registriere spi_device (erstellt lokales struct spi_device)
2
            -> erstelle chardev
3
                 ->per struct file_operation open,read,write,realease funktionen definiert
4
5
write_function()
6
{
7
    leseDatenVonCharDev();
8
    erstelleSPISequenz();
9
    schreibe per spi_write([lokales struct spi_device], pBuffer, nByte)
10
}

Ist das denn Grundsätzlich die richtige Herangehensweise? Der Treiber 
muss "selbst (per code)" das Character Device erstellen?

Per "echo >" und cat liefert der Treiber mir die erwarteten Werte.

Ich werde mir den originale spidev Quellcode mal etwas genauer ansehen.

Ich habe mich zulange mit diesem Kommentar beschäftigt:
>> Muss ich nun dafür in mein Treibermodul ein eigenes "Character Device"
>> implementieren?

>Nein. Siehe Documentation/spi/spi-summary.

Daraufhin habe ich nach einem anderem Weg gesucht auf meinen Treiber 
zuzugreifen. Scheinbar 'ne blöde Idee :D

Gruß,
Sebastian (seho85)

von Sheeva P. (sheevaplug)


Bewertung
0 lesenswert
nicht lesenswert
seho85 schrieb:
> Ist das denn Grundsätzlich die richtige Herangehensweise? Der Treiber
> muss "selbst (per code)" das Character Device erstellen?

Ja, genau so.

> Per "echo >" und cat liefert der Treiber mir die erwarteten Werte.

Prima.

> Daraufhin habe ich nach einem anderem Weg gesucht auf meinen Treiber
> zuzugreifen. Scheinbar 'ne blöde Idee :D

Das folgt der UNIX-Philosophie "alles ist eine Datei", oder anders 
gesagt: Hardware wird durch "special files" ("device files" oder 
"Gerätedateien") im Verzeichnis /dev repräsentiert. Über diese Dateien 
wird dann die Hardware angesprochen.

Früher wurden solche Gerätedateien noch fest mit mknod(1) angelegt, und 
zwar unabhängig davon, ob die dadurch repräsentierte Hardware überhaupt 
im System vorhanden war. Dadurch hatte das /dev/-Verzeichnis damaliger 
Linux-Versionen viele überflüssige Einträge für nicht vorhandene 
Hardware; obendrein machte das System Probleme, wenn Geräte wie zB. 
USB-Hardware dynamisch zur Laufzeit angeschlossen wurden.

Deswegen haben die Kernelentwickler mit Linux 2.2 dann zunächst das 
devfs entwickelt, welches viele der Nachteile des vorherigen statischen 
Systems behoben hat, aber immer noch nicht der Weisheit letzter Schluß 
war. Heute benutzen moderne Linux-Systeme "udev", das mittlerweile auch 
in das neue Init-System systemd integriert wurde.

von seho85 (Gast)


Bewertung
0 lesenswert
nicht lesenswert
Nochmals danke für deine Ausführungen.

Ich habe die "Character Device Vorlage" aus dem "Linux Treiber 
entwickeln" Buch übernommen. In der Beschreibung stand auch das udev in 
dem Beispiel verwendet wird.

Nun werde ich mich mal an den "NON BLOCKING" Zugriff machen.

Das procfs wollte ich mir auch mal näher ansehen.

Wenn das geschafft ist werde ich mir mal das "LED Subsystem" ansehen, 
und schauen ob ich  ein paar LED auf dem STM32 Board als Netzwerk Tx 
Signal nutzen kann.

Dabei gibts dann bestimmt noch die eine oder andere Frage :D

Gruß,
Sebastian

von Hans Ulli K. (Gast)


Bewertung
0 lesenswert
nicht lesenswert
seho85 schrieb:
> Was ich eigentlich vorhabe:
> Ich habe ein STM32F3 Discovery Board, auf diesem läuft ein SPI-Slave,
> über einfaches Protokoll können die LEDs auf dem Discovery Board ein und
> ausgeschaltet werden. Für diese Ansteuerung möchte ich einen Kernel
> Treiber erstellen.

Hallo Sebastian,

Also du hast ein SPI Device was einige LEDs ansteuern kann.

Wenn es ein kaufbares Device ist und nicht was selbst hergestelltes, 
warum benutzt du nicht den DeviceTree als "Beschreibungssprache" und das 
Linux LED Interface. Wird in deinem Fall dann irgendwie als led-gpio 
oder was in /sys/devices auftauchen.

Zu Test brauchst du den DeviceTree nicht

von seho85 (Gast)


Bewertung
0 lesenswert
nicht lesenswert
Wie das mit den LEDs genau funktioniert habe ich mir noch gar nicht 
angeschaut.

Wo du den DeviceTree anspricht, fällt mir auch wieder ein das da was mit 
LEDs war. Den wollte ich mir sowie nochmal im Zusammenhang mit meinem 
Device Treiber ansehen (meinen Treiber per FDT einbinden).

Ich hatte erst mal vermutet das ähnlich wie bei GPIO Subsystem ist. Über 
das GPIO Subsystem bin ich "gestolpert" als ich paar Device Treiber von 
GPIO Portexpandern angesehen habe.

Das STM32F3 Discovery hatte ich hauptsächlichen wegen den 8 Onboard LEDs 
verwendet und weil ich kein anderes SPI-Slave zur Verfügung Device 
hatte.

Im Nachhinein viel mir dann auf des eigentliche eine gute Idee ist den 
STM32 zu nehmen, da ich ihn als universelle Peripherie einsetzen kann.
SPI, I2C, UART, usw.

Ich bin sicherlich auch kein Profi für den STM32, aber durch CubeMX und 
OpenSTM32 gepaart mit ein bisschen Übung ist es ja nicht mehr sonderlich 
schwer ein lauffähiges Programm hinzubekommen.
Zumindest zu meinen "Bastler Anforderungen" :)

Ursprünglich kam ich durch Embedded Linux und yocto zu den Kernel 
Modulen.

Nun werde ich aber doch erstmal versuchen meinem BeagleBone zu überreden 
per NFS zu booten. Das läuft noch als neben Task.

Gruß,
Sebastian

von Andreas S. (Firma: Schweigstill IT) (schweigstill) Benutzerseite


Bewertung
0 lesenswert
nicht lesenswert
Warum soll Dein character device denn überhaupt die read- und 
write-Funktionen implementieren? Der von PittyJ skizzierte Weg über 
ioctl ist doch wesentlich sinnvoller, und zwar aus den folgenden 
Gründen:

- Bei SPI werden immer gleichzeitig Daten gesendet und empfangen. Daher 
kann man read und write nicht separat betrachten, sondern würde z.B. 
durch write einen Transfer anstoßen und die gelesenen Daten dann per 
write nur noch abholen. Dadurch müsst man unnötigerweise zwei 
Systemaufrufe mit allem Overhead durch Kontext- bzw. Adressraumwechsel 
usw. machen. ioctl wäre hingegen nur ein Aufruf.

- SPI kann sowohl mit Wortbreiten ungleich acht Bit umgehen als auch 
Transfers mit mehr als einem Byte/Wort durchführen. Im Gegensatz zur 
üblichen Verwendung von character devices, bei denen die Anwendung davon 
ausgehen kann/muss, dass Zeichenfolgen getrennt oder zusammengefasst 
werden, kommt es bei SPI sehr darauf an, die exakt passende Anzahl von 
Zeichen zu einem Transfer zusammenzufassen. Auch wenn man bei read/write 
die Puffergrößen übergibt, bedeutet dies nur die Puffergrößen auf der 
Anwendungsseite, nicht auf Geräteseite. Bei dem obigen Beispiel mit 
spidev und ioctl wird jedoch die Transfergröße auf Geräteseite korrekt 
definiert.

von seho85 (Gast)


Bewertung
0 lesenswert
nicht lesenswert
Hi,

danke für deine Infos.

Warum read und write. Das war so in dem Beispiel das ich Vorlage 
verwendet hatte so vorhanden. Und ich hatte ganz zu Anfang überlegt, die 
Ansteuerungen per "LEDn ON/OFF" Befehl auf das entsprechende Device zu 
realisieren. Also die Bytes nicht einfach transparent durchzureichen.

Dank deiner Beschreibung sehe ich das nun genauso, zumindest beim 
FullDuplex Betrieb, spart das deutlich Ressourcen. Das mit der 
definierten Länge der SPI Telegramme macht auch Sinn.

Hat aus meiner Sicht nur unter Umständen den Nachteil, dass der 
Konsument des Treibers selbst den SPI Datenstrom selbst definiert.

Wobei man auch dabei wahrscheinlich in der entsprechenden io_ctl 
Implementierung die übergebenen Datenströme überprüfen könnte.

Nochmals vielen Dank für den Post, der hat mich dazu gebracht mal 
genauer über die "Konsumentenseite" nachzudenken.

Gruß,
Sebastian

Antwort schreiben

Die Angabe einer E-Mail-Adresse ist freiwillig. Wenn Sie automatisch per E-Mail über Antworten auf Ihren Beitrag informiert werden möchten, melden Sie sich bitte an.

Wichtige Regeln - erst lesen, dann posten!

  • Groß- und Kleinschreibung verwenden
  • Längeren Sourcecode nicht im Text einfügen, sondern als Dateianhang

Formatierung (mehr Informationen...)

  • [c]C-Code[/c]
  • [code]Code in anderen Sprachen, ASCII-Zeichnungen[/code]
  • [math]Formel in LaTeX-Syntax[/math]
  • [[Titel]] - Link zu Artikel
  • Verweis auf anderen Beitrag einfügen: Rechtsklick auf Beitragstitel,
    "Adresse kopieren", und in den Text einfügen




Bild automatisch verkleinern, falls nötig
Bitte das JPG-Format nur für Fotos und Scans verwenden!
Zeichnungen und Screenshots im PNG- oder
GIF-Format hochladen. Siehe Bildformate.
Hinweis: der ursprüngliche Beitrag ist mehr als 6 Monate alt.
Bitte hier nur auf die ursprüngliche Frage antworten,
für neue Fragen einen neuen Beitrag erstellen.

Mit dem Abschicken bestätigst du, die Nutzungsbedingungen anzuerkennen.