Hallo,
es geht zwar um Rmbedded Linux, aber da es nicht sehr embeddedspezifisch
ist passts hier vielleicht besser.
Also, ich habe einen ARM Prozessor mit einem lauffähigen embedded Linux.
Uart, Ethernet etc. geht. Diese Devices sind auch alle im Device Tree
vermerkt und unter /dev habe ich entsprechende Devicefiles, z.B. für den
Uart.
Am Prozessor ist noch ein kleiner FPGA dran, wo ich ein spezielles
Device mit memory mapped IO synthetisiere. Jetzt muss ich natürlich, um
das Ding aus Linux heraus ansprechen zu können, einen Treiber dafür
haben.
Dazu ein paar Fragen:
Muss ich das "Spezialdevice" zwingend mit in den Device Tree aufnehmen?
Für das Ding brauche ich ja auch noch ein Device File unter /dev. Wer
erzeugt das File?
Ich dachte eigentlich, den Treiber könnte ich als loadable Module bauen.
Wenn das Device benutzt werden soll, dann wird das Modul geladen und
irgend ein System call erzeugt mir das Device file? oder wie geht es
richtig?
ich lese zwar grade "Linux device driver development", aber irgendwie
wird nicht sauber darauf eingegangen, woher das Device File kommt und
wer das Modul für den Treiber lädt.
Dann - um auf die memory mapped IO Register des Device zugreifen zu
können, rufe ich einfach ioremap() oder mmap() auf und mache den
Memorybereich so zugreifbar? oder wie geht es richtig.
Danke & Gruss
Tobias
Tobias Plüss schrieb:> Muss ich das "Spezialdevice" zwingend mit in den Device Tree aufnehmen?
Nein. Siehe Netzwerkdevices.
> Für das Ding brauche ich ja auch noch ein Device File unter /dev. Wer> erzeugt das File?
Die Distribution oder Du oder udev.
> Ich dachte eigentlich, den Treiber könnte ich als loadable Module bauen.> Wenn das Device benutzt werden soll, dann wird das Modul geladen und> irgend ein System call erzeugt mir das Device file? oder wie geht es> richtig?
Das Kernel Modul implementiert nur die Funktionalität des physikalischen
Gerätes. Es registriert eine ( Major- und Minor- ) Device Nummer und
wartet auf Arbeit. Der Rest sollte in Deinem Buch stehen.
Das Devicefile ist der Teil, auf den Benutzer mit ihren Berechtigungen
über ioctl's oder File I/O zugreifen können. Das Devicefile greift auf
eine bestimmte ( Major- und Minor- ) Device Nummer zu und stellt so die
Verbindung zum Kernel Modul her.
Siehe auch: man mknod(1)
Alle Geräte, mit denen Benutzer direkt kommunizieren können sollen,
brauchen ein Devicefile.
Hallo zusammen,
> Lies mal was zum Thema "User Space Drivers". Vielleicht kannst Du auf
ja, das ist gut möglich dass das mit einem User Space Driver geht. Was
ist das Kriterium hierfür?
> Ist das Zynq von Xilinx
nein, ist was anderes. Die Frage ist auch eher allgemein gemeint.
@Till
Also, verstehe ich das richtig:
Man lädt das Kernelmodul. Dieses registriert die beiden Nummern (Major
und Minor). Zudem muss zuvor das Device File erstellt werden (oder aber
man erstellt es beim Laden des Kernelmoduls automatisch, was auch noch
nett wäre...) Dann gibts da noch eine Struktur, weiss den Namen nicht
mehr genau auswendig, aber irgendwas mit ...ops - dort stehen
Funktionspointer drin für open, read, write, ...
Wenn jetzt ein Devicefile mit File I/O geöffnet wird, schaut der Kernel,
welche Major/Minor-Nummer dem Devicefile zugeordnet ist, und pickt sich
das jenige Kernelmodul raus, welches ebendiese Nummern registriert hat.
Über die Funktionspointer open, read, write, ... werden dann
entsprechenden Callbackfunktionen aufgerufen. Richtig?
Weisst du ein gutes Beispiel, wo ich mir das ein wenig abgucken kann?
Oder kann ich den Beispielcode aus "Linux Device Driver Development"
benutzen? Der dürfte zwar recht alt sein, weil er für Kernel 2.6x ist...
Gruss Tobias
>> Lies mal was zum Thema "User Space Drivers". Vielleicht kannst Du auf>> ja, das ist gut möglich dass das mit einem User Space Driver geht. Was> ist das Kriterium hierfür?
Gast Stephan hebt hier auf eine kleine Erleichterung ab. Im Kernel
herrscht ein anderes Memory Mapping als im User Space. Wenn Du jetzt
einen Kernel Treiber schreibst, mußt Du zwischen User Space und Kernel
Space Memory konvertieren. Was Gast Stephan nicht berücksichtigt, ist,
daß Dein Prozessor memory mapped auf Dein Gerät zugreift. Die genannte
Erleichterung gilt aber nur für den Zugriff von Programmen auf das
Device File. Nur hierbei ist bei einem User Space Treiber keine
Umwandlung nötig.
Ansonsten enthält ein User Space Treiber 99% den gleichen Code, wie sein
Bruder im Kernel und ist definitiv auch ein "echter" Treiber.
Gängige Praxis ist, einen User Space Treiber zu entwickeln und den dann
ins Kernel zu portieren.
> @Till> Also, verstehe ich das richtig:>> Man lädt das Kernelmodul. Dieses registriert die beiden Nummern (Major> und Minor). Zudem muss zuvor das Device File erstellt werden
Ja, aber alt, für viele Systeme und Kernel aber noch aktuell.
> (oder aber man erstellt es beim Laden des Kernelmoduls automatisch, was > auch
noch nett wäre...)
Dein Modul, kann / darf das nicht, es läuft im Kernelspace, da gibt es
keine Filesystemfunktionen. Dafür ist aber udev da. Wenn Dein Modul
lädt, besser gesagt, wenn es ein gültiges Gerät erkennt und dieses
registriert, meldet es dem Kernel seine Daten. Das Kernel erzeugt dann
ein Event für udev. udev erzeugt dann ( im Userspace ) anhand von Regeln
in /etc/udev.d/ ein Devicefile und evtl. verschiedene Sym-Links darauf.
Hierbei hast Du dann mit Major- und Minor- Nummern nichts mehr zu tun.
> Dann gibts da noch eine Struktur, weiss den Namen nicht> mehr genau auswendig, aber irgendwas mit ...ops - dort stehen> Funktionspointer drin für open, read, write, ...> Wenn jetzt ein Devicefile mit File I/O geöffnet wird, schaut der Kernel,> welche Major/Minor-Nummer dem Devicefile zugeordnet ist, und pickt sich> das jenige Kernelmodul raus, welches ebendiese Nummern registriert hat.> Über die Funktionspointer open, read, write, ... werden dann> entsprechenden Callbackfunktionen aufgerufen. Richtig?
Ja. Nur die ioctl hast Du vergessen.
Du mußt Dich auch noch mit Device Klassen aus einander setzen. Dein
Gerät wird vermutlich nichts wirklich Neues machen. Durch das
einsortieren in eine vorhandene Klasse kannst Du Dir eine Menge Arbeit
sparen.
> Weisst du ein gutes Beispiel, wo ich mir das ein wenig abgucken kann?
In den Kernel Sourcen gibt es mehrere Skeleton Treiber, die sollten Dir
helfen.
> Oder kann ich den Beispielcode aus "Linux Device Driver Development"> benutzen? Der dürfte zwar recht alt sein, weil er für Kernel 2.6x ist...
Kenn' ich nicht, sollte aber gehen. In 2.6 war udev schon drin'.
Tobias Plüss schrieb:
Hallo Tobias ;-)
>> Am Prozessor ist noch ein kleiner FPGA dran, wo ich ein spezielles> Device mit memory mapped IO synthetisiere. Jetzt muss ich natürlich, um> das Ding aus Linux heraus ansprechen zu können, einen Treiber dafür> haben.>> Dazu ein paar Fragen:>> Muss ich das "Spezialdevice" zwingend mit in den Device Tree aufnehmen?
Wenn du es als Device im Userland anspfrechen willst jat.
>> Für das Ding brauche ich ja auch noch ein Device File unter /dev. Wer> erzeugt das File?
In Prinzip dein Treiber beim "allozieren" des Deivces
> Ich dachte eigentlich, den Treiber könnte ich als loadable Module bauen.> Wenn das Device benutzt werden soll, dann wird das Modul geladen und> irgend ein System call erzeugt mir das Device file? oder wie geht es> richtig?
Die Frage ist erstmal was für ein Device Type dein FPGA darstellen kann.
Block oder Char Device.
Das bezieht sich nur auf den Datentransport.
Also einzelne Bytes oder in Block zu z.B. 512 Bytes (aka Floppy HDD),
Um Kommandos an dein Device zu geben gibt es ioctl's
> ich lese zwar grade "Linux device driver development", aber irgendwie> wird nicht sauber darauf eingegangen, woher das Device File kommt und> wer das Modul für den Treiber lädt.>
Düfte irgend wo in den Sourcen zu finden.
Aber erstmal die obige Frage beantworten Block oder Char.
> Dann - um auf die memory mapped IO Register des Device zugreifen zu> können, rufe ich einfach ioremap() oder mmap() auf und mache den> Memorybereich so zugreifbar? oder wie geht es richtig.
ioremap ist eine Kernelfunktion um dein Device für den Kernel vom
physikalischen in den virtuellen Bereich zu mappen. Weil du nur per
"virtueller" Adresse auf dein Device zugreifst.
mmap () ist eine Kernel/Userland Funktion um sagen wir das unnnötige
kopieren von Daten zu verhindern.
> Danke & Gruss> Tobias
Hallo Ulli und alle anderen,
so ich habe dank euren Infos die richtigen Stichworte bei Google
eingeben können und bin nun auf folgendes gestossen:
http://hg.berlios.de/repos/kedr/file/b0f4d9d02d35/sources/examples/sample_target/cfake.c
Der Code sieht in meinen Augen eigentlich nicht soo schlecht aus. Ich
habe ihn ein wenig modifiziert, und in den open / read usw.
Callbackfunktionen einfach entsprechende printk() Aufrufe eingebaut und
das Ding auf meinem PC compiliert. Nach einer anfänglichen kernel panic,
weil ich irgendwo einen Pointer noch verbogen hatte, hats beim zweiten
mal nun geklappt:
mit insmod test.ko wird das Modul geladen, und ein ls /dev bringt mir
ein cfake0 Devicefile. Mit cat /var/log/syslog | grep cfake sehe ich
meinen Text beim Open-Aufruf :-) Danach habe ich noch mit einem kleinen
Bastel-C-Programm das Device File mit open() geöffnet, ein paar Bytes
rein geschrieben und wieder zugemacht - konnte man alles im syslog
verfolgen. Das Modul scheint also zu funktionieren. Und - besonders toll
- mit rmmod test.ko verschwindet auch das Device File wieder und es wird
sauber aufgeräumt.
Super! Das Modul scheint also so grundsätzlich mal zu funktionieren.
Jetzt muss ich mir nur noch überlegen, ob mein Device was ich
hardwaremässig anspreche ein Block- oder Chardevice ist. Also, es hat
ein paar Memory Mapped Register, eigentlich interessieren mich nur die.
Kann ich mir also die Read / Write Funktionen sparen und nur ioctl
implementieren? Ich dachte mir, mit ioctl könnte ich dann die
Registernummern und die zu schreibenden Werte übergeben. So wie hier:
http://www.linuxforu.com/2011/08/io-control-in-linux/
Was meint ihr?
Gruss
Die Sache mit den Userspace-Treibern geht leider vernünftig nur bei x86,
weil da mmap auf /dev/mem auch für den PCI/IO/Peripheriebereich geht.
Bei ARM-Linux geht das nicht. Hab deswegen für ödes LED-Geblinke über
GPIO extra einen Treiber schreiben müssen...
> Kann ich mir also die Read / Write Funktionen sparen und nur ioctl> implementieren?
Sicher. read/write ist für viele Sachen gar tauglich.
@Georg
was meinst du mit "gar tauglich" ? :-)
Ich habe mal den Code ein wenig aufgeräumt und das was ich nicht brauche
raus genommen. So sieht er jetzt aus:
1
#include<linux/version.h>
2
#include<linux/module.h>
3
#include<linux/moduleparam.h>
4
#include<linux/init.h>
5
#include<linux/kernel.h>
6
#include<linux/slab.h>
7
#include<linux/fs.h>
8
#include<linux/errno.h>
9
#include<linux/err.h>
10
#include<linux/cdev.h>
11
#include<linux/device.h>
12
#include<linux/mutex.h>
13
#include<asm/uaccess.h>
14
15
16
17
#define CFAKE_DEVICE_NAME "cfake"
18
19
structcfake_device
20
{
21
intmajor;
22
dev_tdevnbr;
23
staticstructclass*class=NULL;
24
structcdevcdev;
25
};
26
27
staticstructcfake_device*this_device=NULL;
28
29
staticint
30
cfake_open(structinode*inode,structfile*filp)
31
{
32
unsignedintmj=imajor(inode);
33
unsignedintmn=iminor(inode);
34
structcfake_device*dev=NULL;
35
36
if((mj!=this_device->major)||(mn<0)||(mn>1))
37
{
38
/* no such device */
39
return-ENODEV;
40
}
41
42
/* store a pointer to struct cfake_dev here for other methods */
Der goto-Teil gefällt mir nicht so, aber es ist am einfachsten ;-)
sieht nicht so schlecht aus, oder? Habe das in der Zwischenzeit aber
nicht mehr getestet. Aber vielleicht sieht einer noch Fehler. Was ich
nun noch machen muss ist ioctl implementieren; ich habe da allerdings
gesehen dass es verschiedene Varianten gibt; irgendwie ioctl_unlock
usw., welches ist das richtige?
Ich wollte noch ein "nicht" kaufen ;)
Das goto ist den Dijkstra-Jüngern zwar ein Graus, aber hier durchaus
gerechtfertigt. Alles andere macht den Fluss noch undurchschaubarer bzw.
fehlerträchtiger, und "try-catch" gibts ja auch nicht.
Die Sache mit dem Lock kommt noch vom "Big-Kernel-Lock", der bis Ende
der 2.6er (oder so) die IO-Ops geschützt hat und jetzt noch aus
Kompatibilitätsgründen da ist. Nimm den alten (nicht den unlocked*), und
du musst dir keine grossen Gedanken machen. Die Performanceeffekte des
unlocked* wären in der Anwendung ohnehin nicht bemerkbar. Da gehts um
Multicore/Massiv-SMP.
Hi Georg,
ach soo :-)
Okay aber den Callback "nur" ioctl() gibt es m.E. nicht, ich finde nur
die modifizierten Varianten. Was mache ich falsch?
Btw, Dijkstra war mir kein Begriff, hab aber kurz gegoogled ;-)
Allerdings ist "GOTO Statement considered harmful" tatsächlich das, was
ich mal gelernt habe :D
Ehm, etwas ist mir noch aufgefallen - mit
sudo insmod ./test.ko
wird das Modul geladen, und unter /dev wird meine Devicenode erstellt,
soweit alles gut... wenn ich aber
sudo rmmod test
mache, dann wird auch das Modul entladen, aber die Devicenode bleibt
bestehen bis ich den PC neu starte. Ist das richtiges bzw. gewolltes
Verhalten? Ich hätte eigentlich erwartet dass die Devicenode beim
Entladen des Moduls entfernt wird.
Gruss Tobias
> Was mache ich falsch?
Das normale/alte würde compat_ioctl heissen.
> Allerdings ist "GOTO Statement considered harmful" tatsächlich das, was> ich mal gelernt habe :D
Wobei die Goto-Nutzung in der Zeit aber auch extrem war. Die meisten
Sprachen hatten nichts strukturierendes, oft nichtmal ein else zum if...
Von daher halte ich es für übertrieben, heute beim Auftauchen von ein
paar versprengten Gotos gleich wieder den ollen Dijkstra-Spruch
rauszulassen.
> Ich hätte eigentlich erwartet dass die Devicenode beim> Entladen des Moduls entfernt wird.
Sollte es, ein printk im exit-code zeigt, ob er überhaupt aufgerufen
wird ;) Die Reihenfolge beim Aufräumen kommt mir aber komisch vor. Mach
mal den class_destroy vor den cdev_del, so machens andere.. Ausserdem
solltest du als letztes noch this_device freigeben (das wirds aber nicht
sein).
Tobias Plüss schrieb:> sudo rmmod test>> mache, dann wird auch das Modul entladen, aber die Devicenode bleibt> bestehen bis ich den PC neu starte. Ist das richtiges bzw. gewolltes> Verhalten? Ich hätte eigentlich erwartet dass die Devicenode beim> Entladen des Moduls entfernt wird.>> Gruss Tobias
Was macht denn udev oder ein anderer Hotplugmanager (mdev / logfile)
Der Device-Node in /dev wird ja nicht per Kernel erzeugt, sondern der
Kernel gibt per /proc/sys/kernel/hotplug an der Userspace weiter.
Das mit dem goto in den Sourcen finde ich normal. Außerdem ist eine
return Anweisung auch ein goto, nur sieht man es nicht sofort.
Wer findet hier den Fehler ...
Hallo Hans Ulli und Georg,
sorry für die Verspätete Antwort, ich hatte viel zu tun ;-)
Ich werde noch meine Reihenfolge im Module-Exit Code korrigieren und
dann heute Abend nochmal schauen, ob es so besser funktioniert.
Noch eine andere Frage:
angenommen, mein FPGA-Device bietet ein paar memory mapped Register an,
sagen wir an der physikalischen Adresse 0xf0000000. Normalerweise klemmt
die MMU ja Zugriffe auf nicht gemapptes Memory ab, oder? Das heisst,
selbst wenn mein Treiber geladen wurde, kann ich noch nicht auf die
Adresse 0xf0000000 einfach so zugreifen, das würde doch einen Fehler
auslösen - wahrscheinlich segmentation fault, oder sowas. Kann ich mit
mmap() oder so einer Funktion das Memory irgendwie so mappen, dass ich
zugreifen darf? das ist mir noch nicht klar. Irgendwie muss ich ja auf
die Adresse meines Devices zugreifen können damit ich die Register lesen
und schreiben kann.
Das Lesen / Schreiben geschieht dann mit den Makros readl() bzw.
writel(), ja?
Gruss Tobias
PS: Ulli, der Fehler liegt doch im 3. "if(fail)", oder? Da wird nur
spinlock 1 unlocked, nicht aber die 2.
Wenn du auf HW-Speicher im Kernel zugreifen willst, nimm
ioremap(_nocache). Das liefert dann die (virtuelle) Startadresse zurück,
auf der du dann den Bereich siehst. Ohne das gibt's einen Oops... Ein
iounmap zum Aufräumen gibts auch. Schau dir zB. mal die diversen
Kerneltreiber an, fast jeder nutzt das. Meistens kommt da die
physikalische Addresse aus dem PCI-Config-Space, bei Embedded-Kram ist
sie manchmal auch hardcoded.
> Das Lesen / Schreiben geschieht dann mit den Makros readl() bzw.> writel(), ja?
Ja, sollte man nehmen. Wobei für einen Quick-Hack auf x86 auch einfach
normales Gepointere geht. readl/writel kümmern sich u.a. um
little/big-Endian-Probleme (PCI ist zB. LE, auf BE-Maschinen hätte ein
roher 32Bit-Read vertauschte Bytes). Auf seltsamen Architekturen (zB.
Alpha...) macht das auch noch die Aufteilung der Byte/Word/Long-Zugriffe
auf verschiedene Adressbereiche.
Hi Georg,
super, danke, damit komme ich zurecht, denke ich ;-)
Wenn ich noch ein Problem raus finde melde ich mich wieder hier.
Gruss & schönen Abend
Tobias
Tobias Plüss schrieb:>> PS: Ulli, der Fehler liegt doch im 3. "if(fail)", oder? Da wird nur> spinlock 1 unlocked, nicht aber die 2.
Beachte mal die Reihenfolge des Lockings :
1
spin_lock(&spinlock_1);
2
3
/* Do something */
4
5
if(fail){
6
spin_unlock(&spinlock_1);
7
return;
8
}
9
10
spin_lock(&spinlock_2);
11
12
/* Do something */
13
14
if(fail){
15
spin_unlock(&spinlock_2);
16
spin_unlock(&spinlock_1);
17
return;
18
}
19
20
if(fail){
21
spin_unlock(&spinlock_1);
22
spin_unlock(&spinlock_2);
23
return;
24
}
Beim zweiten "fail" wird falsch unlocked.
Im Programmfluss wurde erst das spinlock_1, dann spinlock_2 gesetzt.
Ein Unlock sollte, dann Rückwärts ablaufen.
Es ist quasi wie beim allozieren/free dieser struct.
1
struct{
2
char*mem;
3
}data;
4
5
structdata*test;
Wenn du den Speicher von *test freigibst muss du ja auch erst ein
free (test->mem);
machen, sonnst hast du ein memory hole ...
Und da ist es echt einfacher diese Sache mit einem
goto
in C abzufangen und es ist einfacher zu lesen.
Mit dem mmap vom deinem FPGA, schaue mal in dem Bereich
drivers/gpu
Die Grafikkarten haben das gleiche "Problem" wie du. Da wird auch ein
Bereich per mmap in den Userspace gemapped.
Hallo zusammen,
ich habe noch eine Frage hierzu!
Ich habe hier ein laufendes Embedded Linux System, das ich selber
compiliert habe. Den Kernel habe ich mit arm-none-eabi-gcc kompiliert.
Da ich auf dem Target keinen Compiler habe, muss ich meinen Treiber
Cross-Compilieren. Dazu habe ich folgendes Makefile:
und dann müsste ich ja unter /var/log/messages meine Meldungen von dem
Treiber sehen. Also, compilieren geht, ABER, wenn ich auf meinem Target
(das mit Buildroot erstellt wurde) dann eingebe
insmod ./test.ko
dann kriege ich den Fehler:
test: version magic '3.10.0-00163-gd4db2a9-dirty SMP mod_unload ARMv7
p2v8 ' should be '3.10.0-0016'
insmod: can't insert './test.ko': invalid module format
warum? ich habe für den Treiber ja dieselben Header verwendet wie für
den Kernel. Was mache ich falsch?
Gruss Tobias
Tobias Plüss schrieb:>> test: version magic '3.10.0-00163-gd4db2a9-dirty SMP mod_unload ARMv7> p2v8 ' should be '3.10.0-0016'> insmod: can't insert './test.ko': invalid module format>>> warum? ich habe für den Treiber ja dieselben Header verwendet wie für> den Kernel. Was mache ich falsch?>>> Gruss Tobias
Wenn ich das richtig lese "*-gd4db2a9-dirty" hast du den Treiber über
das Makefile von Kconfig/Kbuild angeworfen.
Mache bitte in Kconfig die Option
"Automatically append version information to the version string"
aus.
An deine Kernelversion wird ein git Hash angefügt. Außerdem zeigt
"dirty" an das du nicht commited hast.
Hi Hans Ulli,
s u p e r, das hat funktioniert! jetzt kann ich mein crosscompiliertes
Modul auf meinen embedddd device laden. Toll! :D
debuggen kann man aber solch ein kernelmodul nicht so richtig oder?
Gruss