Skip to content

alpha.110: an FoE read of config.csv on an Integro answered 3852 of 4432 bytes with success, after the PC held a mailbox frame for 100 ms #138

Open

Description

@apremji

mm-alpha110-desk.zip

What happens

On our desk Integro with alpha.110, one read of config.csv, a file of 4432 bytes, answered with 3852 bytes and success. MM's log:

[17:33:59.169] [debug] FOEread slave 1 'config.csv'
[17:33:59.285] [info] Device 1: aborted the FoE transfer
[17:33:59.385] [warning] FOEread slave 1 'config.csv' failed (FoE error, wkc -1); attempt 1 of 3
[17:34:05.485] [info] Device 1: aborted the FoE transfer
[17:34:05.585] [warning] FOEread slave 1 'config.csv' failed (no response); attempt 2 of 3
[17:34:05.755] [debug] FOEread slave 1 'config.csv' ok (3852 bytes)

The third attempt returned a file 580 bytes short. That is item 3 of synapticon/somanet_software#5817: after a transfer ends early, the Integro serves the rest of the file as a whole file. Here it happened with no netem rule, on the PC of #113 as it is.

What started it: the PC held a return for 100 ms

The capture of that run (POST /api/captures, 30 s beside the rounds) has one frame later than 20 ms. It is the read of the drive's receive mailbox that carried an FoE packet:

17:33:59.184866 from PC  FPRD idx 22 adp 0x1001 ado 0x1080 len 128
                         (no frame on the wire for 100 ms)
17:33:59.284969 from PC  FPRD idx 23 adp 0x1001 ado 0x080D len 1
17:33:59.284993 returned FPRD idx 22 adp 0x1001 ado 0x1080 len 128
17:33:59.284993 returned FPRD idx 23 adp 0x1001 ado 0x080D len 1
  • The return of idx 22 carries the same kernel timestamp as the return of the next frame, and comes 24 µs after that frame left the PC.
  • A mailbox frame's round trip is 22 µs at the median and 39 µs at the 99th percentile in this capture. Of the other 119,418 mailbox frames of the run, 3 came back later than 2 ms and none later than 20 ms.
  • So the drive answered at once, and the return sat in the PC's receive path until the next frame's return made the driver hand both up. It looks like a missed receive interrupt in r8169. Interrupt coalescing is off on the adapter (ethtool -c: rx-usecs 0, rx-frames 1).
  • The 100 ms is MM's own wait for the frame. The held return comes up when MM, after that wait, sends its next frame.
  • Run 3 shows the same, 100.139 ms after an FPRD of 0x1080, during an SDO read of 0x200E:00. alpha.99's second read rode through it: "got no answer (wkc -1); asked once more", then ok.

The #113 test on alpha.110

Same PC, drive and test as #113. A run is 10 rounds at PreOp of POST /api/devices/1/parameters/read (577 parameters, so 577 SDO uploads) and GET /api/devices/1/files/config.csv. The drive runs a v5.6 build with open firmware PRs, v5.6.11+10.g9bcc5ff33.

MM Runs Failed SDO reads per run Writes without a frame back per run Median parameter read config.csv reads that differ
alpha.110 3 0, 0, 0 0, 0, 0 2.3, 2.3, 2.3 s 0, 1, 0
alpha.93 3 0, 0, 1 76, 70, 54 2.9, 2.8, 2.5 s 0
alpha.92 2 13, 7 74, 64 3.1, 2.4 s 0
alpha.91 1 0 not logged 2.3 s 0

The parameter read is the time of the whole HTTP request, about 4 ms per SDO upload. The script then held the drive at OP for 30 s, not enabled, with a second capture:

Run Mailbox frames at PreOp Sent again Later than 2 / 20 / 100 ms Cyclic frames at OP Cyclic round trip, median / 99th percentile / max Cycles skipped at OP Short working counter
1 100,716 0 3 / 0 / 0 30,000 32 / 81 / 326 µs 0 0
2 119,419 1 4 / 1 / 1 30,000 31 / 88 / 224 µs 0 0
3 101,185 0 2 / 1 / 1 29,999 32 / 83 / 345 µs 1 0

