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
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.
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 ...
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.
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?
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.
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!
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.
Diese Geräteklasse hab ich mir natürlich schon einmal angeguckt. Da wird mir aber die erzielbare Datenrate zu niedrig sein.
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?
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?
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
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.