Forum: PC-Programmierung Virtuelle serielle Schnittstelle verliert Daten


von Sven B. (scummos)


Lesenswert?

Hi!

Ich habe einen Mikrocontroller mit USB 2, der (in Software) das 
Interface für eine virtuelle serielle Schnittstelle implementiert. Wenn 
ich mit dem Mikrocontroller sehr schnell Daten auf die Schnittstelle 
schreibe, dann kommen die auf der anderen Seite irgendwie nicht mehr 
alle an (nach ein paar MB fehlen immer mal ein paar Pakete, ziemlich 
zufällig). Der Code, der die Daten liest, ist denkbar einfach (Python):
1
class Receiver:
2
    def __init__(self):
3
        self.fd = os.open("/dev/ttyACM0", os.O_RDWR|os.O_NOCTTY|os.O_NONBLOCK)
4
5
    def read(self, sz):
6
        buf = bytes()
7
        while len(buf) < sz:
8
            buf += os.read(self.fd, sz-len(buf))
9
        return buf

Schreibt man nur ein bisschen langsamer, tritt der Fehler nicht mehr 
auf.
Klar, jetzt denkt man natürlich, bestimmt wird der Write-Buffer auf dem 
Mikrocontroller neu befüllt bevor er leer ist oder so. Aber nein: Wenn 
ich mit Wireshark den Traffic auf der USB-Schnittstelle mitschneide, 
sind die Pakete alle da! Nur aus /dev/ttyACM0 purzeln nicht alle Daten 
wieder raus.

Es klingt blöd, aber das hört sich fast nach einem Fehler in cdc-acm im 
Linux-Kernel an, oder? Mir fällt keine andere Erklärung ein. Hat jemand 
andere Ideen?

Danke und viele Grüße,
Sven

: Bearbeitet durch User
von Frank K. (fchk)


Lesenswert?

Deine Hypothese kannst Du durch Verwendung eines anderen Betriebssystems 
(Windows, FreeBSD,...) erhärten oder widerlegen.

Was für einen "Mikrocontroller mit USB 2" hast Du denn? Doch nicht etwa 
diesen VUSB-Mist?

fchk

von Sven B. (scummos)


Lesenswert?

Hi,

ne, kein V-USB, der Controller ist ein LPC43xx mit einem ganz normalen 
Hardware USB2-Controller.

Ja, anderes Betriebssystem ... werde ich wohl versuchen müssen.

Viele Grüße,
Sven

von Georg (Gast)


Lesenswert?

Sven B. schrieb:
> Schreibt man nur ein bisschen langsamer, tritt der Fehler nicht mehr
> auf.

Ich sehe nirgends eine Absicherung der Datenübertragung, z.B. durch eine 
Prüfsumme, und kein Protokoll, das bei Fehlern die Übertragung neu 
anfordert - das ist bei externen Übetragungen über Schnittstellen 
generell nicht zu verantworten.

Georg

von Peter II (Gast)


Lesenswert?

Georg schrieb:
> Ich sehe nirgends eine Absicherung der Datenübertragung, z.B. durch eine
> Prüfsumme, und kein Protokoll, das bei Fehlern die Übertragung neu
> anfordert - das ist bei externen Übetragungen über Schnittstellen
> generell nicht zu verantworten.

was hier aber absolut keine Rolle spielt. Er will wissen warum die Daten 
verloren gehen. Da er scheinbar festgestellt hat das Daten fehlen, 
scheint es eine "Prüfung" zu geben.

Oder gibt du dich damit zu frieden, ein perfektes Protokoll geschrieben 
zu haben was über eine 100Mbit/s Leitung nur 5byte/s schafft, Hauptsache 
es geht nicht verloren?

von Sven B. (scummos)


Lesenswert?

Georg schrieb:
> Ich sehe nirgends eine Absicherung der Datenübertragung, z.B. durch eine
> Prüfsumme, und kein Protokoll, das bei Fehlern die Übertragung neu
> anfordert - das ist bei externen Übetragungen über Schnittstellen
> generell nicht zu verantworten.

