
Firmware per Raspberry Pi Pico W che crea un adattatore Wi-Fi USB senza driver con bridging trasparente di livello 2, autenticazione WPA2/WPA3 e console di gestione fuori banda.
= pico-usb-wifi :toc: macro :toclevels: 3 :idprefix: :idseparator: -
pico-usb-wifi è un firmware per il Raspberry Pi Pico W che lo trasforma in un adattatore Wi-Fi USB senza driver, enumerandosi come dispositivo USB CDC-NCM.
:figure-caption: AI Slop
.Diagramma di pico-usb-wifi image::images/openrouter-banana2-rpi-pico.png[]
Il firmware funziona come un bridge trasparente di livello 2 che inoltra i frame tra l'interfaccia wireless del Pico W e la sua interfaccia USB. L'interfaccia USB dell'host adotta l'indirizzo MAC della stazione Wi-Fi del Pico W, fornendo un'unica identità MAC e IP end-to-end.
Non è richiesto alcun driver lato host, modulo del kernel o stack wireless; vedi <<no-host-side-wi-fi-stack,Nessuno Stack Wi-Fi Lato Host>>.
L'host ha bisogno solo dei driver cdc_ncm e cdc_acm inclusi nel sistema, presenti in ogni sistema operativo moderno: Linux, macOS, Windows e mobile.
== Caratteristiche
pico-usb-wifi offre queste funzionalità:
.Una situazione del mondo reale image::images/slop_2.png[]
== Perché Esiste
Mi serviva un adattatore Wi-Fi USB per un prossimo progetto embedded Linux. Non avevo un dongle Wi-Fi USB economico, quindi invece di andare a comprarne uno in un negozio fisico per cinque USD, ho passato due giorni di un lungo weekend festivo e circa un milione di token di Claude Code a costruire questo firmware.
白一百, Autore di pico-usb-wifi
Google ha detto che non era fattibile:
.pico-usb-wifi "Non fattibile" image::images/gemini_says_not_possible.png[]
toc::[]
[#no-host-side-wi-fi-stack] == Nessuno Stack Wi-Fi Lato Host
A differenza di un dongle Wi-Fi USB, questo adattatore espone all'host solo un'interfaccia simile a Ethernet. Il Pico W contiene l'intero lato wireless: la radio, l'associazione, il supplicant WPA2/WPA3 e il dominio regolatorio.
Questo consente ai sistemi di evitare l'installazione di wpa_supplicant, dello stack wireless cfg80211/mac80211, di un database regolatorio e del firmware del chipset o di un driver del vendor.
La configurazione delle credenziali Wi-Fi avviene sul dispositivo, tramite la sua console di gestione fuori banda, non attraverso strumenti wireless lato host.
Questo consente a un host vincolato o appliance, o a un host privo di driver wireless, o il cui kernel del vendor ne è privo, di connettersi a reti wireless usando solo driver universali di classe CDC.
== Come Funziona
[#fig-topology] .Diagramma di topologia image::images/topology.svg[Topologia bridge trasparente layer-2,820]
All'interfaccia USB dell'host viene assegnato l'indirizzo MAC della stazione Wi-Fi del Pico W, quindi esiste un unico MAC end-to-end e il Pico W può inoltrare i frame Ethernet così come sono tra USB e Wi-Fi. Una stazione Wi-Fi non può fare da bridge per più indirizzi MAC, quindi ricondurre host e stazione a un unico MAC è ciò che rende possibile un bridge trasparente. La motivazione completa, il percorso dei dati e la gestione IPv6/multicast sono descritti in <<architecture,Architettura>>.
== Requisiti dell'host
L'host richiede i driver cdc_ncm e cdc_acm inclusi nel kernel.
Entrambi fanno parte del kernel Linux mainline da oltre un decennio, quindi qualsiasi kernel attualmente supportato li include.
Non è coinvolto alcun modulo fuori tree, blob di firmware o driver del vendor.
Gli stessi driver di classe esistono su macOS, Windows 10 e successivi, Android e iOS.
[NOTE] Nessun altro sistema operativo è stato testato.
== Compilazione
Il progetto è un progetto CMake standard del pico-sdk. Richiede la toolchain embedded ARM, CMake, un backend di build (Ninja o Make), Python 3 e un checkout del pico-sdk con i suoi sottomoduli. TinyUSB e lwIP inclusi nel pico-sdk vengono usati senza modifiche.
=== Dipendenze
Sui sistemi basati su Arch (Arch, CachyOS, Manjaro), la toolchain proviene dai repository ufficiali:
arm-none-eabi-newlib fornisce la libreria C embedded e gli header; senza di essa il cross-compilatore non riesce a trovare stdint.h e header simili.
libusb serve solo per picotool, che il pico-sdk compila dai sorgenti durante la prima configurazione per generare il file UF2; non è richiesto alcun pacchetto picotool separato.
=== Passaggi di compilazione
git clone -b 2.2.0 --recurse-submodules https://github.com/raspberrypi/pico-sdk export PICO_SDK_PATH="$PWD/pico-sdk"
cp src/wifi_config.h.example src/wifi_config.h # then edit SSID/password, or leave blank cmake -S . -B build -G Ninja -DPICO_BOARD=pico_w -DCMAKE_BUILD_TYPE=Release cmake --build build
Il flag -G Ninja è opzionale; omettilo per usare il generatore Make predefinito (quindi cmake --build build -j).
wifi_config.h contiene le credenziali predefinite a tempo di compilazione ed è ignorato da git.
Lasciarlo vuoto produce un'immagine senza credenziali incorporate, che viene configurata a runtime tramite la console di gestione (<<management-console,Console di Gestione>>); riempirlo incorpora una rete predefinita.
== Scrittura del firmware
I passaggi seguenti caricano il firmware sulla scheda.
. Tieni premuto il pulsante BOOTSEL mentre colleghi la scheda tramite USB.
Viene montata come volume di memorizzazione di massa USB RPI-RP2, di solito in /run/media/<user>/RPI-RP2 o /media/<user>/RPI-RP2.
. Copia pico-usb-wifi.uf2 su quel volume.
La scheda si riavvia automaticamente avviando il firmware.
. Collega la scheda all'host che deve ricevere la connettività Wi-Fi.
== Uso su un host Linux
Collega il dispositivo all'host e configura una volta le sue credenziali Wi-Fi tramite la console di gestione (<<management-console,Console di Gestione>>). L'interfaccia dell'host si comporta quindi come qualsiasi connessione cablata sulla rete dell'access point.
Un host che gestisce le interfacce automaticamente (NetworkManager, systemd-networkd, dhcpcd) non richiede configurazione: esegue DHCP e SLAAC sul bridge e riceve un singolo indirizzo IPv4, un indirizzo IPv6, il gateway dell'access point e il DNS, esattamente come farebbe un client cablato.
Non esiste alcun indirizzo o gateway lato dispositivo da configurare, perché il Pico non ne possiede.
L'indirizzo MAC dell'interfaccia è il MAC della stazione Wi-Fi, ed è così che un'unica identità viene presentata alla rete.
L'output del comando ip qui mostra l'interfaccia risultante: un normale client DHCP/SLAAC sulla sottorete dell'access point, con il MAC della stazione e nessuna traccia del Pico.
[#management-console] == Console di Gestione
La console di gestione è il front-end di configurazione, sulla prima funzione seriale CDC-ACM (di solito /dev/ttyACM0).
È raggiungibile appena il dispositivo si enumera, prima che il Wi-Fi sia associato, quindi la configurazione non richiede mai una rete.
Apri la console con un terminale seriale come picocom o screen; la velocità di baud è irrilevante per USB CDC.
La console ripete l'input e mostra un prompt, e ogni comando stampa lo stato completo del dispositivo.
L'autenticazione Wi-Fi è WPA2-PSK o WPA3-SAE (AES).
La password è la passphrase della rete, oppure aperta quando la password viene lasciata vuota.
Un profilo protetto da password usa la modalità di transizione WPA2/WPA3, quindi si associa a entrambi i tipi di access point.
La console memorizza fino a otto profili di credenziali; uno è il profilo attivo, e il dispositivo si associa a esso.
set ssid/set pass modificano il profilo attivo, list/use/del gestiscono l'insieme e scan scopre le reti vicine e consente di unirsi a una da un elenco numerato -- utile quando un SSID contiene caratteri difficili da digitare.
Le parole dei comandi non sono sensibili alle maiuscole; la console le mostra in minuscolo.
La sessione seguente configura una rete cercandola tramite scansione.
$ picocom /dev/ttyACM0
Una modifica ha effetto immediato, ri-associando il profilo attivo; non è richiesto un riavvio.
Ripeti per salvare altre reti; list le mostra e use <n> cambia il profilo attivo:
save salva ogni profilo in flash; restore scarta le modifiche non salvate ricaricando il record salvato.
La tabella seguente elenca il set di comandi.
[#tbl-config-commands] .Comandi della console di gestione [cols="2,3", options="header"] |=== |Comando |Effetto
|set ssid <text>
|Imposta l'SSID del profilo attivo (il valore può contenere spazi) e ri-associa; crea il primo profilo se non esiste.
|set pass <text>
|Imposta la passphrase WPA2/WPA3 del profilo attivo (vuota per una rete aperta) e ri-associa.
|set country <CC\|WORLDWIDE>
|Imposta il paese regolatorio (applicato completamente al prossimo avvio).
|set debug <on\|off>
|Trasmette la diagnostica sulla console di debug; vedi <<debug-console,Console di Debug>>.
|list
|Elenca i profili salvati, contrassegnando quello attivo.
|use <n>
|Rende attivo il profilo n e ri-associa.
|del <n>
|Elimina il profilo n.
|scan
|Scansiona le reti vicine e apre il sottomenu di scansione (back, join <n>, scan per ripetere o live per un flusso continuo disassociato). join prepara la rete scelta come profilo attivo, pronta per set pass.
|save
|Salva tutti i profili e le impostazioni in flash.
|restore
|Scarta le modifiche non salvate ricaricando le impostazioni salvate.
|===
L'indirizzo assegnato all'host è mostrato nel dump di stato come host IPv4 e host IPv6, rilevato passivamente dal traffico in bridge, poiché il Pico non possiede alcun indirizzo da riportare.
Il settore di configurazione si trova alla fine della flash, separato dall'immagine del programma all'inizio, quindi un normale reflash di pico-usb-wifi.uf2 lascia intatti i profili salvati (una cancellazione completa del chip li rimuove).
L'eccezione è l'aggiornamento a v1.1.0: il layout del record è cambiato per contenere più profili, quindi un record pre-1.1.0 viene scartato e le reti devono essere reinserite una volta (vedi il changelog).
[#debug-console] == Console di Debug
La console di debug è un flusso diagnostico di sola scrittura sulla seconda funzione seriale CDC-ACM (di solito /dev/ttyACM1).
Rimane silenziosa finché non viene emesso set debug on sulla console di gestione, quindi non ha alcun costo quando è disattivata e non interferisce mai con la gestione.
Quando è abilitata, riporta i cambi di associazione e una riga periodica di statistiche del bridge, come nella sessione seguente.
Una build con -DTRACE_FRAMES=1 aggiunge un riepilogo di una riga per ogni frame bridgato, ma inonda la console sotto carico, quindi è disattivata per impostazione predefinita.
I campi delle statistiche sono descritti nella tabella seguente.
[#tbl-debug-stats] .Campi delle statistiche di debug [cols="1,3", options="header"] |=== |Campo |Significato
|->wifi
|Frame inoltrati dall'host al Wi-Fi.
|->host
|Frame inoltrati dal Wi-Fi all'host.
|txdrop
|Frame host-to-Wi-Fi scartati perché la stazione non era ancora associata (l'host ritenta).
|rxdrop
|Frame Wi-Fi-to-host scartati perché il lato USB non riusciva a smaltirli abbastanza velocemente.
|refl
|Frame Wi-Fi-to-host scartati perché erano la trasmissione dell'host stesso, riflessa dall'access point.
|poolfail
|Frame host-to-Wi-Fi scartati perché il pool di pbuf di lwIP era momentaneamente esaurito.
|ringpk
|Picco di profondità della coda di trasmissione USB Wi-Fi-to-host (su 32) dall'ultima riga di statistiche, poi azzerato -- un indicatore live, dove un valore vicino a 32 significa che la USB non riesce a smaltire velocemente quanto il Wi-Fi consegna. (A differenza di un massimo assoluto, torna a scendere una volta superato il burst.)
|link
|Stato del collegamento Wi-Fi della stazione: up (associata), join/down (in associazione) o un motivo di errore -- badauth (passphrase errata), nonet (SSID non trovato), fail.
|hangs
|Numero di volte in cui il watchdog ha recuperato il firmware da un blocco dall'ultimo avvio a freddo; vedi <<automatic-recovery,Recupero Automatico>>.
|faults
|Fault hardware da cui il firmware si è ripreso dall'ultimo avvio a freddo.
|faultpc
|Indirizzo del fault hardware più recente (0x00000000 se nessuno), per il mapping con addr2line.
|freeram
|RAM libera in byte, per valutare il margine quando si ottimizzano le dimensioni dei buffer.
|===
La traccia per-frame condivide il collegamento USB Full-Speed con il traffico bridgato, quindi riduce sia il throughput sia inonda la console; è un'opzione a tempo di build (-DTRACE_FRAMES=1) pensata solo per un debug approfondito.
[#automatic-recovery] == Recupero Automatico
Un watchdog hardware riavvia il dispositivo se il firmware smette di servire il suo loop principale - un blocco o un deadlock del driver - quindi si rienumera da solo entro pochi secondi invece di dover essere scollegato. Un handler separato per i fault hardware intercetta immediatamente un fault della CPU e registra l'indirizzo del fault.
I contatori del bridge di poco prima del crash sopravvivono al riavvio nella RAM non inizializzata.
Al recupero, il dispositivo stampa un report di una riga RECOVERED from ... sulla console di debug con quei contatori (e l'indirizzo del fault in caso di hard fault), e la riga stats: in esecuzione riporta i totali di hangs, faults e faultpc, quindi un crash lascia una traccia diagnostica anche se si è ripulito da solo.
== Stati del LED di bordo
La tabella seguente elenca i pattern del LED di bordo e il loro significato.
[#tbl-led] .Pattern del LED di bordo [cols="1,3", options="header"] |=== |Pattern |Significato
|Fisso |Associato a un access point - il normale stato di funzionamento.
|Lampeggio lento - 1 Hz |Wi-Fi configurato, in associazione o non ancora associato.
|Lampeggio veloce - 5 Hz |Nessun Wi-Fi configurato; configuralo tramite la console di gestione.
|Doppio lampeggio - due impulsi rapidi, poi una pausa |Scansione continua (live) in corso; il dispositivo è disassociato e trasmette gli access point vicini alla console di gestione finché non viene premuto un tasto.
|Spento |USB non pronta. |===
== Lavori Futuri
Il bridge gira sulla USB Full-Speed nativa dell'RP2040 (12 Mbit/s), quindi il throughput raggiunge circa 4-5 Mbit/s di payload TCP - ampio per una dashboard o una superficie di controllo, ma un tetto duro. Il collo di bottiglia è il collegamento USB, non la radio Wi-Fi. Alcune direzioni che potrebbero alzarlo, in ordine approssimativo di impegno:
Nessuna di queste è necessaria per l'uso previsto del firmware; sono punti di partenza per chi vuole più throughput.
[#upstream-libraries-and-credits] == Librerie Upstream e Crediti
Questo firmware è assemblato da diversi progetti upstream, riportati nella tabella seguente.
[#tbl-upstream] .Componenti upstream [cols="1,2,1,4", options="header"] |=== |Componente (file in-tree) |Upstream |Licenza |Ruolo
|USBNet |https://github.com/mattmyne/usbnet[mattmyne/usbnet] |MIT a|Modulo USB-network di base, descrittori USB e scheletro principale, estesi qui nel bridge Wi-Fi.
usb_network.c, usb_network.h - riscritti come bridge L2usb_descriptors.c - modificato per includere CDC-NCM composito + doppio CDC-ACMtusb_config.h - modificato|TinyUSB |https://github.com/hathach/tinyusb[hathach/tinyusb] |MIT a|Stack di dispositivo USB CDC-NCM e CDC-ACM, usato come incluso nel pico-sdk. +
|pico-sdk 2.2.0 |https://github.com/raspberrypi/pico-sdk[raspberrypi/pico-sdk] |BSD-3-Clause a|Supporto della scheda, sistema di build e TinyUSB, lwIP e cyw43-driver inclusi.
pico_sdk_import.cmake - copia esattalwipopts.h - esempio pico_w ridotto|TinyUSB net_lwip_webserver example
|Peter Lawrence and Ha Thach, via https://github.com/hathach/tinyusb[hathach/tinyusb]
|MIT
a|Base originale della colla USB-network; ridotto al percorso CDC-NCM. +
|lrndis |https://github.com/fetisov/lrndis[fetisov/lrndis] |MIT a|Influenza progettuale sull'approccio USB-network +
Le restanti sorgenti sono originali di questo progetto:
main.cconfig.cconfig.hconfig_proto.cconfig_proto.hserial_console.cserial_console.hwifi_scan.cwifi_scan.hdebug_console.cdebug_console.h== Licenza
Questo progetto è concesso in licenza MIT; vedi link:LICENSE[LICENSE]. I componenti upstream mantengono le proprie licenze come indicato in <<upstream-libraries-and-credits,Librerie Upstream e Crediti>>.
[#architecture] == Architettura
=== Panoramica
Il dispositivo è una periferica USB CDC-NCM che collega un host al Wi-Fi.
Il Pico W esegue la stazione Wi-Fi e trasporta i frame Ethernet tra il collegamento USB e la radio.
L'host esegue il proprio stack IP e detiene l'unica identità di rete; il Pico non possiede un IP proprio.
L'host non necessita di nulla oltre ai driver cdc_ncm e cdc_acm inclusi.
=== Perché un Bridge Layer-2 tramite Adozione del MAC
L'obiettivo è che l'host appaia sulla rete Wi-Fi come un normale dispositivo con un solo indirizzo, mentre il Pico rimane invisibile. Un vincolo rigido del livello fisico determina come ciò viene ottenuto.
Una stazione Wi-Fi non può fare da bridge trasparente per più indirizzi MAC. Quando l'Infineon CYW43 si associa a un access point in modalità stazione, l'associazione concede esattamente un indirizzo MAC, e i frame di dati 802.11 che invia sono legati a quel MAC della stazione. Senza frame a quattro indirizzi (WDS), che anche l'access point deve supportare e consentire, la radio non può trasportare frame per conto di altri indirizzi MAC dietro di essa. Questa è la nota limitazione per cui un client Wi-Fi non può essere messo in bridge.Questo firmware non combatte quel vincolo; lo rimuove. All'interfaccia USB dell'host viene fatto adottare l'indirizzo MAC della stazione Wi-Fi, quindi c'è esattamente un solo MAC end-to-end. Con host e stazione che condividono un'unica identità, il Pico è un semplice bridge di livello 2: inoltra i frame Ethernet invariati tra USB e Wi-Fi, senza toccare nulla al di sopra del livello 2. L'access point vede un'unica stazione ordinaria; l'host esegue DHCP, SLAAC e Neighbor Discovery da sé e detiene gli indirizzi risultanti.
Le conseguenze sono elencate nella tabella qui.
[#tbl-bridge-effects] .Proprietà del bridge di adozione del MAC [cols="1,3", options="header"] |=== |Proprietà |Perché è valida
|Un solo IP, detenuto dall'host |Il Pico non assegna a sé stesso alcun indirizzo, quindi c'è un'unica identità, sulla sottorete dell'access point stesso - non una sottorete di tethering privata.
|IPv4 e IPv6 allo stesso modo |L'inoltro avviene al livello 2, quindi SLAAC, DHCPv6, router advertisement e Neighbor Discovery passano attraverso senza modifiche, senza alcun codice specifico per versione.
|Niente NAT né port forwarding |Nulla viene riscritto, quindi le connessioni in ingresso raggiungono direttamente l'host; non c'è nulla da mascherare o mappare.
|Nessuno stack Wi-Fi lato host
|Il Pico gestisce l'associazione e il supplicant, quindi l'host non ha bisogno di wpa_supplicant, database regolatorio o driver wireless - solo dei driver di classe CDC.
|===
=== Percorso dei dati
Sul lato USB, TinyUSB fornisce il dispositivo CDC-NCM e il MAC dell'interfaccia dell'host viene impostato sul MAC della stazione all'avvio (usb_network_set_host_mac, prima dell'enumerazione).
Host-to-Wi-Fi: un frame arriva tramite tud_network_recv_cb, viene messo in coda e, nel loop principale, viene trasmesso sulla radio con cyw43_send_ethernet.
Wi-Fi-to-host: il gestore input della netif della stazione viene sostituito, quindi ogni frame ricevuto dal driver cyw43 viene consegnato al bridge invece che a lwIP, accodato e trasmesso all'host con tud_network_xmit.
Sul lato USB non esiste alcuna interfaccia IP lwIP e la netif della stazione non trasporta alcun IP; lwIP serve solo lo stato di link della netif cyw43 e il pool di pbuf.
=== Modello di concorrenza
Il firmware usa pico_cyw43_arch_lwip_threadsafe_background.
Il Wi-Fi viene servito in un IRQ in background e in un contesto asincrono, così non affama mai la USB, la cui tud_task() gira nel loop principale.
Questa disposizione ha una conseguenza rigorosa per il bridge.
TinyUSB deve essere toccato solo dal loop principale, eppure i frame Wi-Fi vengono ricevuti nel contesto di background.
Il gestore di ricezione Wi-Fi quindi si limita ad accodare ogni frame in un ring buffer, e il loop principale svuota quel ring in tud_network_xmit.
La violazione di questa regola si è manifestata lato host come NETDEV WATCHDOG: transmit queue timed out, con la USB che si disconnetteva.
L'invio dei frame dell'host verso il Wi-Fi avviene nel loop principale e mantiene cyw43_arch_lwip_begin()/cyw43_arch_lwip_end() attorno alla chiamata a cyw43.
=== Multicast e IPv6
Una stazione, per impostazione predefinita, riceve solo i gruppi multicast a cui si è unita. Il bridge non esegue un proprio stack IP e non si unisce a nulla, quindi senza intervento la radio lascerebbe cadere il multicast da cui IPv6 dipende, e l'IPv6 dell'host non funzionerebbe attraverso il bridge. I router advertisement, il rilevamento degli indirizzi duplicati e la risoluzione degli indirizzi si basano tutti sul multicast.
A ogni associazione il firmware imposta l'iovar allmulti di CYW43, così la stazione consegna tutti i frame multicast indipendentemente dal filtro.
Ciò viene fatto con la cyw43_ioctl pubblica verso WLC_SET_VAR, non entrando in modalità monitor, quindi il formato dei frame Ethernet rimane invariato.
Una rete dietro uno switch con IGMP o MLD snooping può comunque potare parte del multicast che l'host non ha mai richiesto; un access point domestico piatto lo inonda.
=== Filtro di auto-riflessione
Poiché l'host condivide il MAC della stazione, un multicast o broadcast inviato dall'host viene inondato dall'access point di nuovo verso la stazione, che ora riceve tutto il multicast, e verrebbe riconsegnato all'host come se fosse un suo frame.
Il gestore di ricezione scarta qualsiasi frame Wi-Fi-to-host il cui MAC sorgente è il MAC della stazione, poiché quel frame può essere solo la trasmissione dell'host stesso riflessa dall'access point.
Un bridge non deve rimandare alla stazione i suoi stessi frame; lo scarto viene conteggiato come refl nelle statistiche di debug.
=== Le due console seriali
Gestione e diagnostica sono fuori banda, su due funzioni CDC-ACM dello stesso dispositivo USB composito, anziché sulla rete. Questa è una conseguenza deliberata della trasparenza: il lato rete trasporta solo il traffico dell'host e non ha un indirizzo su cui il Pico possa rispondere.
La console di gestione (/dev/ttyACM0) esegue il protocollo di configurazione a righe ed è raggiungibile nell'istante in cui la USB viene enumerata, prima che il Wi-Fi sia attivo, quindi il dispositivo è sempre configurabile senza alcun IP.
La console di debug (/dev/ttyACM1) è un flusso diagnostico di sola scrittura - eventi di associazione, contatori periodici del bridge e sommari opzionali dei pacchetti per frame - emesso solo quando abilitato, quindi non costa nulla quando è disattivato e non ingombra mai la console di gestione.
Nessuna delle due console tocca la rete, quindi nessuna genera traffico che un firewall dell'host registri.
=== Memorizzazione della configurazione
Le impostazioni di runtime - fino a otto profili di credenziali Wi-Fi, l'indice del profilo attivo, il paese regolatorio e il flag di debug - risiedono in un unico record nell'ultimo settore di flash, lontano dall'immagine del programma all'inizio della flash.
Il record si estende su più pagine di flash, quindi un save programma l'intero settore in una volta sola (la cancellazione è comunque a livello di settore).
All'avvio il record viene accettato solo in caso di corrispondenza esatta del suo numero magico e di un CRC-32 sul resto della struct; qualsiasi mancata corrispondenza carica invece i valori predefiniti del tempo di compilazione.
Non esiste una migrazione versionata: una modifica alla struttura del record fa semplicemente fallire il controllo magic/CRC e si ripiega sui valori predefiniti, il che è accettabile perché un valore predefinito incorporato in fase di compilazione inizializza comunque il primo profilo. (È per questo che l'aggiornamento alla v1.1.0, che ha ampliato il record a una lista di profili, scarta un record precedente alla 1.1.0.)
Un save riscrive il record tramite flash_safe_execute, che coordina cancellazione e programmazione con l'altro core e disabilita gli interrupt per i pochi millisecondi necessari.
Questa scrittura viene eseguita dal gestore della console mentre è mantenuto il lock di lwIP; la breve finestra a interrupt disabilitati sospende il servizio Wi-Fi in background, il che è accettabile per un salvataggio poco frequente e avviato dall'host.
=== Particolarità di hardware e toolchain
Questi comportamenti di RP2040, Infineon CYW43 e pico-sdk costano tempo di debug reale e sono facili da reintrodurre.
==== Il servizio in background richiede TinyUSB solo dal loop principale
Questa è la regola di concorrenza indicata in <<concurrency-model,Modello di concorrenza>>. Con il servizio in background, la ricezione Wi-Fi gira in un contesto che non può chiamare TinyUSB, ed è per questo che esiste il ring di trasmissione differita.
==== L'associazione non è un IP
L'helper cyw43 cyw43_tcpip_link_status riporta CYW43_LINK_UP solo quando la stazione possiede un indirizzo IP, e questa stazione deliberatamente non ne assume mai uno.
Lo stato di associazione viene quindi letto dal flag di link della netif (netif_is_link_up), che il bridge usa anche per decidere se inoltrare.
==== Un'identità cambiata richiede un nuovo product ID
Un dispositivo composito che cambia il proprio set di interfacce mantenendo lo stesso vendor e product ID USB può ricevere dall'host un descrittore in cache.
Il product ID deriva dalle classi abilitate, quindi l'aggiunta di ogni funzione CDC-ACM lo sposta (cafe:4020 a cafe:4022) e l'host rilegge il nuovo layout.
==== Le build Release rendono assert una no-op
Una CMAKE_BUILD_TYPE=Release definisce NDEBUG, che compila assert() in nulla, quindi un'allocazione protetta solo da un'asserzione procede oltre un errore e dereferenzia NULL.
Un crash del firmware prima che tud_task venga eseguito appare lato host come errore di enumerazione USB -110, un timeout di lettura del descrittore di dispositivo.
Sul percorso di avvio vengono usati veri controlli di NULL invece delle asserzioni.
=== Ambiente verificato
La configurazione confermata funzionante usa pico-sdk 2.2.0, la toolchain GCC arm-none-eabi e i TinyUSB e lwIP inclusi nel pico-sdk, senza modifiche.
La scheda di riferimento è la Pico W (RP2040).
È previsto che la Pico 2 W (RP2350) funzioni.
Una build produce un build/pico-usb-wifi.uf2 di circa 670 KB.