Forum: PC-Programmierung Linux USB-Gadget mit FunctionFS: Problem mit Auslesen der Endpoints


von Markus D. (mowlwurf)


Angehängte Dateien:

Lesenswert?

Hallo zusammen,

ich versuche mich in letzter Zeit daran, ein SOM mit einem iMX7 als 
USB-Device an einem Windows 10-Rechner mit den WinUSB-Treibern 
anzumelden.
Betrieben wird das SOM mit einem Yocto-Linux (Angstrom) mit 
Kernelversion 4.9.166. GadgetFS ist im Kernel aktiviert und libcomposite 
wurde geladen.

Bein bisheriger Stand ist, dass ich mit libusbgx das Gadget einrichten, 
das FunctionFS mounten und schlussendlich Endpoint 0 mit den 
USB-Deskriptoren beschreiben kann. Das SOM meldet sich erfolgreich unter 
Windows mit den Endpoints 0 und 1 an und der WinUSB-Treiber wird 
draufgeschaltet.

So weit so schön.

Mein Problem ist nun das Auslesen der Endpoints. Dazu nutze ich jeweils 
den Befehl poll(), nachdem ich mit open() den jeweiligen Endpoint 
geöffnet habe.

Endpoint 0 scheint zu funktionieren, da ich beim An-/Abstecken des 
USB-Kabels die Befehle für "Bind", "Enable", "Disable" usw. empfangen 
kann.

Endpoint 1 zickt da schon mehr rum. Ich komme da sofort aus dem poll() 
zurück, obwohl ich auf dem PC keine Applikation am Laufen hab, die den 
Endpoint 1 beschreibt. Eventuell schickt Windows irgendwelche Daten.
Wenn ich nun versuche, Daten aus dem Endpoint 1 zu lesen, bleibt mein 
Programm im read() hängen und kommt erst mit einer Fehlermeldung zurück, 
wenn ich das USB-Kabel wieder abziehe.