The game loop held 1000 Hz with SCHED_FIFO, with a cycle time of 25 to 27 µs on average and 470 to 850 µs at most.

So alpha.110 is as fast as alpha.91, and what #113 reported is gone. What is left is the return the PC holds, about one in 100,000 mailbox frames here. When it falls into an FoE read, #5817 (3) turns it into a wrong file with status 200.

What should happen

A file read that answers with success returns the whole file.

Possible fixes

  • Do not wait 100 ms for a return the PC holds. After a short wait with no return, send one harmless frame, for example the SyncManager 1 status read that MM sends after its 100 ms today. Both held returns came up within 25 µs of the next frame.
  • After an FoE read on an Integro that ended early, answer the request with an error rather than trying again, or try again and answer only when two reads of the file agree. The tail #5817 (3) serves is shorter than the file.

Also seen

  • The capture summary counts as lost the last frame sent before the capture ends. Each of our six captures has one such frame, sent in the last microseconds, with lost: 1. A frame sent less than the longest wait, 100 ms, before the end could be counted apart.
  • The description of POST /api/captures says "A capture that shows late frames puts the delay on the wire or in the device." The kernel takes a return's timestamp when the driver hands it up, so a return the driver holds shows as late too, as here.

Environment

  • PC: AMD Ryzen 7 PRO 8840HS, Ubuntu 26.04.1 LTS, kernel 7.0.0-38-generic, no real-time patch. The bus is on a Realtek NIC, driver r8169, with interrupt coalescing off.
  • MM: 6.0.0-alpha.110 from the .deb.
  • Drive: SOMANET Integro (ACTILINK-S), serial 9024-05-0000764-2550, firmware v5.6.11+10.g9bcc5ff33. One drive on the bus.
  • The script: MMAF's dev/research/mailbox_ab.py with --rounds 10 --capture 30 --op-sec 30.

Files

The six captures, MM's log of the three runs, the script, and the pairing of the late frames are in a zip in a comment below.