?
Das ist ein USB-Bulk-Endpoint. Der garantiert mir laut Spezifikation, 
dass die Daten vollständig und fehlerfrei übertragen werden. Prüfsummen 
und das Erkennen fehlender Pakete sind schon auf USB-Protokoll-Ebene 
implementiert und es ist ziemlich sinnlos, das noch einmal auf höhrer 
Ebene draufzusetzen. Eigentlich.

> Da er scheinbar festgestellt hat das Daten fehlen,
> scheint es eine "Prüfung" zu geben.

Genau, jedes Paket hat einen Header mit ein paar magischen Zeichen am 
Anfang, und die sind irgendwann nicht mehr da wo sie sein sollten. 
Prüfsumme gibt es wie gesagt keine, ich kann die in dem Tempo nicht 
ausrechnen und es darf auch einfach nicht nötig sein.

: Bearbeitet durch User
von Peter II (Gast)


Lesenswert?

Sven B. schrieb:
> Das ist ein USB-Bulk-Endpoint. Der garantiert mir laut Spezifikation,
> dass die Daten vollständig und fehlerfrei übertragen werden. Prüfsummen
> und das Erkennen fehlender Pakete sind schon auf USB-Protokoll-Ebene
> implementiert und es ist ziemlich sinnlos, das noch einmal auf höhrer
> Ebene draufzusetzen. Eigentlich.

das würde ich nicht sagen. Wenn du die Daten aus ttyACM0 nicht schnell 
genug aufragst, dann können sie auch verloren gehen.

von Sven B. (scummos)


Lesenswert?

Peter II schrieb:
> das würde ich nicht sagen. Wenn du die Daten aus ttyACM0 nicht schnell
> genug aufragst, dann können sie auch verloren gehen.