In einem Demo-Bespiel in den Kernel-Quellen habe ich ein Beispiel für 
das FunctionFS gefunden 
(https://github.com/torvalds/linux/blob/master/tools/usb/ffs-test.c).
Dort wird in Zeile 400 mit einem ioctl der Status des Endpoints geprüft. 
Wenn ich das bei meinen Endpoints versuche, kommen die folgenden 
Fehlermeldungen:
    Endpoint 0: Inappropriate ioctl for device
    Endpoint 1: Operation not supported

Irgendwas scheint also mit meinen Endpoints noch nicht zu stimmen. Mir 
gehen aber die Ideen aus, wo ich noch ansetzen könnte.

Meinen Quellcode habe ich angehängt, vielleicht hat einer von Euch eine 
zündende Idee und kann mir einen kleinen Schups in die richtige Richtung 
geben.

Gruß,
Mowlwurf

: Bearbeitet durch User
von DPA (Gast)


Lesenswert?

Ich hab da jetzt nichts ausprobiert, aber beim poll solltest du auf 
jeden fall vor dem read prüfen, ob .revents&POLLIN gesetzt ist. 
Ausserdem solltest du jeweils noch die fälle .revents&POLLERR und 
.revents&POLLNVAL behandeln, die kann es immer geben, egal was in 
.events gesetzt wurde.

von Markus D. (mowlwurf)


Lesenswert?

Guter Hinweis, danke.

Ist aber leider nicht hilfreich für mein Problem.
Gerade noch einmal geprüft: wann immer das poll() zurückkommt, ist nur 
POLLIN als Event gesetzt. Das darauffolgende read() auf Endpoint 1 geht 
dann schief ...

von DPA (Gast)


Lesenswert?

Ansonsten sehe ich sonst keinen Fehler bei der Verwendung von poll, der 
das Verhalten erklären könnte.

Ich frage mich aber, ob die epX überhaupt poll unterstützen. Bei ep0 ist 
es da:
https://elixir.bootlin.com/linux/v4.2/source/drivers/usb/gadget/function/f_fs.c#L627

Bei den restlichen epX nicht:
https://elixir.bootlin.com/linux/v4.2/source/drivers/usb/gadget/function/f_fs.c#L1061

Jedoch kenne ich mich nicht mit linux aio (.read_iter) aus, es kann 
sein, dass das dort anders gemacht wird. Beim Code von poll sehe ich da 
zwar kein spezielles Handling. Wenn ich den Code vom Poll syscall
https://elixir.bootlin.com/linux/v4.2/source/fs/select.c#L750
richtig verstehe, dann macht das quasi:
1
mask = DEFAULT_POLLMASK; // mask = POLLIN | POLLOUT | POLLRDNORM | POLLWRNORM
2
mask &= pollfd->events | POLLERR | POLLHUP; // pollfd->events=POLLIN; mask=POLLIN;
3
pollfd->revents = mask; // pollfd->revents=POLLIN

Das würde dann auch erklären, warum es sofort mit POLLIN zurückkehrt. 
Das fühlt sich zwar irgendwie falsch ein, aber naja...

Wenn ich das also alles richtig verstehe, bleibt dir bei allen epX, 
abgesehen von ep0 nichts anderes übrig, als in nem thread blocking read 
zu machen, oder linux aio zu verwenden. Das sollte ja auch keine 
grösseres Problem sein, du hast ja sowieso für jedes epX einen eigenen 
Thread, und man kann das read normalerweise immernoch mit einem Signal 
unerbrechen, oder gar den thread gleich abschiessen.

von Markus D. (mowlwurf)


Lesenswert?

Deine Erklärung klingt plausibel.

In dem Beispiel aus den Kernel-Quellen, das ich bereits oben verlinkt 
habe 
(https://github.com/torvalds/linux/blob/master/tools/usb/ffs-test.c), 
wird auch ohne poll() oder select() einfach ein read() gemacht und der 
jeweilige Thread damit geblockt.

Was mir aber noch nicht klar ist, warum ioctl() auf meine Endpoints 
diese Fehlerausgaben zurückgibt. Muss eventuell erst eine Applikation 
diese Endpoints geöffnet haben?

von DPA (Gast)


Lesenswert?

ep0 hat eine andere Aufgabe als die restlichen epX. Der Fehler "Endpoint 
0: Inappropriate ioctl for device" ist, weil es den IOCTL für ep0 nicht 
gibt. Im beispiel code wird ep0 auch explizit von dem ioctl 
ausgeschlossen: 
https://github.com/torvalds/linux/blob/master/tools/usb/ffs-test.c#L398

Bei der anderen Fehlermeldung kenne ich mich mit FunctionFS zu wenig 
aus, um da etwas dazu sagen zu können.

von Markus D. (mowlwurf)


Lesenswert?

Ich werd morgen versuchen, dass die PC-Software sich mit dem Device 
verbindet und Endpoint 1 beschreibt. Mal sehen, wie sich dann das read() 
verhält und was ioctl() zurück gibt.

Auf jeden Fall schon mal vielen Dank für deine Hilfe!

von Gerd E. (robberknight)


Lesenswert?

Mal ne ganz andere Idee:

musst Du unbedingt direkt die USB-Transfers selber behandeln?

Könntest Du nicht eine Standard-Geräteklasse verwenden, für die es 
sowohl auf dem Windows-Host, als auch auf dem Linux-Gadget, bereits 
fertige Treiber gibt?

Ich denke da z.B. an das CDC ACM (serieller Port).

Auf der Linux-Seite kannst Du dann einfach einen normalen tty öffnen und 
darüber die Daten übertragen, unter Windows den entsprechenden Com-Port. 
Das ganze Handling von USB-Endpoints etc. fällt auf beiden Seiten weg.

von Markus D. (mowlwurf)


Lesenswert?

Diese Geräteklasse hab ich mir natürlich schon einmal angeguckt. Da wird 
mir aber die erzielbare Datenrate zu niedrig sein.

von Markus D. (mowlwurf)


Lesenswert?

Für das ioctl der epX habe ich herausgefunden, dass mein SOM/SOC 
entweder keinen FIFO nutzt oder der Status des FIFOs nicht ermittelt 
werden kann:
https://elixir.bootlin.com/linux/v4.2/source/include/linux/usb/gadget.h#L441

Das read() meiner Endpoints epX verhält sich nun allerdings so, dass es 
nur aller 4 Pakete zurückkommt. Dabei ist es unerheblich, ob ich 4-mal 
dasselbe Paket schicke oder nur 1-mal und anschließend 3 Zero-Pakete.

Ein anderes SOM mit einem Snapdragon 410 von Qualcomm zeigt dasselbe 
Verhalten.

Von einem ehemaligen Kollegen weiß ich, dass man dieses Verhalten 
verändern kann. Kann ihn dummerweise nicht mehr fragen ...

Weiß einer von Euch da mehr?

von Markus D. (mowlwurf)


Lesenswert?

Bin noch zu keiner Lösung gekommen, habe aber folgenden Effekt 
beobachtet:

Bisher war mein Empfangspuffer, den ich an read() übergebe, 64 kB groß.

Wenn ich nun ein 12-Byte großes Paket übertragen will, muss ich dieses 
genau 4x senden, damit die read()-Funktion zurück kommt. Die Anzahl der 
gelesenen Bytes beträgt dann 48 Byte, mir gehen also so gesehen keine 
Pakete verloren.

Wenn ich diesen Puffer nun auf maximal 20 kB verkleinere, dann kommt 
read() auch nach jedem einzelnen, empfangenen Paket zurück. So, wie es 
sein sollte ...

Ist der Puffer 21 kB groß, dann kommt read() nur nach jedem zweiten 
empfangenen Paket zurück (mit entsprechend 24 Byte als ausgelesener 
Länge).

Gibt es da im USB Stack vom Linux eine Beschränkung, die ich nicht 
beachte?

von Markus D. (mowlwurf)


Lesenswert?

Nach längerer Rücksprache mit dem Hersteller des SOMs gibt es zumindest 
einen Teilerfolg:

https://www.toradex.com/community/questions/39123/usb-configfs-functionfs-read-of-endpoint-bundles-p.html?childToView=42419#answer-42419

: Bearbeitet durch User
Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.