SoftBank Air ターミナル6はUSB NICになる。Windows/LinuxとUSB直結して1.4Gbpsを確認
TL;DR
SoftBank Air ターミナル6(SBA6D)は、内部のUSBコントローラをDEVICE側へ切り替え、Linux USB GadgetのCDC-NCMを構成すると、Windows/Linux PCとUSB直結できるUSBネットワークアダプタとして動作します。
実機では、
- T6A側:
ncm0 - Windows側:
UsbNcm Host Device - Windows標準ドライバ:
UsbNcm.sys - VID/PID:
2C7C:7006
として認識しました。
今回のWindows直結構成は、
T6A 192.168.77.1/24
Windows 192.168.77.2/24
です。
iperf3では、SuperSpeed接続時に、
T6A → Windows:約1.37~1.39 Gbit/s
Windows → T6A:約1.00~1.01 Gbit/s
を安定して確認しました。
重要なのは、T6Aでは単にConfigFSへ ncm.gs8 を追加するだけでは不十分だったことです。
実機で再現できた成功条件は、
USB Host未接続
↓
mode=3
↓
xHCI ABSENT
↓
GPIO322 LOW
↓
USB-A VBUS 0V
↓
f5 -> ncm.gs8
↓
UDC unbind / rebind
↓
Windows/Linux Host接続
↓
SuperSpeed
です。
さらに今回、GPIO322 がHIGHのままだと同じケーブル・同じWindows USBポートでもUSB 2.0 High-Speedへフォールバックし、実測約333~350Mbpsになりました。
その状態から、
echo 0 > /sys/class/gpio/gpio322/value
でGPIO322をLOWへ戻し、UDCをrebindすると、
current_speed = super-speed
へ復帰し、再び約1.4Gbpsが出ました。
つまりGPIO322は、単なる安全上のVBUS制御だけでなく、この実機でSuperSpeedリンクを成立させるうえでも重要な条件でした。
最初に重要:USB A-Aなら何でもよいわけではない
今回の接続にはUSB 3.xのA-Aケーブルを使用しています。
ここはかなり重要です。
USB-Aオス同士のケーブルには、外観が同じでも配線や用途が異なるものがあります。
特にPCと機器のUSB-Aポート同士を接続する場合、双方の5V VBUSを不用意に直結する構成は避ける必要があります。
今回のT6Aでは、Host接続前に、
GPIO322 = LOW
USB-A VBUS = 0V
を実機で確認してから接続しています。
したがってこの記事の「USB直結」は、
USB-A端子同士を適当なA-Aケーブルでつなげばよい
という意味ではありません。
USB roleとVBUS状態を確認したうえで接続しています。
T6AをUSB NICにするとどう見えるのか
構成はシンプルです。
Windows / Linux PC
│
│ USB 3.x
│ CDC-NCM
▼
SoftBank Air ターミナル6
│
▼
ncm0
普通のUSB NICでは、USB接続した小型アダプタがEthernet NICとして見えます。
今回は、そのUSB NIC役をSoftBank Air ターミナル6自身にやらせます。
Windowsから見ると、
UsbNcm Host Device
という普通のネットワークアダプタとして認識されます。
T6A内部にはUSB Gadget構成がある
SoftBank Air ターミナル6の内部OSはLinuxです。
今回の実機は、
Linux 5.4.238
aarch64
でした。
ConfigFSは、
/config
へmountされています。
USB Gadget本体は、
/config/usb_gadget/g1
です。
functionsを見ると、
functions/acm.gs0
functions/acm.gs1
functions/acm.gs2
functions/ffs.adb
functions/ncm.gs8
などが存在します。
今回使うのは、
functions/ncm.gs8
です。
これがCDC-NCMのネットワークfunctionです。
まずUSBコントローラをDEVICE側へ切り替える
ここが重要です。
T6AにはMediaTek MTU3系のdual-role USBコントローラがあり、Host側とDevice側を切り替えられます。
今回実際にUSB NCMが成功した状態では、
/sys/devices/platform/11201000.usb/mode
が、
3
でした。
実際に使用した操作は、
printf 3 > /sys/devices/platform/11201000.usb/mode
です。
このT6A実機では、
mode=3
がUSB Deviceとして成功した状態です。
一般論からmode番号を推測しない
ここでは一度かなりハマりました。
cold boot後のT6Aは、
mode=2
になっていました。
そこでConfigFSを復元し、
f5 -> ncm.gs8
UDC bind
ncm0生成
まで進めることはできました。
しかし、
UDC state = not attached
current_speed = UNKNOWN
ncm0 = NO-CARRIER
のままで、WindowsからUSB Deviceとして列挙されませんでした。
一次記録を再確認すると、最初に成功した実績では、
printf 3 > /sys/devices/platform/11201000.usb/mode
を使用していました。
そこで mode=3 へ戻すと、
xHCI = ABSENT
UDC configured
ncm0 LOWER_UP
へ復帰しました。
このため、少なくともこのT6Aでは、
一般的なLinuxのmode番号を推測せず、実機で成功した値を使う
ことが重要です。
xHCIが消えたことを確認する
DEVICE側へ切り替えたら、Host controller側のxHCIが消えたことを確認します。
成功状態では、
xHCI: ABSENT
でした。
つまり、
T6AがUSB Hostとして動く
状態ではなく、
T6A自身がUSB Deviceになる
状態になっています。
GPIO322をLOWへする
今回、ここが非常に重要だと分かりました。
T6AではGPIO322がUSB-A側のVBUS制御に関係しており、実機では、
GPIO322 HIGH
→ USB-A VBUS 約5V
GPIO322 LOW
→ USB-A VBUS 0V
となりました。
GPIO状態は、
cat /sys/kernel/debug/gpio | grep -C 2 'gpio-322'
で確認できます。
成功状態は、
gpio-322 ... out lo
です。
GPIO322をLOWへする操作は、
echo 0 > /sys/class/gpio/gpio322/value
です。
確認は、
cat /sys/class/gpio/gpio322/value
で、
0
となればLOWです。
Hostを接続する前に、可能であれば実際にUSB-A側のVBUSが0Vになっていることも確認します。
GPIO322 HIGHだとUSB2へ落ちた
今回、GPIO322の重要性を偶然かなりきれいに確認できました。
同じT6A、同じA-Aケーブル、同じWindows USBポートを使用しているにもかかわらず、
GPIO322 = HIGH
の状態では、
UDC state = configured
current_speed = high-speed
ncm0 = LOWER_UP
となりました。
ここでいう high-speed はUSB 2.0の480Mbpsモードです。
iperf3の実測も、
T6A → Windows:約350 Mbit/s
Windows → T6A:約333 Mbit/s
でした。
USB NCM自体は正常に通信していますが、SuperSpeedではありません。
そこで、
echo 0 > /sys/class/gpio/gpio322/value
としてGPIO322をLOWへ戻し、
G=/config/usb_gadget/g1
echo > "$G/UDC"
sleep 1
echo 11201000.usb > "$G/UDC"
とUDCをrebindしました。
すると直後に、
current_speed = super-speed
へ復帰しました。
同じケーブル、同じWindowsポートのままです。
したがって少なくとも今回の実機では、
GPIO322 HIGH
↓
USB 2.0 High-Speed
↓
約350Mbps
GPIO322 LOW
↓
UDC rebind
↓
USB 3.x SuperSpeed
↓
約1.4Gbps
という非常に分かりやすい差が再現しました。
GPIO322 LOWは、単にVBUSを切るためだけではなく、SuperSpeed成立にも重要な条件と考えています。
ConfigFSへNCMを組み込む
NCM functionは、
/config/usb_gadget/g1/functions/ncm.gs8
にあります。
USB configurationは、
/config/usb_gadget/g1/configs/b.1
です。
元の構成には、
f1 -> acm.gs2
f2 -> ffs.adb
f3 -> acm.gs0
f4 -> acm.gs1
が存在していました。
ここへ、
f5 -> ncm.gs8
を追加します。
実機上では、
f5 -> ../../../../usb_gadget/g1/functions/ncm.gs8
のように表示されます。
概念的には、
functions/ncm.gs8
│
│ f5
▼
configs/b.1
│
▼
USB Gadget configuration
です。
symlink確認には readlink -f を使う
ConfigFSではsymlink表示が相対pathになるため、単純な文字列比較では判定を誤ることがあります。
例えば、
../../../../usb_gadget/g1/functions/ncm.gs8
と、
/config/usb_gadget/g1/functions/ncm.gs8
は文字列としては違います。
実体確認には、
readlink -f
を使います。
test \
"$(readlink -f "$G/configs/b.1/f5")" = \
"$(readlink -f "$G/functions/ncm.gs8")"
とすれば、実体path同士で比較できます。
UDCをunbind / rebindする
NCMをconfigurationへ組み込んだら、UDCを再bindします。
今回のUDCは、
11201000.usb
です。
G=/config/usb_gadget/g1
echo > "$G/UDC"
echo 11201000.usb > "$G/UDC"
とします。
Host未接続の時点では、
UDC state = not attached
ncm0 = NO-CARRIER
carrier = 0
で問題ありません。
これはUSB Gadget側の準備が完了し、Host接続を待っている状態です。
ここで初めてWindows/Linuxを接続する
今回再現できた順序をまとめると、
USB Host未接続
↓
mode=3
↓
xHCI ABSENT
↓
GPIO322 LOW
↓
VBUS 0V確認
↓
f5 -> ncm.gs8
↓
UDC unbind / rebind
↓
not attached / NO-CARRIER
↓
Windows/Linux Host接続
↓
SuperSpeed
です。
Host接続後は、
UDC state = configured
current_speed = super-speed
ncm0 = LOWER_UP
carrier = 1
となりました。
Windowsからは標準USB NICとして認識
Windows Pavilionへ接続すると、
UsbNcm Host Device
として認識しました。
ドライバはWindows標準の、
UsbNcm.sys
です。
識別情報は、
VID 2C7C
PID 7006
MI 07
でした。
Windows側では、
イーサネット 3
説明:
UsbNcm Host Device
IPv4:
192.168.77.2
サブネット:
255.255.255.0
として使用しています。
T6A側は、
192.168.77.1/24
です。
したがって、
Windows
192.168.77.2
│
│ USB NCM
│
T6A
192.168.77.1
という普通のIPネットワークになります。
SuperSpeedになっているか必ず確認する
Windowsから認識されたからといって、USB 3.xで接続できているとは限りません。
T6A側では、
cat /sys/class/udc/11201000.usb/state
cat /sys/class/udc/11201000.usb/current_speed
cat /sys/kernel/debug/usb/11201000.usb/link-state
を確認します。
今回の正常状態は、
configured
super-speed
opstate: 0x0, ltssm: 0x11
でした。
current_speed が、
high-speed
ならUSB 2.0です。
この状態でもNCMは普通に動いてしまうため、速度を見るまではSuperSpeed成功とは判定しない方がよいです。
実測速度
SuperSpeedへ復帰したあと、iperf3を複数回測定しました。
T6AからWindows方向は、
1.39 Gbit/s
1.37 Gbit/s
1.38 Gbit/s
でした。
すべてTCP retransmissionは0です。
別の単発測定でも、
1.38 Gbit/s
を確認しました。
したがって現時点のbaselineは、
T6A → Windows:約1.37~1.39 Gbit/s
です。
逆方向は、
1.00 Gbit/s
1.01 Gbit/s
1.00 Gbit/s
でした。
したがって、
Windows → T6A:約1.00~1.01 Gbit/s
です。
USB2 High-Speedへフォールバックしていたときの、
333~350 Mbit/s
と比べると差は明確です。
1GbEを明確に超えた
T6A → Windows方向では、約1.4Gbpsが安定しています。
これは1GbEの実効速度を明確に超えています。
一方、Windows → T6A方向は約1Gbps付近で頭打ちです。
複数TCP streamなども試しましたが、大幅な改善はありませんでした。
現在は、
- CDC-NCM NTB処理
- aggregation / deaggregation
- USB request completion
- MTU3 QMU / DMA
- skb allocation / copy
- request queue depth
- offload制限
などを候補として調査しています。
さらに、この約1.4Gbpsの壁を越えるため、T6A向けUSB NCMカーネルドライバの改造も進めています。
管理経路はUSBとは完全に分離する
今回かなり重要だった設計です。
T6Aの管理にはUSBを使いません。
raspi2
│
│ LAN / SSH
▼
T6A
です。
USBはあくまで、
Windows
│
│ USB NCM
▼
T6A
というデータプレーンです。
全体では、
┌── USB NCM ── Windows
│
T6A
│
└── LAN/SSH ── raspi2
です。
T6AへのSSHは例えば、
sudo -u codex -H ssh \
-i /home/codex/.ssh/terminal6_ed25519 \
root@192.168.3.2
で行っています。
この構成のおかげで、USB Gadget構成が壊れてもLAN側から復旧できます。
ConfigFS上には ffs.adb も存在しますが、今回のT6A管理にはADBを使用していません。
管理はLAN/SSH、USBはNCMデータプレーン。
この2つを分離しています。
実験中にncm0を消した
性能改善のためUSB Gadget構成やNCM実装を触っている途中で、
f5 消失
ncm0 消失
UDC not attached
Windows NCM不通
という状態になりました。
ただし、
/config/usb_gadget/g1/functions/ncm.gs8
自体は残っていました。
つまり、
NCM functionは存在するが、USB configurationへ接続されていない
状態でした。
管理LANは生きていたため、SSHから復旧できました。
NCM functionがあることとncm0があることは別
cold boot後には、
/config/usb_gadget/g1/functions/ncm.gs8
が存在していても、
ncm0
はありませんでした。
ncm.gs8を作っただけでは足りません。
functions/ncm.gs8
↓
configs/b.1/f5
↓
UDC bind
↓
ncm0
という流れで実際のconfigurationへ参加させる必要があります。
さらに今回分かったのは、
ncm0がある
だけでも足りないことです。
mode=2 の状態では ncm0 を作れても、Hostから列挙されませんでした。
つまり確認すべき段階は、
ncm.gs8存在
↓
f5存在
↓
ncm0存在
↓
carrier
↓
UDC configured
↓
current_speed=super-speed
まであります。
cold bootすれば全部戻る、ではなかった
途中ではMTU3/DRD側の状態までおかしくなり、
UDC = not attached
current_speed = UNKNOWN
ncm0 = NO-CARRIER
から戻らない状態も経験しました。
cold bootするとUSB controller自体は正常化しました。
しかしその直後は、
mode=2
ncm.gs8 は存在
f5 はない
ncm0 はない
という状態でした。
つまり、
cold bootでUSBコントローラは初期化されても、USB NCMとして使える状態まで自動復旧するわけではない
ということです。
ConfigFSはRAM上なので、手動追加した f5 も消えます。
正しい復旧順序
今回確立した復旧順序は、
USB Hostを外す
↓
mode=3
↓
xHCI ABSENT
↓
GPIO322 LOW
↓
VBUS 0V確認
↓
ncm.gs8確認
↓
f5 -> ncm.gs8
↓
UDC unbind / rebind
↓
ncm0生成
↓
192.168.77.1/24
↓
ncm0 UP
↓
Windows/Linux Host接続
↓
configured
↓
current_speed=super-speed
↓
iperf3 baseline確認
です。
今回この順序で再現できました。
復旧後の確認
最低限、次を確認します。
G=/config/usb_gadget/g1
echo "mode=$(cat /sys/devices/platform/11201000.usb/mode)"
cat /sys/kernel/debug/gpio | grep -C 2 'gpio-322'
echo "UDC=$(cat "$G/UDC")"
echo "state=$(cat /sys/class/udc/11201000.usb/state)"
echo "speed=$(cat /sys/class/udc/11201000.usb/current_speed)"
readlink -f "$G/configs/b.1/f5"
ip -br addr show ncm0
cat /sys/class/net/ncm0/carrier
正常状態は概ね、
mode=3
gpio-322 ... out lo
UDC=11201000.usb
state=configured
speed=super-speed
f5 -> ncm.gs8
ncm0 LOWER_UP
carrier=1
です。
MAC addressは変わる場合がある
cold boot後には、
dev_addr
host_addr
が再生成される場合がありました。
Windowsから見えるMAC addressも変化します。
そのためWindowsが別のネットワークインターフェースとして扱い、
192.168.77.2/24
という静的IPが外れる可能性があります。
T6A側が正常なのにpingが通らない場合は、Windows側のIP設定も確認します。
今回やらないことにした操作
復旧・再現中は次を禁止しました。
- ADBを管理に使わない
- 未検証のUSB role変更を使わない
- flashを書き換えない
- 復旧途中に別のcandidate driverを投入しない
- 管理LANを切らない
- 過去にT6Aを不安定化させたruntime role切替moduleを再実行しない
特に大事だったのは、
一般的なLinuxの知識からT6A固有のmodeやroleを推測せず、実機で成功した一次記録を優先する
ことでした。
まとめ
SoftBank Air ターミナル6は、USBコントローラをDEVICE側へ切り替え、ConfigFSでCDC-NCMを組み込むことで、
Windows / Linux
│
│ USB 3.x NCM
▼
T6A
│
▼
ncm0
というUSB NICとして利用できます。
今回の実機で再現できた重要条件は、
mode=3
xHCI ABSENT
GPIO322 LOW
VBUS 0V
f5 -> ncm.gs8
UDC rebind
Hostは最後に接続
current_speed=super-speed
です。
Windowsでは標準の UsbNcm.sys で認識し、実測では、
T6A → Windows:約1.37~1.39 Gbit/s
Windows → T6A:約1.00~1.01 Gbit/s
を確認しました。
さらに今回、
GPIO322 HIGH
→ USB 2.0 High-Speed
→ 約333~350Mbps
GPIO322 LOW
→ UDC rebind
→ USB 3.x SuperSpeed
→ 約1.4Gbps
という差まで再現できました。
最初は単に、
「SoftBank Air ターミナル6はUSB NICになる」
という発見でした。
しかし実験を進めるうちに、
「どうすれば再現できるのか」
「なぜUSB2へ落ちるのか」
「壊れたらどう戻すのか」
まで少しずつ分かってきました。
次は、この約1.4Gbpsの壁をT6A側のUSB NCM実装を改造して越えられるか試しています。