Das ist genau die Frage, die mich seit längerem beschäftigt. Ist das so? 
Ich glaube nicht: Wenn der Buffer voll ist, sollte der Host keine 
weiteren Pakete mehr annehmen, und soweit ich das sehen kann ist das 
auch das was passiert: Wenn man nur Daten anfragt aber nicht liest dann 
werden ein paar übertragen, und danach hängt der uC in der "warte auf 
`Endpoint bereit'"-Schleife fest.

von Peter II (Gast)


Lesenswert?

Sven B. schrieb:
> Das ist genau die Frage, die mich seit längerem beschäftigt. Ist das so?
> Ich glaube nicht: Wenn der Buffer voll ist, sollte der Host keine
> weiteren Pakete mehr annehmen,

Das sollte sich ja zumindest gut testen lassen. Ich kenne es nur von den 
USB-Seriell Adaptern, da gehen schon mal Daten verloren. Keine Ahnung ob 
sie CDC verwenden.

Mach doch mal ein paar sleeps in dein PC-Programm und schau ob es 
schlimmer wird.

von Sven B. (scummos)


Lesenswert?

Hab ich schon getestet, wird es nicht. Es dauert dann nur so lange, dass 
ich nicht weiß ob der Fehler auftritt (das passiert nur so alle 100000 
Pakete mal, und wenn ich nach jedem Paket eine halbe Sekunde warte ... 
naja). Umgekehrt ist es aber so, wenn man im Mikrocontroller kurze 
Sleeps einbaut, wird es besser bzw. geht ganz weg. Es muss also schon 
irgendwas mit dem Timing zu tun haben. Aber der Fehler muss irgendwo 
nach dem Linux-USB-Subsystem auftreten, denn da sind die Pakete ja 
alle noch da. Also entweder im cdc_acm-Treiber, oder in meinem Programm.

Mit dem Debugger sehe ich, dass der uC während der Host schälft in 
seiner "warte bis der Endpoint frei ist"-Schleife festhängt, wie es sein 
sollte.

: Bearbeitet durch User
von Pit (Gast)


Lesenswert?

Sven B. schrieb:
> Mit dem Debugger sehe ich, dass der uC während der Host schälft in
> seiner "warte bis der Endpoint frei ist"-Schleife festhängt, wie es sein
> sollte.

Das mag ja sein. Aber was passiert, wenn der Host nicht 'schläft'?

Naja, er nimmt die Daten vom Microcontroller entgegen und schreibt sie 
in den Datenpuffer. Der Datenpuffer hat (ich rede mal von Windows, bei 
Linux wirds wohl genauso sein) eine bestimmte Größe, z.B. 64 kBytes. 
Ausserdem ist der Datenpuffer als Ringpuffer organisiert.

Der Host geht also her und schreibt die Daten in den Ringpuffer. Wenn Du 
diese nicht rechtzeitig abholst, dann werden sie eben überschrieben und 
es fehlen ein paar kBytes.
Es ist dem Host nämlich ziemlich egal, was mit den Daten passiert. Sein 
Job ist es nur, sie in den Ringpuffer zu schreiben.

Der Applikation ist es nicht egal, ob die Daten überschrieben werden 
oder nicht. Und genau da kommt dann der Programmierer ins Spiel - also 
Du.

Gruß Klaus

von Sven B. (scummos)


Lesenswert?

Moment, mal langsam.

Wer ist denn für dich "der Host"? Da gibt es mindestens 4 Komponenten: 
Den USB-Hostcontroller mit seinem Hardware-Buffer; das 
Linux-USB-Subsystem; den cdc_acm-Treiber; und mein Programm. Wie ich das 
sehe garantieren 1), 2) und 4) für einen Bulk-Endpoint auf jeden Fall, 
dass alle Daten ankommen. Für die virtuelle serielle Schnittstelle bin 
ich mir nicht ganz sicher, aber ich war bisher auch der Meinung, dass da 
eigentlich nichts verloren gehen darf.

Ich wiederhole nochmal: Wenn ich den uC einfach Daten in den 
Endpoint-Buffer schreiben lasse so schnell er kann, dann aber immer 
warte, bis die Übertragung abgeschlossen ist (und das tue ich), dann 
schreibt der den nur einmal voll und wartet, bis der Host die Daten 
abgeholt hat und bereit ist, neue zu empfangen, bevor er da wieder was 
reinschreibt. Der einzige Fall, wo mMn Daten verloren gehen können wenn 
irgendwas nicht schnell genug ist, ist wenn der uC keinen Speicher mehr 
hat um die ganzen Daten, die er zum Senden buffert, zwischenzuspeichern. 
Das kommt auch vor, aber ich habe das so implementiert dass dabei immer 
nur ganze Pakete verloren gehen, das ist nicht der Grund für den hier 
beobachteten Fehler.

"Der Host schläft" heißt übrigens, dass 4) schläft. Auf 1), 2) und 3) 
hat das keinen Einfluss.

: Bearbeitet durch User
von Steffen R. (steffen_rose)


Lesenswert?

Sven B. schrieb:
> while len(buf) < sz:
>             buf += os.read(self.fd, sz-len(buf))

Ich kann kein Python, daher verzeihe man mir, wenn das eine blöde Frage 
ist:
Wenn per USB kein ganzes Paket der Länge sz vorhanden ist, kommt die 
while-loop zum tragen.
In C wäre dies:
Das read liest ein paar Byte und setzt buf ans Ende. Damit liefert 
len(buf) immer 0 und das nächste read() schreibt dann hinter dem Puffer 
weiter, da dieser keine Länge sz mehr hat.

von Sven B. (scummos)


Lesenswert?

Steffen R. schrieb:
> Sven B. schrieb:
>> while len(buf) < sz:
>>             buf += os.read(self.fd, sz-len(buf))
>
> Ich kann kein Python, daher verzeihe man mir, wenn das eine blöde Frage
> ist:
> Wenn per USB kein ganzes Paket der Länge sz vorhanden ist, kommt die
> while-loop zum tragen.
> In C wäre dies:
> Das read liest ein paar Byte und setzt buf ans Ende. Damit liefert
> len(buf) immer 0 und das nächste read() schreibt dann hinter dem Puffer
> weiter, da dieser keine Länge sz mehr hat.

Das hab' ich leider nicht ganz verstanden, was meinst du? Das os.read() 
liest bis zu sz-len(buf) Bytes aus self.fd. Die gelesenen Bytes werden 
an buf angehängt. Das wird solange wiederholt, bis buf die Länge sz hat.

von Peter II (Gast)


Lesenswert?

Ich find auf merkwüdig das du mit "os.O_NONBLOCK" arbeitest und dann ein 
einer schleife so lange liest bis alle zeichen da sind. Da kann man doch 
gleich blockieren.

Aber das dürfte dein Problem nicht lösen.

von Steffen R. (steffen_rose)


Lesenswert?

Sven B. schrieb:
> Das hab' ich leider nicht ganz verstanden, was meinst du?

Die Frage ist, ob die Zeile das tut, was sie soll, wenn der erste read() 
nur ein paar Bytes liest.

von Sven B. (scummos)


Lesenswert?

Ich denke schon. Wieso sollte sie nicht?

von Steffen R. (steffen_rose)


Lesenswert?

Stochern im Nebel an Stellen, welche häufig zu Fehlern führen.
Die offensichtlichen Sachen hast Du ja selbst schon geprüft.

Konnte leider die genaue Erklärung für os.open() nicht finden. Wird die 
Schnittstelle im Binärmode geöffnet? Ansonsten könnten die fehlenden 
Zeichen Steuerzeichen sein.

von Sven B. (scummos)


Lesenswert?

Daran liegt's nicht, ich verwende sonst auch das serial.Serial-Modul, 
das verhält sich genau gleich. Es fehlen ja auch nicht gelegentlich 
einzelne Zeichen -- die Daten sind ziemlich homogen und es werden 50 MB 
fehlerfrei übertragen und dann fehlen plötzlich genau 4kB oder sowas. 
Aber danke für den Vorschlag.

von Sven B. (scummos)


Lesenswert?

Hier mal ein bisschen Beispiel-Code, den ich gerade noch gebaut habe, um 
das Problem möglichst minimal zu reproduzieren. Der Code überträgt immer 
dieselben 12kB Daten mit einer fortlaufenden Sequenznummer am Anfang. 
765 mal funktioniert das im Beispiel, beim 766ten Mal (ändert sich 
zufällig) fehlt was. In Wireshark sind alle Pakete hintereinander so 
verhanden wie man's erwarten würde. Sehr merkwürdig.

uC:
1
    uint32_t data[1024*3];
2
    uint8_t running = 0;
3
    uint32_t seq = 0;
4
    while ( 1 ) {
5
        USB_USBTask(VirtualSerial_CDC_Interface.Config.PortNumber, USB_MODE_Device);
6
        if ( CDC_Device_BytesReceived(&VirtualSerial_CDC_Interface) ) {
7
            // Start streaming random data as soon as somebody sends a byte
8
            rec_yellow_led_on();
9
            running = true;
10
            for ( int j = 0; j < 1024*3; j++ ) {
11
                data[j] = 0xFFFF | (j << 16);
12
            }
13
        }
14
        if ( running ) {
15
            Endpoint_SelectEndpoint(0, 2);
16
            // test sequence number
17
            data[0] = seq++;
18
            while ( Endpoint_WaitUntilReady() != ENDPOINT_READYWAIT_NoError ) { }
19
            DcdDataTransfer(0, 5, (uint8_t*) data, 1024*3*4);
20
        }
21
    }

Host:
1
#include <stdio.h>
2
#include <stdlib.h>
3
#include <string.h>
4
#include <sys/stat.h>
5
#include <fcntl.h>
6
#include "stdio.h"
7
#include <unistd.h>
8
9
int main(){
10
    int fd;
11
    fd = open("/dev/ttyACM0", O_RDWR | O_NOCTTY | O_NONBLOCK);
12
    if ( fd <= 0 ) exit(1);
13
14
    char a = 'x';
15
    if ( write(fd, &a, 1) != 1 ) exit(2);
16
17
    unsigned long total_xferd = 0;
18
    int compare = -1;
19
    while ( 1 ) {
20
        char buf[3*1024*4];
21
        long size = 3*1024*sizeof(unsigned int);
22
        long cur = 0;
23
        while ( cur < size ) {
24
            long r = read(fd, &buf[cur], size-cur);
25
            if ( r < 0 ) {
26
                exit(3);
27
            }
28
            cur += r;
29
        }
30
        total_xferd += size;
31
        if ( cur == size ) {
32
            unsigned int test_val = *(unsigned int*) &buf[72]; // some random offset
33
            if ( compare == -1 ) {
34
                compare = test_val;
35
            }
36
            printf("total %i, value %X, seq %X\n", total_xferd, test_val, *(unsigned int*) buf);
37
            if ( compare != test_val ) {
38
                printf("error\n");
39
                exit(4);
40
            }
41
        }
42
    }
43
}

Ergebnis:
1
# ./test
2
total 12288, value 12FFFF, seq 0
3
total 24576, value 12FFFF, seq 1
4
total 36864, value 12FFFF, seq 2
5
total 49152, value 12FFFF, seq 3
6
total 61440, value 12FFFF, seq 4
7
[... snip ...]
8
total 9375744, value 12FFFF, seq 2FA
9
total 9388032, value 12FFFF, seq 2FB
10
total 9400320, value 12FFFF, seq 2FC
11
total 9412608, value 12FFFF, seq 2FD
12
total 9424896, value 912FFFF, seq 900FFFF
13
error

: Bearbeitet durch User
von Klaus (Gast)


Lesenswert?

Sven B. schrieb:
> Wenn
> ich mit Wireshark den Traffic auf der USB-Schnittstelle mitschneide,
> sind die Pakete alle da!

Ich muss jetzt nochmal was abklären, und zwar die Richtung:
1. Gerät -> Host
2. Host -> Gerät

Also bei welchem Transfer zeigt Dir Wireshark die Daten, aber sie kommen 
trotzdem nicht an?

von Sven B. (scummos)


Lesenswert?

Die Transfers sind immer Gerät -> Host. Also "In" im USB-Slang.

von Klaus (Gast)


Lesenswert?

Sven B. schrieb:
> Die Transfers sind immer Gerät -> Host. Also "In" im USB-Slang.

Also der Wireshark sagt Dir, dass die Daten am PC ankommen, und Du 
suchst nach dem Problem auf Microcontroller-Seite? Obwohl die Daten am 
PC ankommen?

Das musst Du mir mal erklären?

Und dann sind wir wieder bei meinem ersten Post!

Pit schrieb:
> Der Host geht also her und schreibt die Daten in den Ringpuffer. Wenn Du
> diese nicht rechtzeitig abholst, dann werden sie eben überschrieben und
> es fehlen ein paar kBytes.
> Es ist dem Host nämlich ziemlich egal, was mit den Daten passiert. Sein
> Job ist es nur, sie in den Ringpuffer zu schreiben.
>
> Der Applikation ist es nicht egal, ob die Daten überschrieben werden
> oder nicht. Und genau da kommt dann der Programmierer ins Spiel - also
> Du.
>
> Gruß Klaus

Gruß Pit und Klaus

von Kaj (Gast)


Lesenswert?

Ich hab das selbe Problem:
Meiner Meinung nach ist der ACM-Treiber oder irgendwas in der Gegen das 
Problem.

Beispiel:
Verbinde ich einen Arduino mit einem RPi2 über USB und lese von ttyACM 
gehen zeichen verloren.
Verbinde ich den Arduino (RX0/TX0) direkt mit der Pinleiste des RPi2 
(RX/TX) und lese von ttyAMA geht nichts verloren.
Es geht ebenfalls nichts verloren, wenn ich das ganze über eine 
UART-USB-Bridge (CP21xx) laufen lasse und von ttyUSB lese...

Ist natürlich nur (m)eine Theorie, das der ACM-Treiber das Problem 
darstellt... Just sayin'.

BTW:
Um in Python von dem Seriellenport zu lesen kannst du auch das Modul 
PySerial nutzen.

von Sven B. (scummos)


Lesenswert?

Klaus schrieb:
> Sven B. schrieb:
>> Die Transfers sind immer Gerät -> Host. Also "In" im USB-Slang.
>
> Also der Wireshark sagt Dir, dass die Daten am PC ankommen, und Du
> suchst nach dem Problem auf Microcontroller-Seite? Obwohl die Daten am
> PC ankommen?

Ich suche nicht nach dem Problem auf dem uC. Ich bin mir ziemlich 
sicher, dass das Problem auf dem Host ist.

> Und dann sind wir wieder bei meinem ersten Post!

Entschuldigung, aber der Post ist meines Erachtens Quatsch. USB hat die 
Möglichkeit, Pakete nochmal zu übertragen und die Übertragung zu 
verlangsamen wenn die Buffer voll sind etc. und natürlich werden diese 
Möglichkeiten hier auch im Hintergrund genutzt. Noch einmal, das ist ein 
Bulk-Endpoint, der garantiert laut Spezifikation, dass alle Daten die 
ich sende vollständig, fehlerfrei, und in der richtigen Reihenfolge auch 
ankommen! Die einzige Komponente, bei der ich mir nicht komplett sicher 
bin ob dasselbe gilt ist ACM. Wenn du dazu explizit weißt ob das 
garantiert dass keine Daten verloren geht oder dass das eben explizit 
nicht garantiert wird, bin ich dankbar; ansonsten vergiss bitte den "da 
musst du dich als Anwendungsentwickler drum kümmern"-Ansatz, der ist 
Unsinn, weil das an sich von den zugrundeliegenden Technologien 
garantiert wird. Außer ich irre mich da eben, dann bitte ich, mich zu 
erleuchten.

: Bearbeitet durch User
von Sven B. (scummos)


Lesenswert?

Kaj schrieb:
> Beispiel:
> Verbinde ich einen Arduino mit einem RPi2 über USB und lese von ttyACM
> gehen zeichen verloren.
> Verbinde ich den Arduino (RX0/TX0) direkt mit der Pinleiste des RPi2
> (RX/TX) und lese von ttyAMA geht nichts verloren.
> Es geht ebenfalls nichts verloren, wenn ich das ganze über eine
> UART-USB-Bridge (CP21xx) laufen lasse und von ttyUSB lese...
>
> Ist natürlich nur (m)eine Theorie, das der ACM-Treiber das Problem
> darstellt... Just sayin'.

Sehr interessant. Hast du da Details dazu wie oft das passiert, und 
unter welchem Umständen oder so?

Ich stimme dir zu, ich sehe auch keine andere Fehlerquelle als den 
cdc_acm-Treiber.

> BTW:
> Um in Python von dem Seriellenport zu lesen kannst du auch das Modul
> PySerial nutzen.

Danke, das tu ich auch, ich hab's nur rausgenommen um eine Komponente 
weniger zu haben in der ein Bug sein kann.

Seufz, ich glaub' ich muss dann wohl echt mal meinen Windows-PC anwerfen 
und das da versuchen.

: Bearbeitet durch User
von Sven B. (scummos)


Lesenswert?


von Klaus (Gast)


Lesenswert?

Sven B. schrieb:
> Noch einmal, das ist ein
> Bulk-Endpoint, der garantiert laut Spezifikation, dass alle Daten die
> ich sende vollständig, fehlerfrei, und in der richtigen Reihenfolge auch
> ankommen!

Natürlich ist das so. Alles andere wäre auch völliger Schwachsinn.

Dann kann es aber nur noch an den Treibern liegen. Und das sind nun mal 
nur der Kernel-Treiber (und dass es daran liegt, ist nur schwer 
vorstellbar) und der User Mode Treiber, also das Teil, was Du 
programmierst.

Stellt sich also die Frage: Machen die Kernel-Treiber Entwickler 
irgendwas falsch (und das wohl schon seit Jahren, oder gibt es diese 
Schnittstelle erst seit gestern?), oder liegt es an Deiner Software?

von Klaus (Gast)


Lesenswert?

Sven B. schrieb:
> Hah https://bugzilla.kernel.org/show_bug.cgi?id=14163
> Ist aber alt ...

Das hatte ich zuvor überlesen... Offenbar kann es durchaus an den 
Kernel-Jungs liegen - und das tatsächlich schon seit Jahren.
Unfassbar!

User:
'Hey, ich habe hier ein Problem mit dem USB, kann das mal einer fixen?'
Entwickler:
'Ich hab mal ne neue Version erstellt, ist das Problem jetzt weg?'
User 2:
'Naja, ich hab mal bischen rumprobiert. Bei mir klappt es.'
Entwickler:
'OK, dann ist das Problem wohl gelöst. Ich mach den Thread mal zu.'

Einfach nur geil! Was für Penner.

von Rolf M. (rmagnus)


Lesenswert?

Sven B. schrieb:
> while ( cur < size ) {
>             long r = read(fd, &buf[cur], size-cur);
>             if ( r < 0 ) {
>                 exit(3);
>             }
>             cur += r;
>         }

Ich verstehe nicht ganz, warum das überhaupt funktioniert. Da du die 
Schnittstelle mit O_NONBLOCK geöffnet hast, müßte read doch eigentlich, 
wenn keine Daten verfügbar sind, -1 zurückliefern und errno auf EAGAIN 
setzen.

von Sven B. (scummos)


Lesenswert?

Rolf M. schrieb:
> Ich verstehe nicht ganz, warum das überhaupt funktioniert. Da du die
> Schnittstelle mit O_NONBLOCK geöffnet hast, müßte read doch eigentlich,
> wenn keine Daten verfügbar sind, -1 zurückliefern und errno auf EAGAIN
> setzen.

Ich glaube es sind einfach immer Daten verfügbar.

Klaus schrieb:
> Stellt sich also die Frage: Machen die Kernel-Treiber Entwickler
> irgendwas falsch (und das wohl schon seit Jahren, oder gibt es diese
> Schnittstelle erst seit gestern?), oder liegt es an Deiner Software?

Meine Software ist halt nur zwei Zeilen lang. Da ist das Potential für 
Fehler nicht so groß.

Tatsächlich ist es halt so, dass cdc_acm wenn es einen Block liest 
diesen in den tty-Buffer schreibt:
http://lxr.free-electrons.com/source/drivers/usb/class/cdc-acm.c#L405
Wenn man jetzt aber schaut, was tty_insert_flip_string konkret tut, so 
ist es dies:
http://lxr.free-electrons.com/source/drivers/tty/tty_buffer.c#L324

Das kopiert also, bis der Buffer voll ist und wenn der voll ist hört es 
auf und gibt die Anzahl der kopierten Bytes zurück. Es wird aber 
nirgends überprüft dass das auch alle sind, die hätten kopiert werden 
müssen.
Jetzt ist halt die Frage, ob das Absicht ist oder nicht?
Alle anderen Treiber machen das jedenfalls genauso. Einer gibt zumindest 
eine Fehlermeldung aus ("dopping n bytes of data" blabla). 
Wahrscheinlich ist das also Absicht.

Im Prinzip gibt es wohl dieses Throttling, um Bufferüberläufe zu 
verhindern. Das setzt aber erst ein, wenn der Buffer nur noch 128 Byte 
Platz hat. Das könnte schon zu spät sein für diesen Anwendungsfall...
Die Frage ist also im Endeffekt: Müsste das Throttling diesen Fehlerfall 
mit Sicherheit verhindern?

: Bearbeitet durch User
von c-hater (Gast)


Lesenswert?

Sven B. schrieb:

> Peter II schrieb:
>> das würde ich nicht sagen. Wenn du die Daten aus ttyACM0 nicht schnell
>> genug aufragst, dann können sie auch verloren gehen.
>
> Das ist genau die Frage, die mich seit längerem beschäftigt. Ist das so?

Natürlich. Wenn Daten übertragen werden sollen, aber alle Buffer der 
Protokollschichten bereits voll sind, dann MÜSSEN *ZWINGEND* Daten 
weggeworfen werden.

> Ich glaube nicht: Wenn der Buffer voll ist, sollte der Host keine
> weiteren Pakete mehr annehmen, und soweit ich das sehen kann ist das
> auch das was passiert: Wenn man nur Daten anfragt aber nicht liest dann
> werden ein paar übertragen, und danach hängt der uC in der "warte auf
> `Endpoint bereit'"-Schleife fest.