Activity

  1. apremji commented on Oct 22, 2026

    @apremji
    Author

    The FoE trace of the short read

    Rebuilt from the FoE messages in run 2's PreOp capture, capture-20261022-173342-enp1s0f0.pcap. A master message is a write into SM0, and a drive message is a read of SM1 that came back with a working counter of 1.

    17:33:59.174212 master RRQ 'config.csv'
    17:33:59.178008 drive  DATA packet 1, 116 bytes
    17:33:59.178018 master ACK packet 1
    17:33:59.180110 drive  DATA packet 2, 116 bytes
    17:33:59.180119 master ACK packet 2
    17:33:59.181957 drive  DATA packet 3, 116 bytes
    17:33:59.181965 master ACK packet 3
    17:33:59.284993 drive  DATA packet 4, 116 bytes     <- the return the PC held for 100 ms
    17:33:59.285013 master ERROR code 0x8000             <- 20 µs later, MM ends the transfer
    17:33:59.485327 master RRQ 'config.csv'              <- no answer for 6 s
    17:34:05.485548 master ERROR code 0x8000
    17:34:05.686164 master RRQ 'config.csv'
    17:34:05.689726 drive  DATA packet 1, 116 bytes
                    ... packets 2 to 33, 116 bytes each, each ACKed ...
    17:34:05.755533 drive  DATA packet 34, 24 bytes
    17:34:05.755548 master ACK packet 34
    

    The 34 packets of the third read are exactly bytes 580 to 4432 of the file, which are packets 6 to 39 of a whole read, numbered 1 to 34. Every other read of config.csv in the capture is 39 packets and 4432 bytes. The first read of the three ended after 4 packets, 464 bytes, the head of the file.

    Why the read answered with success

    The third transfer is a valid FoE read as far as the protocol goes. It starts with an RRQ, its packet numbers count up from 1, every packet is acknowledged, and it ends with a packet shorter than the mailbox allows, which is how FoE marks the end of a file. FoE carries no file size or checksum, so nothing in the transfer itself shows that it starts at byte 580.

    • The drive (somanet_software#5817, item 3). While it waited for ACK 4, it answered neither the new RRQ nor the ERROR for 6 s, where an ERROR from the master ends the transfer (ETG.1000.6, Table 116). When it answered again, it served a new read from the middle of the file and numbered the packets from 1. This is where the wrong data comes from, and only the firmware can make every read start at byte 0.
    • MM. MM ended two transfers of the same file on the same Integro, then made a third and answered 200. Nothing in the answer says that it took three attempts or that two of them were aborted. MM knows from #5817 that an Integro can serve the tail of a file after an aborted read, so the retry turns an error the caller would have seen into a wrong file with success.
    • The PC. The held return made MM abort a transfer that was going well. Packet 4 came up 20 µs before MM sent the ERROR.

    What we would ask of MM

    1. After an FoE read on an Integro that ended early, do not answer a later attempt with success unchecked. Either answer with the error, or make one more read and answer only when two reads agree. A tail is shorter than the file, so two equal reads rule it out.
    2. At least, say in the answer how many attempts the read took.
    3. Avoid the abort itself, as in the issue: release a held return with a harmless frame after a short wait.

    The script that rebuilt the trace, foe_trace.py, is in the zip below.

  2. markosankovic commented on Oct 23, 2026

    @markosankovic
    Contributor

    A test for the held return

    held_frame tests the reading of the capture above, and it tests a remedy for it. Its source is hil/held_frame/main.cc on the branch of this issue.

    The binary: held_frame-portable.zip (attached below)

    held_frame-portable.zip

    The program reads config.csv over FoE again and again, with the frames MM sends for a file read: a mailbox write for the request and for each acknowledgement, a read of the SyncManager 1 status every 200 µs until the mailbox is full, and a read of the mailbox. The mailbox writes and mailbox reads go out once, as MM sends them. Two modes take turns, one file read each. They differ only in how they wait for the return of a mailbox write or a mailbox read:

    • plain waits up to 100 ms, as MM does today. When no return comes, it sends the SyncManager 1 status read, the frame MM sends next, and looks for the missing return beside its return. That is the event of the capture above. MM ends the transfer at that point. The program uses the return when it came, so the drive is never left part-way through the file.
    • nudge sends a NOP datagram after each 2 ms with no return, and looks for the return right after the NOP comes back. Every slave passes a NOP on and acts on nothing in it.

    Each read is compared with the first read of the run. The last line is one of these:

    • THE NOP RELEASES A HELD FRAME: in plain some returns came only beside the status read, in nudge a NOP released at least one return, and every return came within 20 ms.
    • NO HELD FRAME SEEN: no held return in plain. The traffic did not cause a hold, or the run was too short.
    • NOT CONFIRMED: anything else. The lines above it say what happened.

    How to run it

    On the PC of this issue, with the Integro on the bus and no MM server using the adapter:

    unzip held_frame-portable.zip
    chmod +x held_frame-portable
    sudo ./held_frame-portable --ifname <bus adapter> --reads 6000

    A read of config.csv is about 320 frames. The held return came about once in 100,000 mailbox frames, so 6000 reads give each mode about ten chances. That takes about 8 minutes. The program puts the drive into INIT and then PRE-OP, and leaves it in PRE-OP.

    The binary links libstdc++ statically, so it needs glibc 2.38 or newer and nothing else. It was built on Fedora 44 and was not tested on Ubuntu.

    sha256 28ec86f16ce0abc22a5d63f19d6047a215c1ee18a893b0fdedd022feba66ff6d  held_frame-portable
    

    What to send back

    • The whole output of each run.
    • uname -r and ethtool -i <bus adapter>.

    What the program does not cover

    • The traffic of MM is mostly SDO reads, and one of the two held returns of the issue came during an SDO read. The program sends FoE only.
    • The cause of the hold is not known. If it depends on the load of the PC, or on the other threads of MM, the program may not cause one. Then NO HELD FRAME SEEN says nothing about the NOP.

    On our bench

    Fedora 44, kernel 7.2.11-200.fc44.x86_64, the bus on a USB adapter with the r8152 driver (RTL8153B), and an Integro, product 0x401, 50 reads:

    plain: 25 reads, 25 same as the first, 0 different, 0 failed
      mailbox write  sent       976  late    0  released    0  lost    0  latest    247 us
      mailbox read   sent       950  late    0  released    0  lost    0  latest    420 us
      status read    sent      6005  late    0  released    0  lost    0  latest    424 us
    nudge: 25 reads, 25 same as the first, 0 different, 0 failed
      mailbox write  sent       975  late    0  released    0  lost    0  latest    256 us
      mailbox read   sent       950  late    0  released    0  lost    0  latest    408 us
      status read    sent      5984  late    0  released    0  lost    0  latest    386 us
      NOPs sent 0
    NO HELD FRAME SEEN
    

    An earlier version of the program sent 2,000,000 register reads on the same adapter and saw no held return either. This adapter does not hold returns, so the runs show only that the program works. Only a PC that holds returns can show whether the NOP releases them.

  3. markosankovic commented on Nov 1, 2026

    @markosankovic
    Contributor

    Results of held_frame

    Thank you for the run. The log has two runs of 6000 reads each, on enp1s0f0 with an Integro, product 0x401.

    Run 1: THE NOP RELEASES A HELD FRAME

    plain: 3000 reads, 2996 same as the first, 0 different, 4 failed
      mailbox write  sent    122513  late  168  released  161  lost    0  latest 100472 us
      mailbox read   sent    116844  late   30  released   24  lost    0  latest 100448 us
      status read    sent    914307  late   43  released    0  lost    0  latest   3525 us
    nudge: 3000 reads, 3000 same as the first, 0 different, 0 failed
      mailbox write  sent    122698  late  191  released  184  lost    0  latest   3160 us
      mailbox read   sent    117000  late   24  released   23  lost    0  latest   2303 us
      status read    sent    915348  late   53  released    0  lost    0  latest   3480 us
      NOPs sent 207
    
    • In plain, 185 returns came about 100.2 ms late, each one beside the return of the next frame. This is the event of the capture above.
    • In nudge, 207 NOPs were sent, and each one released a held return. These returns came about 2.2 ms late.
    • The 4 failed reads in plain say "the read request was not written". The host held the return of the read request itself.
    • The host held a return about once in 1,300 mailbox frames. My estimate above was once in 100,000.

    Run 2: NOT CONFIRMED

    plain: 3000 reads, 3000 same as the first, 0 different, 0 failed
      mailbox write  sent    122709  late    4  released    0  lost    0  latest   3072 us
      mailbox read   sent    117000  late    7  released    1  lost    0  latest 100161 us
      status read    sent    914496  late   25  released    0  lost    0  latest   3515 us
    nudge: 3000 reads, 3000 same as the first, 0 different, 0 failed
      mailbox write  sent    122708  late    3  released    0  lost    0  latest   3537 us
      mailbox read   sent    117000  late    7  released    0  lost    0  latest   3308 us
      status read    sent    914805  late   32  released    0  lost    0  latest   3658 us
      NOPs sent 0
    

    The host held one return in 6000 reads. At the end, the script restored napi_defer_hard_irqs 1 and gro_flush_timeout 20000. So it changed these two settings for one of the runs.

    What we need

    1. held_frame_ab.sh, to know which run used which settings.
    2. uname -r.
    3. ethtool -i enp1s0f0.

    If run 2 is the run with the settings changed, the settings cause the hold. In run 1 the NOP released every held return, so it can protect a host where nobody changes them.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions