
드라이버 없는 USB Wi-Fi 어댑터, 투명한 레이어-2 브리징, WPA2/WPA3 인증, 대역 외 관리 콘솔을 생성하는 Raspberry Pi Pico W용 펌웨어.
= pico-usb-wifi :toc: macro :toclevels: 3 :idprefix: :idseparator: -
pico-usb-wifi는 Raspberry Pi Pico W를 드라이버 없는 USB Wi-Fi 어댑터로 변환하는 펌웨어로, USB CDC-NCM 장치로 인식됩니다.
:figure-caption: AI Slop
.pico-usb-wifi 다이어그램 image::images/openrouter-banana2-rpi-pico.png[]
이 펌웨어는 Pico W의 무선 인터페이스와 USB 인터페이스 간에 프레임을 전달하는 투명한 레이어-2 브리지 역할을 합니다. 호스트의 USB 인터페이스는 Pico W Wi-Fi 스테이션의 MAC 주소를 채택하여, 종단 간 단일 MAC 및 IP 식별자를 제공합니다.
호스트 측 드라이버, 커널 모듈 또는 무선 스택이 필요하지 않습니다. <<no-host-side-wi-fi-stack,호스트 측 Wi-Fi 스택 불필요>> 참조.
호스트는 모든 최신 Linux, macOS, Windows 및 모바일 OS에 기본 탑재된 cdc_ncm 및 cdc_acm 드라이버만 있으면 됩니다.
== 기능
pico-usb-wifi는 다음 기능을 제공합니다:
.실제 상황 image::images/slop_2.png[]
== 존재 이유
곧 진행할 임베디드 Linux 프로젝트에 사용할 USB Wi-Fi 어댑터가 필요했습니다. 저렴한 USB Wi-Fi 동글이 없었기에, 오프라인 매장에서 5 USD에 하나를 사러 가는 대신, 긴 주말 연휴 중 이틀과 약 백만 개의 Claude Code 토큰을 들여 이 펌웨어를 구축했습니다.
백일백, pico-usb-wifi 저자
Google은 불가능하다고 말했습니다:
.pico-usb-wifi "불가능" image::images/gemini_says_not_possible.png[]
toc::[]
== 호스트 측 Wi-Fi 스택 불필요
USB Wi-Fi 동글과 달리, 이 어댑터는 호스트에 이더넷과 유사한 인터페이스만 노출합니다. Pico W가 무선 측 전체(라디오, 연결, WPA2/WPA3 supplicant, 규제 도메인)를 처리합니다.
이를 통해 시스템은 wpa_supplicant, cfg80211/mac80211 무선 스택, 규제 데이터베이스, 칩셋 펌웨어 또는 벤더 드라이버를 설치할 필요가 없습니다.
Wi-Fi 자격 증명 제공은 호스트 측 무선 도구를 통하지 않고, 장치 자체의 대역 외 관리 콘솔을 통해 이루어집니다.
이렇게 하면 제약이 있거나 어플라이언스 호스트, 또는 무선 드라이버가 없거나 벤더 커널에 드라이버가 없는 호스트도 범용 CDC 클래스 드라이버만으로 무선 네트워크에 연결할 수 있습니다.
== 작동 방식
[#fig-topology] .토폴로지 다이어그램 image::images/topology.svg[투명 레이어-2 브리지 토폴로지,820]
호스트의 USB 인터페이스에는 Pico W의 Wi-Fi 스테이션 MAC 주소가 할당되므로, 종단 간 단일 MAC이 존재하며 Pico W는 USB와 Wi-Fi 간에 이더넷 프레임을 그대로 전달할 수 있습니다. Wi-Fi 스테이션은 여러 MAC 주소를 브리징할 수 없으므로, 호스트와 스테이션을 하나의 MAC으로 통합하는 것이 투명 브리지를 가능하게 하는 핵심입니다. 전체 근거, 데이터 경로 및 IPv6/멀티캐스트 처리는 <<architecture,아키텍처>>에 설명되어 있습니다.
== 호스트 요구 사항
호스트에는 내장된 cdc_ncm 및 cdc_acm 드라이버가 필요합니다.
두 드라이버 모두 10년 넘게 메인라인 Linux의 일부였으므로, 현재 지원되는 모든 커널에 포함되어 있습니다.
외부 모듈, 펌웨어 블롭 또는 벤더 드라이버가 필요하지 않습니다.
동일한 클래스 드라이버가 macOS, Windows 10 이상, Android 및 iOS에도 존재합니다.
[NOTE] 다른 운영 체제는 테스트되지 않았습니다.
== 빌드
이 프로젝트는 표준 pico-sdk CMake 프로젝트입니다. ARM 임베디드 툴체인, CMake, 빌드 백엔드(Ninja 또는 Make), Python 3, 그리고 서브모듈이 포함된 pico-sdk 체크아웃이 필요합니다. pico-sdk에 포함된 TinyUSB와 lwIP는 수정 없이 사용됩니다.
=== 의존성
Arch 기반 시스템(Arch, CachyOS, Manjaro)에서는 공식 저장소에서 툴체인을 제공합니다:
arm-none-eabi-newlib는 임베디드 C 라이브러리와 헤더를 제공합니다. 이것이 없으면 크로스 컴파일러가 stdint.h 및 유사 헤더를 찾을 수 없습니다.
libusb는 picotool에만 필요하며, pico-sdk는 첫 번째 구성 시 소스에서 이를 빌드하여 UF2를 생성합니다. 별도의 picotool 패키지는 필요하지 않습니다.
=== 빌드 단계
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 # 그런 다음 SSID/비밀번호를 편집하거나 비워 둠 cmake -S . -B build -G Ninja -DPICO_BOARD=pico_w -DCMAKE_BUILD_TYPE=Release cmake --build build
-G Ninja 플래그는 선택 사항입니다. 생략하면 기본 Make 생성기를 사용합니다(이 경우 cmake --build build -j).
wifi_config.h는 컴파일 타임 기본 자격 증명을 보유하며 gitignore 처리됩니다.
비워 두면 내장 자격 증명이 없는 이미지가 생성되어 관리 콘솔(<<management-console,관리 콘솔>>)을 통해 런타임에 프로비저닝됩니다. 내용을 채우면 기본 네트워크가 내장됩니다.
== 펌웨어 작성
다음 단계는 펌웨어를 보드에 로드하는 방법입니다.
. BOOTSEL 버튼을 누른 상태에서 보드를 USB에 연결합니다.
보드는 RPI-RP2 USB 대용량 저장소 볼륨으로 마운트되며, 일반적으로 /run/media/<user>/RPI-RP2 또는 /media/<user>/RPI-RP2에 위치합니다.
. pico-usb-wifi.uf2를 해당 볼륨에 복사합니다.
보드가 자동으로 펌웨어로 재부팅됩니다.
. Wi-Fi 연결을 받을 호스트에 보드를 연결합니다.
== Linux 호스트에서 사용
장치를 호스트에 연결하고 관리 콘솔(<<management-console,관리 콘솔>>)을 통해 Wi-Fi 자격 증명을 한 번 제공합니다. 그러면 호스트의 인터페이스는 액세스 포인트 네트워크에서 유선 연결처럼 작동합니다.
인터페이스를 자동으로 관리하는 호스트(NetworkManager, systemd-networkd, dhcpcd)는 설정이 필요 없습니다. DHCP 및 SLAAC를 브리지 위에서 실행하여 단일 IPv4 주소, IPv6 주소, 액세스 포인트의 게이트웨이 및 DNS를 유선 클라이언트와 동일하게 받습니다.
장치 측 주소나 게이트웨이를 구성할 필요가 없습니다. Pico는 어떤 주소도 보유하지 않기 때문입니다.
인터페이스의 MAC 주소는 Wi-Fi 스테이션의 MAC 주소로, 네트워크에 하나의 식별자로 제시됩니다.
여기 ip 명령 출력은 결과 인터페이스를 보여줍니다: 액세스 포인트 자체 서브넷의 일반 DHCP/SLAAC 클라이언트이며, 스테이션의 MAC을 가지며 Pico의 흔적은 없습니다.
== 관리 콘솔
관리 콘솔은 구성 프론트엔드로, 첫 번째 CDC-ACM 직렬 기능(일반적으로 /dev/ttyACM0)에 위치합니다.
장치가 인식되는 즉시, Wi-Fi 연결 전에도 접근 가능하므로 네트워크 없이도 프로비저닝이 가능합니다.
picocom 또는 screen과 같은 직렬 터미널로 열면 됩니다. USB CDC의 경우 전송 속도는 중요하지 않습니다.
콘솔은 입력을 에코하고 프롬프트를 표시하며, 모든 명령은 전체 장치 상태를 출력합니다.
Wi-Fi 인증은 WPA2-PSK 또는 WPA3-SAE(AES)입니다.
비밀번호는 네트워크의 패스프레이즈이며, 비워 두면 오픈 네트워크로 설정됩니다.
비밀번호로 보호된 프로파일은 WPA2/WPA3 전환 모드를 사용하므로, 두 종류의 액세스 포인트 모두에 연결할 수 있습니다.
콘솔은 최대 8개의 자격 증명 _프로파일_을 저장합니다. 하나는 활성 프로파일이며, 장치는 이 프로파일로 연결됩니다.
set ssid/set pass는 활성 프로파일을 편집하고, list/use/del은 세트를 관리하며, scan은 주변 네트워크를 검색하고 번호 목록에서 하나를 선택하여 연결합니다. 입력하기 어려운 문자가 있는 SSID에 유용합니다.
명령어는 대소문자를 구분하지 않으며, 콘솔은 소문자로 표시합니다.
아래 세션은 스캔을 통해 네트워크를 프로비저닝하는 예입니다.
$ picocom /dev/ttyACM0
변경 사항은 즉시 적용되어 활성 프로파일로 다시 연결됩니다. 재부팅이 필요하지 않습니다.
더 많은 네트워크를 저장하려면 반복하면 됩니다. list는 프로파일을 표시하고 use <n>은 활성 프로파일을 전환합니다:
save는 모든 프로파일을 플래시에 저장합니다. restore는 저장된 레코드를 다시 로드하여 저장되지 않은 편집 내용을 폐기합니다.
아래 표는 명령어 세트를 나열합니다.
[#tbl-config-commands] .관리 콘솔 명령어 [cols="2,3", options="header"] |=== |명령어 |효과
|set ssid <text>
|활성 프로파일의 SSID를 설정 (값은 공백 포함 가능)하고 다시 연결합니다. 프로파일이 없으면 첫 번째 프로파일을 생성합니다.
|set pass <text>
|활성 프로파일의 WPA2/WPA3 패스프레이즈를 설정 (오픈 네트워크는 비워 둠)하고 다시 연결합니다.
|set country <CC\|WORLDWIDE>
|규제 국가를 설정합니다 (다음 부팅 시 완전히 적용됨).
|set debug <on\|off>
|디버그 콘솔에 진단 정보를 스트리밍합니다. <<debug-console,디버그 콘솔>> 참조.
|list
|저장된 프로파일을 나열하고 활성 프로파일을 표시합니다.
|use <n>
|프로파일 _n_을 활성화하고 다시 연결합니다.
|del <n>
|프로파일 _n_을 삭제합니다.
|scan
|주변 네트워크를 검색하고 스캔 하위 메뉴로 진입합니다 (back, join <n>, scan 반복, 또는 live로 지속적인 비연결 스트림). join은 선택한 네트워크를 활성 프로파일로 준비하며, set pass를 위해 대기합니다.
|save
|모든 프로파일과 설정을 플래시에 저장합니다.
|restore
|저장된 설정을 다시 로드하여 저장되지 않은 변경 사항을 폐기합니다.
|===
호스트에 할당된 주소는 host IPv4 및 host IPv6로 상태 덤프에 표시되며, 브리지된 트래픽에서 수동적으로 스누핑됩니다. Pico는 보고할 주소를 보유하지 않기 때문입니다.
설정 섹터는 플래시의 끝에 위치하며, 시작 부분의 프로그램 이미지와 분리되어 있으므로 일반적인 pico-usb-wifi.uf2 재플래시는 저장된 프로파일을 그대로 유지합니다 (전체 칩 지우기는 프로파일을 지웁니다).
예외는 v1.1.0으로 _업그레이드_하는 경우입니다: 레코드 레이아웃이 여러 프로파일을 저장하도록 변경되었으므로, 1.1.0 이전 레코드는 폐기되며 네트워크를 한 번 다시 입력해야 합니다 (변경 로그 참조).
== 디버그 콘솔
디버그 콘솔은 두 번째 CDC-ACM 직렬 기능(일반적으로 /dev/ttyACM1)에 있는 쓰기 전용 진단 스트림입니다.
관리 콘솔에서 set debug on을 실행하기 전까지는 무음이므로, 꺼져 있을 때 비용이 없으며 관리에 방해되지 않습니다.
활성화되면 연결 변경 사항과 주기적인 브리지 통계 라인을 보고합니다. 아래 세션과 같습니다.
-DTRACE_FRAMES=1 빌드는 브리지된 각 프레임에 대한 한 줄 요약을 추가하지만, 부하가 걸리면 콘솔이 넘치므로 기본적으로 꺼져 있습니다.
통계 필드는 아래 표에 설명되어 있습니다.
[#tbl-debug-stats] .디버그 통계 필드 [cols="1,3", options="header"] |=== |필드 |의미
|->wifi
|호스트에서 Wi-Fi로 전달된 프레임 수.
|->host
|Wi-Fi에서 호스트로 전달된 프레임 수.
|txdrop
|스테이션이 아직 연결되지 않아 호스트에서 Wi-Fi로 드롭된 프레임 (호스트 재시도).
|rxdrop
|USB 측이 충분히 빨리 소모하지 못해 Wi-Fi에서 호스트로 드롭된 프레임.
|refl
|액세스 포인트에 의해 반사된 호스트 자신의 전송으로 인해 Wi-Fi에서 호스트로 드롭된 프레임.
|poolfail
|lwIP pbuf 풀이 일시적으로 고갈되어 호스트에서 Wi-Fi로 드롭된 프레임.
|ringpk
|이전 통계 라인 이후 Wi-Fi에서 호스트로의 USB-TX 대기열 최대 깊이 (32 중)를 재설정한 값. 값이 32에 가까우면 USB가 Wi-Fi 전달 속도를 따라가지 못함을 의미합니다. (전체 최대값과 달리, 버스트가 지나면 다시 떨어집니다.)
|link
|스테이션 Wi-Fi 링크 상태: up (연결됨), join/down (연결 중), 또는 실패 이유: badauth (잘못된 패스프레이즈), nonet (SSID를 찾을 수 없음), fail.
|hangs
|마지막 콜드 전원 켜기 이후 워치독이 펌웨어를 행(hang)에서 복구한 횟수. <<automatic-recovery,자동 복구>> 참조.
|faults
|마지막 콜드 전원 켜기 이후 펌웨어가 복구한 하드 폴트 횟수.
|faultpc
|가장 최근 하드 폴트의 주소 (없으면 0x00000000). addr2line으로 매핑할 때 사용.
|freeram
|사용 가능한 RAM 크기(바이트). 버퍼 크기 조정 시 여유 공간을 평가하는 데 사용.
|===
프레임별 추적은 브리지된 트래픽과 USB Full-Speed 링크를 공유하므로 처리량을 줄이고 콘솔을 넘치게 합니다. 따라서 빌드 타임 옵션(-DTRACE_FRAMES=1)이며, 심층 디버깅용으로만 사용됩니다.
== 자동 복구
하드웨어 워치독은 펌웨어가 메인 루프를 서비스하지 못할 경우(잠금 또는 드라이버 데드락) 장치를 재부팅하므로, 플러그를 뽑을 필요 없이 몇 초 내에 자체적으로 다시 열거됩니다. 별도의 하드 폴트 핸들러는 CPU 폴트를 즉시 포착하고 폴트 주소를 기록합니다.
충돌 직전의 브리지 카운터는 초기화되지 않은 RAM에 남아 재부팅 후에도 유지됩니다.
복구 시 장치는 디버그 콘솔에 해당 카운터(하드 폴트의 경우 폴트 주소 포함)와 함께 한 줄의 RECOVERED from ... 보고서를 출력하며, 실행 중인 stats: 라인에는 hangs, faults, faultpc 카운트가 포함되어 충돌이 자체적으로 해제되었더라도 진단 추적을 남깁니다.
== 온보드 LED 상태
아래 표는 온보드 LED 패턴과 그 의미를 보여줍니다.
[#tbl-led] .온보드 LED 패턴 [cols="1,3", options="header"] |=== |패턴 |의미
|켜짐 (Solid) |액세스 포인트에 연결됨 - 정상 작동 상태.
|느린 깜빡임 - 1 Hz |Wi-Fi가 구성되었으며, 연결 중이거나 아직 연결되지 않음.
|빠른 깜빡임 - 5 Hz |Wi-Fi가 구성되지 않음. 관리 콘솔을 통해 프로비저닝하십시오.
|이중 플래시 - 두 번 빠르게 깜빡인 후 정지 |실시간(연속) 스캔 실행 중. 장치는 연결이 해제되었으며, 키를 누를 때까지 액세스 포인트를 관리 콘솔로 스트리밍합니다.
|꺼짐 (Off) |USB가 준비되지 않음. |===
== 향후 작업
브리지는 RP2040의 기본 Full-Speed USB(12 Mbit/s)를 통해 실행되므로, 처리량은 TCP 페이로드 기준 약 4-5 Mbit/s로 제한됩니다. 대시보드 또는 제어 표면에는 충분하지만, 한계입니다. 병목 현상은 Wi-Fi 라디오가 아닌 USB 링크입니다. 이를 개선할 수 있는 몇 가지 방향 (대략적인 노력 순):
이 중 어느 것도 펌웨어의 의도된 사용에 필수적이지 않습니다. 더 높은 처리량을 원하는 사람들을 위한 시작점일 뿐입니다.
== 업스트림 라이브러리 및 크레딧
이 펌웨어는 여러 업스트림 프로젝트로 구성되어 있으며, 아래 표에 기록되어 있습니다.
[#tbl-upstream] .업스트림 구성 요소 [cols="1,2,1,4", options="header"] |=== |구성 요소 (트리 내 파일) |업스트림 |라이선스 |역할
|USBNet |https://github.com/mattmyne/usbnet[mattmyne/usbnet] |MIT a|베이스 USB-네트워크 모듈, USB 디스크립터, 메인 스켈레톤. 여기서 Wi-Fi 브리지로 확장됨.
usb_network.c, usb_network.h - L2 브리지로 재작성됨usb_descriptors.c - 복합 CDC-NCM + 이중 CDC-ACM을 포함하도록 수정됨tusb_config.h - 수정됨|TinyUSB |https://github.com/hathach/tinyusb[hathach/tinyusb] |MIT a|USB CDC-NCM 및 CDC-ACM 장치 스택. pico-sdk에 번들된 대로 사용됨. +
|pico-sdk 2.2.0 |https://github.com/raspberrypi/pico-sdk[raspberrypi/pico-sdk] |BSD-3-Clause a|보드 지원, 빌드 시스템, 번들된 TinyUSB, lwIP, cyw43-driver.
pico_sdk_import.cmake - 정확한 복사본lwipopts.h - pico_w 예제에서 축소됨|TinyUSB net_lwip_webserver 예제
|Peter Lawrence 및 Ha Thach, https://github.com/hathach/tinyusb[hathach/tinyusb] 경유
|MIT
a|USB-네트워크 글루의 원래 기반. CDC-NCM 경로로 축소됨. +
|lrndis |https://github.com/fetisov/lrndis[fetisov/lrndis] |MIT a|USB-네트워크 접근 방식에 디자인 영향 +
나머지 소스는 이 프로젝트에 고유합니다:
main.cconfig.cconfig.hconfig_proto.cconfig_proto.hserial_console.cserial_console.hwifi_scan.cwifi_scan.hdebug_console.cdebug_console.h== 라이선스
이 프로젝트는 MIT 라이선스입니다. link:LICENSE[LICENSE] 참조. 업스트림 구성 요소는 <<upstream-libraries-and-credits,업스트림 라이브러리 및 크레딧>>에 기록된 대로 자체 라이선스를 유지합니다.
== 아키텍처
=== 개요
이 장치는 호스트를 Wi-Fi에 브리징하는 USB CDC-NCM 주변 장치입니다.
Pico W가 Wi-Fi 스테이션을 실행하고 이더넷 프레임을 USB 링크와 라디오 간에 전송합니다.
호스트는 자체 IP 스택을 실행하고 단일 네트워크 식별자를 보유합니다. Pico는 자체 IP를 보유하지 않습니다.
호스트는 기본 제공되는 cdc_ncm 및 cdc_acm 드라이버 외에는 아무것도 필요하지 않습니다.
=== MAC 채택을 통한 레이어-2 브리지 이유
목표는 호스트가 하나의 주소를 가진 일반 장치로 Wi-Fi 네트워크에 나타나고, Pico는 보이지 않게 하는 것입니다. 이를 달성하는 방법을 결정하는 하나의 엄격한 물리 계층 제약이 있습니다.
Wi-Fi 스테이션은 여러 MAC 주소를 투명하게 브리징할 수 없습니다. Infineon CYW43이 스테이션 모드로 액세스 포인트에 연결되면, 연결은 정확히 하나의 MAC 주소를 부여하며, 전송하는 802.11 데이터 프레임은 해당 스테이션 MAC에 바인딩됩니다. 액세스 포인트도 지원하고 허용해야 하는 4-주소(WDS) 프레임이 없으면, 라디오는 그 뒤에 있는 다른 MAC 주소를 대신하여 프레임을 전송할 수 없습니다. 이것이 Wi-Fi 클라이언트를 브리징할 수 없는 잘 알려진 제한 사항입니다.이 펌웨어는 그 제약과 싸우지 않습니다. 제거합니다. 호스트의 USB 인터페이스는 Wi-Fi 스테이션의 MAC 주소를 채택하도록 지시받으므로, 종단 간에 정확히 하나의 MAC만 존재합니다. 호스트와 스테이션이 동일한 ID를 공유함에 따라 Pico는 멍청한 레이어 2 브리지 역할을 합니다. 즉, USB와 Wi-Fi 사이에서 이더넷 프레임을 레이어 2 위의 어떤 것도 건드리지 않고 그대로 전달합니다. 액세스 포인트는 하나의 일반 스테이션만 보게 되고, 호스트는 DHCP, SLAAC, Neighbor Discovery를 직접 실행하여 결과 주소를 보유합니다.
결과는 아래 표에 나열되어 있습니다.
[#tbl-bridge-effects] .MAC 채택 브리지의 속성 [cols="1,3", options="header"] |=== |속성 |이유
|하나의 IP, 호스트가 보유 |Pico가 스스로 주소를 할당하지 않으므로, 액세스 포인트 자체 서브넷에 단일 ID가 존재합니다. (사설 테더링 서브넷이 아님)
|IPv4 및 IPv6 모두 지원 |전달이 레이어 2에서 이루어지므로, SLAAC, DHCPv6, 라우터 알림, Neighbor Discovery가 버전별 코드 없이 그대로 통과합니다.
|NAT 및 포트 포워딩 없음 |아무것도 다시 쓰이지 않으므로, 인바운드 연결은 호스트에 직접 도달합니다. 가장하거나 매핑할 대상이 없습니다.
|호스트 측 Wi-Fi 스택 불필요
|Pico가 연결 및 supplicant를 소유하므로, 호스트는 wpa_supplicant, 규제 데이터베이스, 무선 드라이버가 필요하지 않으며, CDC 클래스 드라이버만 있으면 됩니다.
|===
=== 데이터 경로
USB 측에서 TinyUSB는 CDC-NCM 장치를 제공하고, 호스트의 인터페이스 MAC은 시작 시 (usb_network_set_host_mac, 열거 전) 스테이션 MAC으로 설정됩니다.
호스트에서 Wi-Fi로: 프레임이 tud_network_recv_cb를 통해 도착하여 준비되고, 메인 루프에서 cyw43_send_ethernet을 사용하여 무선으로 전송됩니다.
Wi-Fi에서 호스트로: 스테이션 netif의 input 핸들러가 대체되어, cyw43 드라이버가 수신한 모든 프레임이 lwIP 대신 브리지에 전달되고, 큐에 저장된 후 tud_network_xmit으로 호스트에 전송됩니다.
USB 측에는 lwIP IP 인터페이스가 존재하지 않으며, 스테이션 netif는 IP를 전혀 가지지 않습니다. lwIP는 cyw43 netif의 링크 상태와 pbuf 풀만 제공합니다.
=== 동시성 모델
펌웨어는 pico_cyw43_arch_lwip_threadsafe_background를 사용합니다.
Wi-Fi는 백그라운드 IRQ 및 비동기 컨텍스트에서 서비스되므로 USB를 굶주리게 하지 않으며, tud_task()는 메인 루프에서 실행됩니다.
이 구조는 브리지에 대해 한 가지 엄격한 결과를 초래합니다.
TinyUSB는 메인 루프에서만 접근해야 하는 반면, Wi-Fi 프레임은 백그라운드 컨텍스트에서 수신됩니다.
따라서 Wi-Fi 수신 핸들러는 각 프레임을 링 버퍼에만 넣고, 메인 루프가 그 링을 비워 tud_network_xmit으로 전송합니다.
이 규칙을 위반하면 호스트 측에서 NETDEV WATCHDOG: transmit queue timed out이 나타나며 USB가 끊깁니다.
호스트 프레임을 Wi-Fi로 보내는 작업은 메인 루프에서 이루어지며, cyw43 호출 주위에 cyw43_arch_lwip_begin()/cyw43_arch_lwip_end()를 유지합니다.
=== 멀티캐스트 및 IPv6
기본적으로 스테이션은 가입한 멀티캐스트 그룹만 수신합니다. 브리지는 자체 IP 스택을 실행하지 않으므로 아무것도 가입하지 않으며, 따라서 개입하지 않으면 무선은 IPv6가 의존하는 멀티캐스트를 드롭하게 되고, 호스트의 IPv6는 브리지를 통해 작동하지 않습니다. 라우터 알림, 중복 주소 감지, 주소 해결은 모두 멀티캐스트를 통해 이루어집니다.
각 연결 시 펌웨어는 CYW43 allmulti iovar를 설정하므로, 스테이션은 필터와 관계없이 모든 멀티캐스트 프레임을 전달합니다.
이는 모니터 모드로 전환하는 것이 아니라 WLC_SET_VAR에 대한 공개 cyw43_ioctl을 통해 수행되므로, 이더넷 프레임 형식은 변경되지 않습니다.
IGMP 또는 MLD 스누핑 스위치 뒤에 있는 네트워크는 호스트가 요청하지 않은 일부 멀티캐스트를 여전히 프루닝할 수 있습니다. 평면 홈 액세스 포인트는 이를 플러딩합니다.
=== 자기 반사 필터
호스트가 스테이션 MAC을 공유하기 때문에, 호스트가 보낸 멀티캐스트 또는 브로드캐스트는 액세스 포인트에 의해 스테이션으로 다시 플러딩되며, 이제 스테이션은 모든 멀티캐스트를 수신하므로, 호스트의 자체 프레임으로 호스트에 다시 전달됩니다.
수신 핸들러는 소스 MAC이 스테이션 MAC인 Wi-Fi-호스트 프레임을 드롭합니다. 해당 프레임은 액세스 포인트에 의해 반사된 호스트의 자체 전송일 수밖에 없기 때문입니다.
브리지는 스테이션의 프레임을 다시 에코해서는 안 됩니다. 드롭은 디버그 통계에서 refl로 계산됩니다.
=== 두 개의 직렬 콘솔
관리 및 진단은 대역 외, 동일한 합성 USB 장치의 두 CDC-ACM 기능을 통해 네트워크가 아닌 방식으로 수행됩니다. 이는 투명성의 의도적인 결과입니다. 네트워크 측은 호스트의 트래픽만 전달하며, Pico가 응답할 주소가 없습니다.
관리 콘솔(/dev/ttyACM0)은 구성 라인 프로토콜을 실행하며, Wi-Fi가 켜지기 전 USB가 열거되는 즉시 접근 가능하므로, IP 없이도 항상 장치를 프로비저닝할 수 있습니다.
디버그 콘솔(/dev/ttyACM1)은 쓰기 전용 진단 스트림입니다. 연결 이벤트, 주기적인 브리지 카운터, 선택적 프레임별 패킷 요약이 활성화된 경우에만 방출되므로, 꺼져 있을 때는 비용이 들지 않으며 관리 콘솔을 어지럽히지 않습니다.
두 콘솔 모두 네트워크를 건드리지 않으므로 호스트 방화벽이 로깅할 트래픽을 생성하지 않습니다.
=== 구성 저장소
런타임 설정(최대 8개의 Wi-Fi 자격 증명 프로필, 활성 프로필 인덱스, 규제 국가, 디버그 플래그)은 플래시의 마지막 섹터에 있는 단일 레코드에 저장되며, 플래시 시작 부분의 프로그램 이미지와 멀리 떨어져 있습니다.
레코드는 여러 플래시 페이지에 걸쳐 있으므로, save는 전체 섹터를 한 번에 프로그래밍합니다(소거는 섹터 전체에 대해 수행됨).
부팅 시 레코드는 매직 넘버와 구조체 나머지에 대한 CRC-32가 정확히 일치하는 경우에만 허용되며, 일치하지 않으면 컴파일 타임 기본값이 로드됩니다.
버전별 마이그레이션은 없습니다. 레코드 레이아웃이 변경되면 매직/CRC 검사가 실패하여 기본값으로 폴백됩니다. 이는 컴파일 타임 기본값이 여전히 첫 번째 프로필을 시드하기 때문에 허용됩니다. (이것이 v1.1.0으로 업그레이드할 때 레코드가 프로필 목록으로 확장되어 1.1.0 이전 레코드가 폐기되는 이유입니다.)
save는 flash_safe_execute를 통해 레코드를 다시 씁니다. 이는 다른 코어와 소거 및 프로그래밍을 조정하고, 몇 밀리초 동안 인터럽트를 비활성화합니다.
이 쓰기는 lwIP 잠금이 유지되는 동안 콘솔 핸들러에서 실행됩니다. 짧은 인터럽트 비활성화 윈도우는 백그라운드 Wi-Fi 서비스를 일시 중지하지만, 드물고 호스트가 시작하는 저장 작업에는 허용됩니다.
=== 하드웨어 및 툴체인 특이점
RP2040, Infineon CYW43, pico-sdk의 이러한 동작은 실제 디버깅 시간을 소모하며 쉽게 다시 도입될 수 있습니다.
==== 백그라운드 서비스는 메인 루프 전용 TinyUSB가 필요함
<<concurrency-model,동시성 모델>>에 명시된 동시성 규칙입니다. 백그라운드 서비스의 경우 Wi-Fi 수신은 TinyUSB를 호출할 수 없는 컨텍스트에서 실행되며, 이것이 지연 전송 링이 존재하는 이유입니다.
==== 연결은 IP가 아님
cyw43 도우미 cyw43_tcpip_link_status는 스테이션이 IP 주소를 보유한 경우에만 CYW43_LINK_UP을 보고하며, 이 스테이션은 의도적으로 IP를 전혀 할당받지 않습니다.
따라서 연결 상태는 netif 링크 플래그(netif_is_link_up)에서 읽으며, 브리지도 이를 사용하여 전달 여부를 결정합니다.
==== 변경된 ID는 새 제품 ID가 필요함
동일한 USB 공급업체 및 제품 ID를 유지하면서 인터페이스 세트를 변경하는 합성 장치는 호스트가 캐시된 디스크립터를 제공할 수 있습니다.
제품 ID는 활성화된 클래스에서 파생되므로, 각 CDC-ACM 기능을 추가하면 이동합니다(cafe:4020에서 cafe:4022로). 호스트는 새 레이아웃을 다시 읽습니다.
==== 릴리스 빌드는 assert를 무효화함
CMAKE_BUILD_TYPE=Release는 NDEBUG를 정의하여 assert()를 아무것도 하지 않도록 컴파일하므로, 어설션으로만 보호된 할당은 실패를 무시하고 NULL을 역참조합니다.
tud_task가 실행되기 전에 펌웨어가 충돌하면 호스트 측에서 USB 열거 오류 -110, 즉 장치 디스크립터 읽기 시간 초과로 나타납니다.
시작 경로에서는 어설션 대신 실제 NULL 가드를 사용합니다.
=== 확인된 환경
작동이 확인된 구성은 pico-sdk 2.2.0, arm-none-eabi GCC 툴체인, 그리고 pico-sdk에 번들된 수정되지 않은 TinyUSB 및 lwIP를 사용합니다.
참조 보드는 Pico W(RP2040)입니다.
Pico 2 W(RP2350)도 작동할 것으로 예상됩니다.
빌드 시 약 670KB의 build/pico-usb-wifi.uf2가 생성됩니다.