So what? Dann wirft halt der µC die Daten weg... Ist doch prinzipiell 
vollkommen egal, wer das tut, das Ergebnis ist in jedem Fall: die 
weggeworfenen Daten kommen beim Empfänger nicht an. Aus die Maus.

Bei sinnvollen Implementierungen erfährt der Empfänger aber wenigstens 
noch, dass der Fall des Datenverlustes durch buffer overrun eingetreten 
ist und bekommt damit eine Möglichkeit, sich geordnet neu auf den 
Datenstrom zu synchronisieren. Nützlich für den Fall, dass der Abriss 
des Datenstroms nicht darauf zurückzuführen ist, dass der Empfänger 
generell zu langsam ist, sondern dass er nur durch ein kurzzeitig 
wirksame Ursache zeitweise vom Empfang abgehalten wurde.

von Peter II (Gast)


Lesenswert?

c-hater schrieb:
> Natürlich. Wenn Daten übertragen werden sollen, aber alle Buffer der
> Protokollschichten bereits voll sind, dann MÜSSEN ZWINGEND Daten
> weggeworfen werden.

Unsinn. Wenn jede Schicht prüft ob sie schreiben darf, dann geht nichts 
verloren.

von Sven B. (scummos)


Lesenswert?

c-hater schrieb:
> Natürlich. Wenn Daten übertragen werden sollen, aber alle Buffer der
> Protokollschichten bereits voll sind, dann MÜSSEN *ZWINGEND* Daten
> weggeworfen werden.
Nö, der Absender kann ja auch warten bis wieder Platz ist.

