USB lux - MarekBykowski/readme GitHub Wiki
Notatka robocza z bring-upu. Punkt wyjścia: kamera OAK (Luxonis) na kernelu 5.15 nie trzyma stabilnie linku na 10G (Gen2), po zejściu na 5G (Gen1) się uspokaja. Poniżej cały łańcuch: od topologii, przez xHCI i LTSSM, po stos kernela, metryki i drzewo decyzyjne naprawy.
Jednozdaniowa diagnoza: jeśli zejście z 10G na 5G leczy link, problem prawie na pewno siedzi w warstwie fizycznej/łącza (signal integrity albo LPM), a nie w protokole, sterowniku czy aplikacji. Debuguje się od dołu stosu w górę.
- Model działania i topologia
- Prędkości i generacje
- xHCI — model kontrolera
- URB i TRB
- LTSSM — warstwa łącza
- Stos kernela
- Enumeracja
- LPM — Link Power Management
- Debug: metryki, wartości, instrumentacja
- Drzewo decyzyjne naprawy
- Ściągawki
- Case study: OAK4 10G / DWC3 compliance mode
USB jest host-centryczny: jeden host (kontroler xHCI) na magistralę, wszystko inicjuje host. Urządzenia (funkcje) są bierne — odzywają się dopiero odpytane. Topologia to drzewo (tiered-star): root hub w warstwie 1, huby rozgałęziają, urządzenia są liśćmi.
graph TD
H["Host + Root Hub<br/><i>xHCI — inicjuje wszystko</i>"] --> HUB1["Hub"]
H --> CAM["Kamera OAK<br/><i>urządzenie SS</i>"]
HUB1 --> HUB2["Hub"]
HUB1 --> KBD["Klawiatura<br/><i>interrupt</i>"]
HUB2 --> DISK["Dysk USB<br/><i>bulk</i>"]
Limity: 7 warstw (budżet opóźnień), 127 adresów (adres 0 zarezerwowany dla
świeżo podłączonego urządzenia przed SET_ADDRESS).
Kto inicjuje: zawsze host. Na USB2 to dosłowny polling (token → data →
handshake). Na SuperSpeed urządzenie sygnalizuje NRDY/ERDY asynchronicznie
("mam gotowe, odpytaj mnie"), ale właściwy transfer i tak uruchamia host.
Port to punkt przyłączenia na hubie — istnieje niezależnie od tego, czy coś jest wpięte, i to on trzyma stan (connect, reset, PLS). Kluczowy niuans:
Jedno fizyczne gniazdo USB3 = dwa porty dla kontrolera: osobny USB2 i osobny SuperSpeed. Bo SS i USB2 to fizycznie dwie niezależne magistrale w jednym kablu (USB2 po D+/D−, SS po osobnych parach SSTX/SSRX). Dlatego urządzenie "spada" z SS do USB2 bez wyjmowania z gniazda — przełazi z jednej magistrali na drugą.
| Nazwa techniczna | Marketing | Linia | Lane | Kodowanie | ~MB/s | speed |
bcdUSB |
|---|---|---|---|---|---|---|---|
| USB 3.2 Gen 1 (3.0 / 3.1 Gen1) | USB 5Gbps | 5 Gb/s | 1 | 8b/10b | ~500 | 5000 |
0x0300/10 |
| USB 3.2 Gen 2 (3.1 Gen2) | USB 10Gbps | 10 Gb/s | 1 | 128b/132b | ~1210 | 10000 |
0x0320 |
| USB 3.2 Gen 2x2 | USB 20Gbps | 2×10 Gb/s | 2 | 128b/132b | ~2420 | 20000 |
0x0320 |
| USB4 Gen 2x2 | USB 20Gbps | 2×10 Gb/s | 2 | 64b/66b | ~2420 | 20000 |
0x0400 |
| USB4 Gen 3x2 | USB 40Gbps | 2×20 Gb/s | 2 | 128b/132b | ~4850 | — | 0x0400 |
| USB4 v2 Gen 4 | USB 80Gbps | 2×40 Gb/s | 2 | PAM-3 | ~9700 | — | 0x0400 |
| Tryb | Linia | speed |
|---|---|---|
| Low Speed | 1.5 Mb/s | 1.5 |
| Full Speed | 12 Mb/s | 12 |
| High Speed | 480 Mb/s | 480 |
Dlaczego Gen1→Gen2 to sedno problemu: 8b/10b (Gen1) ma 20% narzutu, ale jest odporne i prosto się trenuje. 128b/132b (Gen2) ma ~3% narzutu — ceną jest cięższy equalization (TSEQ) i dużo ciaśniejszy margines SI. Zejście na 5G "leczy", bo wraca do odporniejszego kodowania.
Lane'y = tylko Type-C. Warianty x2 potrzebują dwóch par TX/RX; na Type-A/microB SuperSpeed jest zawsze jednoliniowy → maks Gen2 10G.
W kernelu ignoruj marketing, patrz na twarde speed (5000/10000/20000)
i bcdUSB. bcdUSB 0x0320 + speed 5000 pod obciążeniem = czarno na białym
fallback z Gen2 do Gen1.
xHCI (eXtensible Host Controller Interface) — jeden kontroler obsługuje wszystkie prędkości (zastąpił UHCI/OHCI/EHCI). Sterowanie przez struktury w pamięci DMA, nie port I/O. Trzy filary:
-
Ringi (kolejki cykliczne):
- command ring — jeden na kontroler (Enable Slot, Address Device, Configure EP)
- transfer ring — jeden na endpoint (tu lądują TRB z URB)
- event ring — kontroler tu produkuje Transfer/Command/Port Status events
- Konteksty — DCBAA → Device Context (stan + do 31 Endpoint Contexts), Input Context (do zmian konfiguracji). Każde urządzenie = jeden slot.
- Doorbell + MSI-X — sterownik dokłada TRB i "dzwoni" doorbellem; kontroler wykonuje, wystawia event, podnosi przerwanie.
| Blok | Zawiera |
|---|---|
| Capability | RO: liczba portów/slotów, offsety |
| Operational |
USBCMD, USBSTS, CRCR, DCBAAP, PORTSC per port (tu PLS) |
| Runtime | interruptery, event ring (ERSTBA, ERDP) |
| Doorbell | po jednym na slot (slot 0 = command) |
PORTSC/PLS = chleb powszedni przy problemie z linkiem.
URB (USB Request Block) — programowa abstrakcja, struct urb, HCD-niezależna.
Jedno żądanie na jednym endpoincie. Sterownik funkcji mówi wyłącznie URB-ami.
TRB (Transfer Request Block) — 16-bajtowy deskryptor sprzętowy z spec xHCI, żyje na ringu w DMA. Typy: Normal, Setup/Data/Status, Isoch, Link, Event Data, No-op.
xhci-hcd tłumaczy: 1 URB → 1..N TRB
bulk >64KB / scatter-gather → wiele Normal TRB (chain bit)
control → 3 TRB (Setup / Data / Status)
isoc N pakietów → N Isoch TRB
| URB | TRB | |
|---|---|---|
| Warstwa | usbcore / sterownik | HCD (xHCI) |
| Definiuje | kernel Linux | spec xHCI (sprzęt) |
| Przenośność | dowolny HCD | tylko xHCI |
| Gdzie żyje | pamięć kernela | ring w DMA |
| Liczność | 1 żądanie | 1..N na URB |
Cycle bit — bit własności na ringu; rozjazd producenta/konsumenta to klasyczny bug przy ręcznym grzebaniu w ringu.
Diagnostyka: usbmon/wireshark = poziom URB; debugfs xhci + tracepointy =
poziom TRB/ringów/eventów.
LTSSM (Link Training and Status State Machine) rządzi linkiem SuperSpeed. Żyje poniżej URB/TRB/xHCI — najniższy programowo-widoczny poziom, tuż nad PHY. To tu objawia się problem z Gen2, zanim cokolwiek dotrze do warstwy URB.
stateDiagram-v2
[*] --> RxDetect
RxDetect --> Polling : wykrycie odbiornika
Polling --> U0 : lock + equalization
Polling --> Compliance : trening pada (SSP)
U0 --> Recovery : błąd na łączu
Recovery --> U0 : re-training OK
U0 --> U1U2U3 : bezczynność (LPM)
U1U2U3 --> Recovery : wyjście z U-state
Recovery --> SSInactive : trening pada
SSInactive --> [*] : fallback do USB2
Compliance --> [*] : link martwy (test state)
note right of Recovery
Cykliczne U0 ↔ Recovery
pod obciążeniem = marginalny link.
PUNKT OBSERWACJI.
end note
note left of Compliance
Stan dla sprzętu testowego.
W polu = link martwy.
Tu ląduje bug OAK4 10G
(patrz sekcja 12).
end note
- U0 — link aktywny, dane płyną.
- U1/U2/U3 — oszczędzanie energii (patrz LPM).
- Recovery — re-training po błędach lub przy wyjściu z U-state. Sporadycznie = norma; cyklicznie pod obciążeniem = marginalny link.
- SS.Inactive — trening padł twardo, link zjeżdża do USB2. Wymaga warm reset.
- Compliance — stan ze specyfikacji dla sprzętu testowego, wchodzony z Polling gdy trening SSP się nie uda. W polu = link martwy (zero transferu). To tu ląduje bug OAK4 10G — patrz sekcja 12.
Podgląd: stan LTSSM = PLS (Port Link State) w PORTSC, czyli
/sys/kernel/debug/usb/xhci/*/ports/* + tracepointy xhci-hcd.
usbcore to framework-broker. Nikt nie rozmawia ze sprzętem wprost — wszystko przez usbcore, które trzyma model sterowników, robi enumerację i zarządza URB.
graph TD
subgraph FN["Sterowniki funkcji / userspace"]
UVC["uvcvideo → V4L2"]
HID["usbhid → input"]
LIB["libusb / usbfs<br/>(depthai, bez klasy)"]
end
subgraph CORE["usbcore — framework USB"]
HUBD["Hub driver<br/>(hub_wq)"]
ENUM["Enumeracja"]
URBC["Rdzeń URB"]
MODEL["Model sterown.<br/>(usb_bus_type)"]
end
subgraph HCD["HCD — sterownik kontrolera"]
XHCI["xhci-hcd<br/>URB→TRB, ringi"]
XPCI["xhci-pci<br/>glue + quirki"]
end
HW["Kontroler xHCI (sprzęt)<br/>PCI, MMIO, DMA, MSI-X"]
FN --> CORE --> HCD --> HW
- Jeden
usb_bus_type; rejestrują się na nim i urządzenia, i interfejsy. - Sterownik zwykle bindowany do interfejsu, nie urządzenia. Jedno
usb_device= wieleusb_interface, każdy z osobnym sterownikiem (kamera UVC: video → uvcvideo, audio → snd-usb-audio). - Dwa typy:
struct usb_driver(per interfejs, codzienny) istruct usb_device_driver(per urządzenie, rzadki — m.in. generyk dla usbfs). - Match po
id_table:USB_DEVICE(vid,pid)alboUSB_INTERFACE_INFO(cls,sub,proto). - Rejestracja:
module_usb_driver()→usb_register_driver().
probe(intf, id):
// przejrzyj endpointy: intf->cur_altsetting->endpoint[i].desc
// usb_endpoint_is_bulk_in() / usb_rcvbulkpipe() ...
// usb_alloc_urb() → bufory DMA → usb_set_intfdata()
praca:
usb_fill_bulk_urb(); usb_submit_urb(GFP_*) // wraca od razu
// complete() leci w softirq (giveback); anchory: usb_anchor_urb()
disconnect():
usb_kill_urb() / usb_kill_anchored_urbs()
// MUSI przeżyć zniknięcie urządzenia (SS.Inactive w trakcie streamingu)Sterownik funkcji nie wie nic o xHCI/TRB/ringach — stąd przenośność.
struct hc_driver = tablica callbacków: .urb_enqueue, .urb_dequeue,
.add_endpoint, .address_device, .hub_control... usb_submit_urb() →
usbcore → hcd->driver->urb_enqueue() = xhci_urb_enqueue() → rozbicie na TRB.
Podział: xhci-hcd (rdzeń: ringi, konteksty) + xhci-pci (glue: probe PCI, BAR-y,
IRQ, usb_add_hcd(), quirki po PCI ID/DMI np. XHCI_COMP_MODE_QUIRK).
Na SoC ten sam xhci-hcd dostaje xhci-plat.
Sterowniki klasowe to mostki do innych podsystemów: uvcvideo→V4L2,
usbhid→input, usb-storage→SCSI/block. Wyjątek: usbfs (/dev/bus/usb/) —
libusb składa URB-y z userspace przez USBDEVFS_SUBMITURB (ścieżka depthai).
Konsekwencja diagnostyczna: uvcvideo loguje
-EPROTOdodmesg; depthai/libusb dostaje błąd w userspace jakoLIBUSB_ERROR_IO/_NO_DEVICE. Trzeba łapać oba końce.
Prowadzi ją hub driver w kontekście hub_wq (workqueue budzony zmianą statusu portu).
graph TD
A["Podłączenie<br/>hub → Port Status Change"] --> B["Reset portu<br/>ustala prędkość"]
B --> C["GET_DESCRIPTOR 8B @ adr 0<br/>poznaje max packet EP0"]
C --> D["SET_ADDRESS<br/>Enable Slot + Address Device"]
D --> E["Deskryptory + SET_CONFIGURATION<br/>device/config/iface/endpoint"]
E --> F["Dopasowanie sterownika<br/>probe: uvcvideo / libusb"]
- Adres 0 to wąskie gardło — enumeracja jednego urządzenia naraz.
- Prędkość ustala reset portu, nie deskryptory. Na SS to moment przejścia Polling+EQ → U0. Marginalny Gen2 może się tu w ogóle nie zenumerować jako SS → fallback do USB2 przed odczytem deskryptorów.
-
Match po interfejsie (
bInterfaceClass/Protocolalbo VID:PID). Depthai omija — bierze urządzenie surowo przez usbfs.
Gdzie pada (z dmesg):
-
device descriptor read/8, error -71→ krok 3 (link nie utrzymał control transferu) -
device not accepting address→ krok 4 - cykliczne re-enumeracje po udanej konfiguracji → link wpada w Recovery/Inactive w trakcie streamingu, urządzenie znika, host enumeruje od nowa
Usypianie linku dla oszczędności energii (stany U1/U2/U3 z LTSSM).
| Stan | Sen | Wybudzenie |
|---|---|---|
| U0 | aktywny | — |
| U1 | lekki (TX/RX off) | µs |
| U2 | głębszy | wolniejsze |
| U3 | suspend | najwolniejsze |
Klucz: wyjście z U1/U2 do U0 przechodzi przez Recovery — każde wybudzenie to mini-retrening. Na zdrowym Gen2 niewidoczne; na marginalnym link może się nie pozbierać i wpaść w pętlę Recovery.
-
Hardware LPM (U1/U2) — sprzęt sam wg wynegocjowanych progów;
sterowane
power/usb3_hardware_lpm_u1/_u2. -
Software (U3/suspend) — autosuspend; sterowane
power/control.
Dlaczego to kandydat na fix: LPM domyślnie ON, strojony pod energię nie stabilność. Kamery często deklarują zbyt optymistyczne exit-latency U1/U2 w BOS → host włącza agresywny LPM → wybudzenia się sypią. Urządzenie streamujące wideo i tak nigdy nie jest bezczynne, więc LPM daje głównie kłopoty. Wyłączenie = link siedzi w U0 stale, brak przejść power-state, brak tej klasy dropów.
USB nie ma odpowiednika PCIe AER. Brak znormalizowanych liczników correctable/uncorrectable. Metryki linku SS się syntezuje — z completion code'ów w tracepointach, ze zmian PLS i z dmesg.
| Metryka | Źródło | Dobrze | Źle |
|---|---|---|---|
| Prędkość | .../speed |
10000 stabilnie |
spadek do 5000 pod ruchem |
| Lane'y |
.../rx_lanes,.../tx_lanes
|
wg deklaracji | mniej niż deklaruje |
| PLS |
portsc / tracepoint |
U0 pod ruchem | Recovery cyklicznie; jakiekolwiek Inactive |
| Błędy transakcji | completion code / dmesg -71
|
~0 przy streamingu | dowolny rate; >kilka/min = SI |
| Resety SS | dmesg reset SuperSpeed
|
0 po enumeracji | powtarzalne pod ruchem |
| Re-enumeracje | dmesg connect/disconnect | raz, zostaje | pętle enumerate/disconnect |
| Isoc missed/underrun | tracepointy xhci | 0 | dowolne = zgubione klatki |
| runtime PM | .../power/runtime_status |
active gdy streamuje |
suspended w trakcie |
Spec zakłada BER 1e-12 — na zdrowym Gen2 błędy transakcji są praktycznie niewidoczne. Rate błędów transakcji = Twój proxy na BER. Widać gołym okiem pod obciążeniem → margines przepalony.
| Completion code (xHCI) | errno | Znaczenie |
|---|---|---|
| 4 — USB Transaction Error |
-71 (EPROTO) |
błąd na łączu = SI/link |
| 3 — Babble Detected | -75 |
za dużo danych / clock — często SI |
| 6 — Stall |
-32 (EPIPE) |
endpoint/protokół, nie fizyka |
| timeout |
-62 (ETIME) / -110
|
link nie odpowiedział / zniknął |
| 13 — Short Packet | status 0, actual<len | zwykle OK (normalny koniec) |
| Missed Service / Ring Under/Overrun | — | isoc timing — zgubione klatki |
- Baseline spoczynek vs pełne obciążenie → błędy tylko pod ruchem = margines.
- Gen1 vs Gen2 — rate błędów na 5G vs 10G. Zero/niezero = potwierdzenie SI.
-
LPM on vs off — spadek do zera po
echo 0= gałąź LPM, nie SI. -
Korelacja czasowa — nałóż PLS + błędy.
U1→Recovery→błądvsU0→Recovery→Inactive→warm resetrozstrzyga gałąź.
Poziom 1 — pasywny monitoring:
cat /sys/kernel/debug/usb/xhci/*/ports/port*/portsc # PLS:U0 / Recovery / Inactive
echo 'module xhci_hcd +p' > /sys/kernel/debug/dynamic_debug/control
dmesg -w | grep -Ei 'error|reset|inactive|link|-71|-62'Poziom 2 — ftrace:
ls /sys/kernel/debug/tracing/events/xhci-hcd/ # co dostępne w 5.15
echo 1 > /sys/kernel/debug/tracing/events/xhci-hcd/enable
echo 1 > /sys/kernel/debug/tracing/events/usb/usb_giveback_urb/enable
cat /sys/kernel/debug/tracing/trace_pipe | grep -Ei 'recovery|inactive|u1|u2|status=-'Poziom 3 — bpftrace (metryki, których kernel nie daje):
# histogram statusów giveback — rate błędów wg typu
bpftrace -e 'tracepoint:usb:usb_giveback_urb { @[args->status] = count(); }'
# rate błędów co sekundę
bpftrace -e 'tracepoint:usb:usb_giveback_urb /args->status != 0/ {
@err[args->status] = count(); }
interval:1s { print(@err); clear(@err); }'
# latencja submit → giveback (ogon = sygnatura re-treningu)
bpftrace -e 't:usb:usb_submit_urb { @s[args->urb] = nsecs; }
t:usb:usb_giveback_urb /@s[args->urb]/ {
@lat = hist(nsecs - @s[args->urb]); delete(@s[args->urb]); }'Sprawdź pola w
.../events/usb/usb_giveback_urb/format— nazwy bywają różne między wersjami;urbsłuży jako klucz do sparowania submit z giveback.
Eye diagram, faktyczny BER, jitter — nie ma w sysfs. Gdy trzeba stroić fizykę: retimer/redriver (rejestry LOS/signal-detect/profil EQ przez i2c/mdio), PHY receiver margining (jeśli wspiera), analizator protokołu (Ellisys/LeCroy) lub scope z eye monitorem na retimerze — ostateczny arbiter "kabel vs ścieżka PCB".
Obie ścieżki kończą w Recovery, ale mają różne przyczyny i różny fix. SS.Inactive nigdy nie jest problemem LPM — to twardy pad treningu linku.
graph TD
S["Pad 10G / link niestabilny"] --> Q0{"Ląduje w Compliance<br/>przy plug-inie?"}
Q0 -->|TAK| ORIENT["Link training race<br/>orientacja USB-C / PD timing"]
Q0 -->|NIE| Q{"Poprzedza je<br/>wejście w U1/U2?"}
Q -->|TAK| LPM["LPM (U-states)<br/>power state gubi link"]
Q -->|"NIE / SS.Inactive"| SI["Signal integrity<br/>trening linku pada"]
ORIENT --> FIX0["compliance-recovery quirk<br/>DWC3: timeout-ms / xHCI: COMP_MODE_QUIRK"]
LPM --> FIX1["Wyłącz LPM<br/>U1/U2, autosuspend, quirk NO_LPM"]
SI --> FIX2["Napraw fizykę<br/>kabel, retimer, EQ, Gen1"]
Czwarty liść (Compliance): odrębny od LPM i SI. Link ląduje w stanie Compliance (martwy) zwykle przy pierwszym plug-inie, nie pod obciążeniem po godzinie pracy. Przyczyna to wyścig treningu — link trenuje, zanim ustali się orientacja USB-C / PD. Fix to timing quirk (recovery), nie kabel. Pełny rozbiór w sekcji 12.
# 1. znajdź węzeł
lsusb -t ; ls /sys/bus/usb/devices/ # np. 3-1
# 2. wyłącz U1/U2 na żywo (test)
dev=3-1
echo 0 > /sys/bus/usb/devices/$dev/power/usb3_hardware_lpm_u1
echo 0 > /sys/bus/usb/devices/$dev/power/usb3_hardware_lpm_u2
echo on > /sys/bus/usb/devices/$dev/power/control
# 3. utrwal quirkiem NO_LPM (flaga 'k'); VID Movidius = 03e7
# (sysfs nie przeżywa replug/reboot)
usbcore.quirks=03e7:XXXX:k # w cmdline
usbcore.autosuspend=-1 # globalnie, na czas testówUwaga na TLP /
powertop --auto-tune— potrafią odkręcić autosuspend w tle.
Tego nie naprawisz z sysfs. Od najtańszego:
- Kabel — certyfikowany 10G, krótki (<1 m), reseat. #1 w terenie.
- Retimer/redriver — konfiguracja/profil na płycie (częsty problem na bring-upie).
- PHY tuning — EQ, de-emphasis, TX swing w rejestrach SerDes.
- PCB — długość/dopasowanie par, przelotki, masy.
- Zostań na Gen1 — świadomy workaround, jeśli 5G daje dość pasma.
Rzadki trzeci przypadek: kontroler błędnie wpada w Compliance/Inactive bez ruchu, zaraz po enumeracji, też na Gen1 → sprawdź
XHCI_COMP_MODE_QUIRK.
/sys/bus/usb/devices/<dev>/speed # 5000 / 10000
/sys/bus/usb/devices/<dev>/rx_lanes , tx_lanes
/sys/bus/usb/devices/<dev>/bcdUSB # 0x0320 = Gen2
/sys/bus/usb/devices/<dev>/power/usb3_hardware_lpm_u1 # LPM U1
/sys/bus/usb/devices/<dev>/power/usb3_hardware_lpm_u2 # LPM U2
/sys/bus/usb/devices/<dev>/power/control # autosuspend (on = off)
/sys/bus/usb/devices/<dev>/power/runtime_status
/sys/kernel/debug/usb/xhci/<hc>/ports/port*/portsc # PLS
/sys/kernel/debug/tracing/events/xhci-hcd/
/sys/kernel/debug/tracing/events/usb/
| Warstwa | Obiekt | Narzędzie |
|---|---|---|
| Fizyka / PHY | eye, BER, jitter | scope, analizator, retimer regs |
| Łącze (LTSSM) | PLS, Recovery, Inactive |
portsc, tracepointy xhci-hcd |
| xHCI | TRB, ringi, eventy | debugfs xhci, tracepointy |
| URB / protokół | transakcje, status | usbmon, wireshark, usb_giveback_urb
|
| Funkcja | klatki, IO | dmesg (klasa) / logi userspace (libusb) |
usbcore.quirks=VID:PID:k # k = USB_QUIRK_NO_LPM
usbcore.autosuspend=-1 # globalnie wyłącz autosuspend
Zadanie: "zbadaj, czemu USB 10Gbps czasem sprawia problemy" na platformie Luxonis
RVC4 / OAK4 (fork linux-msm-5.15). Konkretna instancja całej teorii powyżej —
z jedną istotną korektą modelu.
Cały stos z sekcji 6 zakładał xHCI po stronie hosta. RVC4 to co innego:
- kontroler = DWC3 (Synopsys DesignWare, dual-role), nie samodzielne xHCI;
- właściwy glue = downstreamowy
dwc3-msm-core.c(7500+ linii, Qualcomm), nie mainlinedwc3-qcom.c(ten jest tu martwym kodem — wzorzec "decoy"); - OAK4 występuje w dużej mierze jako urządzenie (gadget) — wystawia się hostowi
jako UVC (
f_uvc.c) i sieć-over-USB (u_ether.c).
Framework się przenosi mimo to: DWC3 w trybie host = interfejs xHCI (więc sekcje 3–4 wciąż obowiązują, gdy DWC3 hostuje np. hub qps615), a LTSSM/Compliance/rejestry stanu łącza są symetryczne — obie strony linku prowadzą własną maszynę stanów.
graph TD
HOST["Host: xhci-hcd<br/>stos z sekcji 3-6 (hub qps615)"] --> DWC3
DEV["Device: gadget UVC<br/>f_uvc, configfs — OAK jako kamera"] --> DWC3
DWC3["DWC3 core + dwc3-msm-core.c<br/>glue Qualcomma, własny SM compliance"] --> LTSSM
LTSSM["Warstwa łącza SS — LTSSM<br/>Compliance = pad treningu SSP"] --> PHY
PHY["PHY / USB-C<br/>orientacja, PD tps25750, brak SS mux"]
Commit Luxonisa 78a89311afa8 ("Fix the controller entering compliance mode")
dokumentuje: kontroler DWC3 wchodzi w compliance mode przy pierwszym plug-inie,
odtwarzalne na hostach z SSP (= SuperSpeedPlus = 10G) i bez PD.
DWC3_LINK_STATE_CMPLY to stan LTSSM ze specyfikacji USB3 przeznaczony dla sprzętu
testowego — w polu oznacza martwy link. To mapuje się jeden do jednego na objaw
"10G czasem nie działa" i na czwarty liść drzewa.
Terminy potrzebne, żeby zrozumieć root cause poniżej.
- Para SS — para różnicowa (dwa przewody, sygnał = różnica napięć). Jeden kierunek = jedna para: TX+/TX− do nadawania, RX+/RX− do odbioru. Jeden lane SS = dwie pary (TX + RX) = 4 przewody, pełny dupleks.
- Dwa komplety par w USB-C — złącze jest odwracalne, więc fizycznie ma dwa komplety par SS: lane 1 (TX1/RX1) i lane 2 (TX2/RX2), po przeciwnych stronach wtyku. Zwykłe USB 3.2 Gen1/Gen2 (5G/10G) to jeden lane — używa naraz tylko kompletu zgodnego z orientacją wtyku. Drugi jest wtedy nieaktywny. (Gen2x2 20G używa obu → tylko Type-C.)
- SS mux — przełącznik 2:1 (crosspoint), który wybiera, który komplet par podłączyć do jednego PHY SS kontrolera, wg wykrytej orientacji. Bywa osobnym układem albo zintegrowany w retimerze/redriverze lub w kontrolerze PD. Bez niego kontroler z jednym PHY nie obsłuży obu orientacji czysto.
-
PD / CC /
tps25750— USB Power Delivery negocjuje zasilanie po liniach CC (Configuration Channel). Ten sam układ CC/PD wykrywa orientację wtyku (po tym, która linia CC1/CC2 jest podciągnięta) i mówi muxowi, którą parę wybrać.tps25750to układ PD od TI. Detekcja zajmuje czas po wpięciu — nie jest natychmiastowa. - Pływająca para — para elektrycznie niepodłączona do niczego określonego, w stanie wysokiej impedancji (high-Z), bez sygnału. W połączeniu USB-C tylko jeden komplet niesie sygnał; drugi często wisi (daleki koniec kabla niepodłączony). Trening linku na pływającej parze nie ma jak się udać — nie ma nadajnika po drugiej stronie.
Pinout — gdzie te pary siedzą fizycznie:
| Pin | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| A | GND | TX1+ |
TX1− |
VBUS | CC1 |
D+ | D− | SBU1 | VBUS | RX2− |
RX2+ |
GND |
| B | GND | TX2+ |
TX2− |
VBUS | CC2 |
D+ | D− | SBU2 | VBUS | RX1− |
RX1+ |
GND |
- Lane 1 = para TX1 (A2/A3) + para RX1 (B10/B11)
- Lane 2 = para TX2 (B2/B3) + para RX2 (A10/A11)
- CC1/CC2 = linie orientacji (stąd PD ją odczytuje)
Lane 1 leży po jednej przekątnej (TX góra-lewo, RX dół-prawo), lane 2 lustrzanie. Flip wtyku o 180° zamienia rzędy A↔B → lane 1 i lane 2 zamieniają się rolami. Dlatego są dwa komplety par: żeby oba włożenia wtyku dały działający link. DWC3 ma jeden PHY SS (1 para TX + 1 RX), więc w danej orientacji jeden komplet jest aktywny, drugi pływa — i to na tym pływającym trening pada w Compliance.
graph LR
P1["Para SS #1<br/>TX1/RX1 — aktywna"] --> MUX
P2["Para SS #2<br/>TX2/RX2 — pływa (high-Z)"] -.-> MUX
MUX["SS mux 2:1<br/>wybiera parę"] --> PHY["DWC3 PHY SS<br/>1 para TX + 1 RX"]
PD["PD tps25750<br/>orientacja z CC"] -.->|steruje| MUX
Normalnie mux wybiera aktywną parę wg orientacji z CC. Bug OAK4: brak muxa + PD nie ustali orientacji przed treningiem → DWC3 trenuje na złej/pływającej parze → Compliance. "Pierwszy plug-in" = wyścig na starcie; "hosty z SSP" = dopiero trening 10G jest tak wrażliwy. Quirk
compliance-recovery-timeout-msodczekuje, aż orientacja się ustali, i restartuje trening — łata na ten wyścig.
Teoria z commita: gdy obie pary SS są podłączone do kontrolera, a płyta nie ma
SS-muxa, i detekcja orientacji USB-C (PD tps25750) nie zdąży się ustalić przed
pierwszym treningiem — DWC3 trenuje na złej/pływającej parze SS → Compliance.
Łata to delayed check-and-restart: po resecie odczekaj
compliance_recovery_timeout_ms, przeczytaj DSTS, jeśli link state = CMPLY,
wymuś pełny restart USB. Włączane przez devicetree:
&usb0 {
qcom,compliance-recovery-timeout-ms = <300>;
};
Ale: DTS OAK4 (kalamap-oak4s_r0-overlay.dts → dziedziczy z
kalamap-rb5-gen2.dtsi) ustawia redriver SS i tuning eUSB2 PHY, lecz nie ustawia
tego knoba. Znaleziony włączony tylko na referencyjnej płycie Qualcomma
(kalama-jf6961.dtsi, 450 ms) — nie na produkcie Luxonisa. Fix na tę dokładną klasę
buga istnieje w sterowniku i jest martwy na Twojej płycie.
Ta sama warstwa LTSSM, inny rejestr zależnie od strony:
| Strona | Rejestr | Pole | Wartość "martwy link" |
|---|---|---|---|
| Host (xHCI) | PORTSC |
PLS | Compliance / Inactive |
| Device (DWC3) | DWC3_DSTS |
USBLNKST |
DWC3_LINK_STATE_CMPLY |
grep po DWC3_LINK_STATE_CMPLY i USBLNKST w core.h daje enum i offset.
-
Potwierdź CMPLY — dmesg z awarii + odczyt
DWC3_DSTS.USBLNKSTw momencie padu (debug print).CMPLY= teoria potwierdzona. Inny stan (Recovery/SS.Inactive) → wróć do gałęzi LPM/SI z sekcji 10. - A/B Gen1/Gen2 — czy na wymuszonym 5G Compliance znika. Odróżnia "trening SSP pada" od głębszego problemu.
- Dopiero potem włącz uśpiony quirk na DTS OAK4 (300 ms) i sprawdź, czy intermittentne padnięcia znikają.
Plaster vs fix: quirk to recovery — wykrywa Compliance i restartuje link, więc zamaskuje objaw nawet przy innej przyczynie. Właściwy fix wymaga potwierdzenia CMPLY (krok 1) i korelacji z momentem ustalania orientacji PD. Jeśli root cause to naprawdę timing
tps25750, docelowo lepszy jest fix po stronie sekwencji orientacja→trening niż stały timeout.
| Commit | Obszar | Opis |
|---|---|---|
78a89311afa8 |
dwc3-msm | fix wchodzenia w compliance mode |
7e1ecf5762f0 |
dwc3-msm | init compliance_recovery_timeout_ms=0 bez DTS |
0a5dc85279a8 |
dwc3 core | hardening teardown gadgetu na races suspend/resume |
3fe3466c5c6d |
typec/tipd | maskowanie IRQ w tps25750 (PD controller) |
ffc08081698f |
gadget uvc | port zmian UVC z kernela RPi |
Kolejność debugowania: potwierdź fallback (speed) → skoreluj PLS z błędami pod
obciążeniem → A/B Gen1/Gen2 i LPM on/off → wskazuje gałąź i mierzy skuteczność fixu,
zanim zejdziesz do retimera/scope.
Case OAK4: potwierdź USBLNKST=CMPLY przy plug-inie → A/B Gen1/Gen2 → włącz uśpiony
qcom,compliance-recovery-timeout-ms na DTS OAK4. Root cause (orientacja/PD timing)
pozostaje do potwierdzenia — quirk to recovery, nie właściwy fix.
Based on axxia
Device Tree: dr_mode = "host"
│
▼
┌─────────────────────────────────────────────────────────┐
│ DWC3 core.c (DWC3 = DesignWare USB3 — blok IP Synopsys) │
│ │
│ dwc3_probe() │
│ │ │
│ ├─ dwc3_get_dr_mode() ← czyta/weryfikuje dr_mode │
│ │ (dr_mode = "dual role mode": host/peripheral/otg)│
│ │ │
│ ├─ dwc3_core_init() ← init rejestrów, PHY │
│ │ │
│ └─ dwc3_core_init_mode() │
│ │ │
│ │ (bo dr_mode == HOST) │
│ ▼ │
│ dwc3_host_init() [host.c] │
└─────────────────────────────────────────────────────────┘
│
│ platform_device_alloc("xhci-hcd", ...)
▼
┌─────────────────────────────────────────────────────────┐
│ nowe urządzenie platformowe: "xhci-hcd" │
│ (rodzic = urządzenie DWC3, dostaje zasoby MMIO + IRQ) │
└─────────────────────────────────────────────────────────┘
│
│ binduje się do sterownika
▼
┌─────────────────────────────────────────────────────────┐
│ xhci-plat.c (glue driver dla xHCI na platform_device) │
│ │ │
│ ▼ │
│ xhci.c (rdzeń logiki zgodnej ze specyfikacją xHCI) │
│ │
│ xHCI = eXtensible Host Controller Interface │
│ (standard Intela: jak software steruje kontrolerem │
│ hosta USB — enumeracja portów, transfery, zarządzanie │
│ deskryptorami TRB itd.) │
└─────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────┐
│ usbcore (ogólna warstwa USB w jądrze Linuksa) │
│ │ │
│ ▼ │
│ realne urządzenia USB podłączone do portu │
│ (pendrive, kamera, hub, itd. — enumerowane standardowo) │
└─────────────────────────────────────────────────────────┘
Czyli finalnie: dr_mode → dwc3_host_init → urządzenie "xhci-hcd" → xhci_plat_probe → dwa usb_hcd (USB2 + USB3) → usbcore → realne urządzenie na porcie
xhci_plat_probe()
└─ usb_add_hcd(hcd, irq, ...)
└─ (standardowy callback hc_driver->reset)
└─ xhci_init() [xhci.c:574]
└─ xhci_mem_init() [xhci.c:589 → xhci-mem.c:2359]
├─ alokuje DCBAA
├─ xhci_ring_alloc(..., TYPE_COMMAND, ...) → cmd_ring
└─ xhci_ring_alloc(..., TYPE_EVENT, ...) → event_ring
Linux kernel for DWC3 https://docs.kernel.org/driver-api/usb/dwc3.html