> So what? Dann wirft halt der µC die Daten weg... Ist doch prinzipiell
> vollkommen egal, wer das tut, das Ergebnis ist in jedem Fall: die
> weggeworfenen Daten kommen beim Empfänger nicht an. Aus die Maus.
Das ist natürlich nicht egal. Der uC kann doch als Absender viel besser 
entscheiden welche Daten er wegwirft als irgendein generischer 
Kerneltreiber auf dem Host. Der kann dann auch komplette Pakete 
verwerfen, statt irgendwo zufällig irgendwelche Bytes rauszuschneiden. 
Das dann wieder zu synchronisieren ist nämlich ziemlich schwierig ...

Ich hab mal ein bisschen rumgebastelt, es ist tatsächlich so, dass der 
tty-Buffer überläuft. Es gibt einen Throttling-Mechanismus, der 
eigentlich bevor dieser Fall eintritt dem Gerät signalisiert dass es 
warten soll, aber der greift viel zu spät (der Buffer ist 64k groß, das 
Throttling throttelt bei < 128 Byte frei, das müssten aber 16kB sein im 
vorliegenden Fall oder so). Hm.

: Bearbeitet durch User
von Paul B. (paul_baumann)


Lesenswert?

Sven B. schrieb:
> Es gibt einen Throttling-Mechanismus...

Wer das nicht beachtet, steht u.U. als Throttel da.

MfG Paul

von Sven B. (scummos)


Angehängte Dateien:

Lesenswert?

Ok, ich hab's, so tut es. Hat jetzt schon > 25 GB kopiert ohne Fehler, 
vorher war immer nach ein paar MB Schluss.

Der Code ist natürlich schrecklich (ich habe keine Ahnung von 
Kernel-Code), aber vielleicht kann das ja ein netter Kernel-Hacker 
nochmal ordentlich machen. Ich tu' es mal in den entsprechenden Bug 
Report. Ansonsten hab ich eben meinen eigenen Treiber, ist auch ok.

von Peter II (Gast)


Lesenswert?

> Hat jetzt schon > 25 GB kopiert ohne Fehler

nur aus Neugier?
Welche Datenrate erreichst du überhaupt?

von Sven B. (scummos)


Lesenswert?

Peter II schrieb:
> Welche Datenrate erreichst du überhaupt?

Ich brauche 11.7 MB/s, die erreiche ich auch. Das Maximum hab ich nicht 
so genau ausprobiert, liegt irgendwo bei 25 MB/s rum, je nach dem.

von Konrad S. (maybee)


Lesenswert?

Sven B. schrieb:
> Ich tu' es mal in den entsprechenden Bug
> Report.

Könntest du noch den Link auf den Bug-Report hier 'reinstellen?

von Sven B. (scummos)


Lesenswert?

https://lkml.org/lkml/2015/7/19/353

In dem Report wird sich wohl nix mehr tun, ich glaube die diskutieren 
nicht so gern Patches im Bugtracker. Verständlicherweise.

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.