Wireless・のおと

WoWのはなし

ブログ
規格 技術解説 省電力 Linux

IEEE 802.11 規格がとうとうアルファベットを2周して3周目に入りました。記念すべき(?)3周目の IEEE 802.11ba タスクグループは「Wake-up Radio」だそうです。ということで、今回は Wake on Wireless LAN (WoW) のおはなしです。

はじめに WoL ありき

無線起床の WoW 以前に、有線 LAN (イーサネット) による Wake on LAN (WoL) がありました。出てきたのは 1996 年頃、ようやく「ふつうのパソコン」にもイーサネットが標準装備されるようになり、ネットワークが「特別なもの」から「あって当たり前、つながっているのが当たり前」になりつつあった頃です。
この時期とくに官公庁や学校などで、一部屋数十台規模で導入されたパソコンの電源管理を一斉にやりたいという要望が出てきました。どうせ LAN でつながっているのなら、LANを用いて管理するのが自然な流れです。しかし「一斉シャットダウン」は何とかなっても、「一斉起床」にはちょっと工夫が必要でした。パケットを受信・解析・処理する CPU が寝ている(サスペンド)のに、パケットを用いて起床処理を行わせるというのは「ニワトリタマゴ」の問題になるからです。

そこで導入されたのが「マジックパケット(Magic Packet)」という特殊なフォーマットでした。マジックパケットは「パケットデータ内のどこかに 6 バイトの 0xff と、その直後に対象機器の 6 バイトの MAC アドレスが 16 回反復されて格納されている」というパケットです。「データのどこかにあれば良い」というのも妙な話ですが、これによって任意のヘッダタイプを付加することができ、生のイーサネットフレーム(慣例的には Type=0x0842 が付けられる)の他にも AppleTalk, IPX, NetBEUI, UDP などのヘッダを付けることができます。まだ「マルチプロトコル・イントラネット」なんて言葉が語られていた 90 年代っぽい仕様ですね。

WoL ではサスペンド中にもネットワークカードだけは通電させておき、ネットワークコントローラ(MAC)は CPU とは無関係にパケットを受信してマジックパターンの検索を続けています。マジックパケット一致が判定されれば CPU (マザーボード)に対して起床信号を発生します。PCI 2.2 以前にはバス上に起床信号がなく、別途 3 ピンの専用コネクタを接続する必要がありました(※註)。

※註:マザーボードにも対応する受け口が必要で、それが備わっていない場合 WoL 機能は使用できませんでした。

WoL から WoW へ

Wake on Wireless LAN (WoW) が実装されるようになるのは、Wi-Fi 無線 LAN が登場してからだいぶ後の 2010 年頃のことになります。無線 LAN は CPU による処理負担が大きく、CPU が寝てしまうと「CPU とは無関係にパケットを受信」することすら難しかったのが理由の一つです。特に Wi-Fi 省電力モードを使うと TIM 受信・PS-POLL 応答といった一連のシーケンスをこなさないとパケット配送すらされないため、有線 LAN のマジックパケット解析のように「ちょっと凝ったロジックシーケンサー」だけでは対応できません。自身が小型の CPU を内蔵し、ある程度の処理をこなせる「(セミ)インテリジェント型」あるいは「オフロード型」の Wi-Fi チップが出てきて、ようやく WoW の機能が実装できるようになりました。
WoW の実装が厄介なのはそれだけではなく、セキュリティやローミングとの関連もあります。WPA 以降のセキュリティではグループ鍵が定期的に更新されますが、これを受信したときは CPU を起床させサプリカントでグループ鍵更新処理を済ませないと、以降のブロード/マルチキャスト受信ができなくなります。また無線 LAN ではサスペンド中に移動して別の AP にローミングする可能性がありますが、ローミング時には再接続・再認証処理が必要であり、このときもまた CPU を起床させる必要があります。
このように、一見同じような機能でありながら有線 WoL と無線の WoW ではだいぶ実装要件が異なります。有線 WoL では「流れてくるデータ列から特定のビットパターンを検出し一致判定する」回路があれば良いのに対し、無線の WoW は「CPU が寝ている間もアップリンクを維持し続け、パケットを受信し続ける」ことに相当なパワーが必要になるからです。

WoW の実装

Linux の無線 LAN サポートは当初 Wireless Extension (WEXT) という IOCTL の拡張仕様として実装されていました。WEXT の歴史は古く 1997 年にまで遡り、もともとは IEEE 802.11 ですらなく WaveLAN という無線ネットワークのサポートが目的でした。当然 WEXT 仕様に WoW 機能は含まれておらず、初期の WoW 対応チップはベンダー独自拡張のプロトコルや設定ユーティリティを使って制御する実装になっていました。
無線チップに WoW モードを設定するのも独自なら、Linux カーネル側で WoW 信号を受けて起床する仕組みも独自に作る必要がありました。無線チップから出ている WoW 信号を GPIO 入力に接続して割り込み設定し、カーネルをサスペンドモードに入れて(echo standby > /sys/power/state、もっと古いカーネルだと echo 3 > /proc/acpi/sleep)起床を待つのですが、割り込みの設定時に IRQF_NO_SUSPEND というフラグを付加しておかないとサスペンド時にマスクされてしまいます。しかし Linux の汎用 GPIO ドライバ(/sys/class/gpio インタフェース)にはこの機能が無いため、2.6 時代のカーネルでは WoW 信号を受けるため「だけ」のドライバをわざわざ書く必要がありました。

static int gpio_wlan = 57;
:
ret = gpio_direction_input(gpio_wlan);
irq_number_wlan = gpio_to_irq(gpio_wlan);
ret = request_irq(irq_number_wlan, 
    handler_wlan,
    (IRQF_NO_SUSPEND|IRQF_TRIGGER_RISING),
    "wowlan", 0);

request_irq による wakeup GPIO 定義の例(部分)

最近の Linux カーネルでは DTS (Device Tree Structure) が採用され、DTS 側に "wakeup-source" を書いておくと IRQF_NO_SUSPEND と同じ効果が得られるようになりました。

wowlan {
  laben = "Wake on WLAN";
  gpios = <&gpio1 57 GPIO_ACTIVE_HIGH>
  wakeup-source;
}

DTS による wakeup GPIO 定義の例(部分)

DTS 本来のデザイン目標としては無線デバイスと WoL の GPIO 割り込みを別々に管理するのではなく /sys/class/net/wlan0/power/wakeup のような形に統合したいのでしょうけれども、2017 年 7 月現在ではそこまで統合は進んでいないようです。

(※註6) なお、「All-oneアドレスはサブネット内ブロードキャストとして扱う」は多くの実装で踏襲されていますが、All-zeroアドレスの扱いは実装によって異なります。Linuxの場合、192.168.0.0/24のようなアドレスをip address addで付けることもできますし、そこに対してpingなどで通信することも可能です。ただしこれは「Linuxの実装がそうなっている」というだけの話で、All-zeroアドレスがインターネットを通じて通信できることは保証されません。

WoW の設定

WEXT の時代にはメーカー毎・チップファミリー毎にばらばらな専用ツールを使う必要がありましたが、NL80211/CFG80211 では iw というツールが無線 LAN 設定の入り口として標準化されています(※註)。

※註:なお有線 LAN の WoL 設定には ethtool を使います。

iw による WoW の設定は iw phy <phy name> wowlan <show/enable/disable> <modes...> というコマンドで行います。例えば

iw phy phy0 wowlan enable disconnect

とすれば PHY0 の切断検出をもって起床通知がかかる設定になります。iw がサポートする起床条件(modes) は iw のバージョンによって異なりますが

any : 全ての(ユニキャスト)パケット受信
disconnect : 切断通知
magic-packet : Magic Packet 受信
gtk-rekey-failure : グループ鍵更新
eap-identity-request : EAP 要求受信 (ローミング対応)
4way-handshake : WPA 認証要求受信 (ローミング対応)
rfkill-release :  無線禁止(RFKILL) スイッチの ON
patterns <pattern...> : 任意の受信パターン設定

などがあり、並べて指定すると OR 条件で適用されることになっています。show コマンドを使うと、現在設定されている WoW 条件が羅列で表示されます。

iw phy phy0 wowlan show
* wake up on disconnect
* wake up on magic packet

「ことになっています」というのも妙な書き方ですが、例によっていろいろあるのです。まずはドライバによって指定可能な条件が異なります。指定不可能なモードが混ざると

command failed: Invalid argument (-22)

というエラーになりますが、いったいどの条件でハネられたのかはわかりません。またドライバによっては指定可能な組み合わせに上限があり、A,B,C の条件が単独に設定できても A+B+C ではダメなのがあったりします。更には「指定できるけど実は効かない条件」というバグ持ちのドライバ/チップもあります。
patterns も厄介で、受信パケットを IEEE 802.3 形式とみなして(※註) 00:80:92:12:34:56:-:-:-:-:-:-:08:42 のように指定できる(- は任意の 8bit に一致)のですが、指定できるパターンの最大長はドライバによって・チップによって異なります。例によって例のごとく、長すぎる値を指定すると突然コアを吐いて死ぬバグ持ちドライバもあったりします。

※註:IEEE 802.11 上の MAC ヘッダ形式は接続モードによって2アドレス・3アドレス・4アドレスがあり、DST・SRC・Type/Length が連続する IEEE 802.3 形式とは相当異なります。しかし iw ではリンク層でのフォーマットに関わらず、一括して 802.3 同等とみなした形式でパターンを設定する API になっているということです。

こういったドライバ毎・チップ毎の WoW の制約はどこにも明文化されていません。そればかりか iw phy wowlan の解説も貧弱で、man iw ではそもそもまともな解説が出て来ないですし、そこから引用されている http://wireless.kernel.org/en/users/Documentation/iw にも wowlan オプションについての解説はありません(少なくとも 2017 年 7 月現在は)。

Linux のパワー制御

Linux のパワー制御はバージョンによって変遷がありますが、Kernel 2.6.23 以降では /sys/power/state という仮想ファイルがパワー制御の API になっています(ただし Kernel Config で CONFIG_SYSPEND=y が設定されている必要があります)。これは読み書き可能な属性を持ち、読みだすと「freeze mem standby」のようなよくわからない文字列が表示されますが、これは「指定可能なパワーモードの一覧」です。パワーモードは一応

freeze (ACPI S0) ... 全リソース起床状態のまま電力節減
standby (ACPI S1) ... 通電状態のまま動作停止
mem (ACPI S3) ... メモリ情報を保持したまま動作停止
disk (ACPI S4) ... メモリ情報をディスクに保存して動作停止

と定義されていますが、このうち何が実装されるのか、あるいは mem/standby/freeze の違いをどう解釈するかは実装によって差異があります。例によって例のごとく、モードはあるのだけど指定しても何も起きないとか、指定すると暴走するというバグ持ちの実装系があったり、あるいはサスペンド・起床するとカーネルは戻ってくるけれど UART の受信ができなくなるとか、USB デバイスを再認識しないとか、何故かシステムクロックが速くなって凄い勢いで日付が進んでゆくとか実に様々な不具合が発生し得ます。


/sys/power/state はシステムの起床・通電状態を制御する API ですが、動作状態のまま CPU クロックだけ可変して消費電流を制御したい場合もあります。その場合の API は /sys/devices/system/cpu/cpu0/cpufreq になります。この仮想ディレクトリには結構沢山の仮想ファイルが入っていますが、「CPU クロック」に直接関係するのは scaling_setspeed です。基本的には、ここに周波数を設定すると CPU の動作速度が変わります。例えば

echo 30000000  > /sys/devices/system/cpu/cpu0/cpufreq/scaling_setspeed

とすれば 30MHz になる、という具合です。ただしこれはあくまで「原理的には」という話で、実際にはそう簡単に CPU クロックを可変させてはくれません。Linux のクロックドライバは「ポリシー」「プロファイル」「ガバナー」という階層構造をなしており、ユーザーによるクロック指定を可能にするガバナーが実装され、そのガバナーが設定可能なポリシー・プロファイルが実装され選択された状態ではじめて、上記のような操作が可能になります(※註)。これは真面目に解説するとそれだけでブログ1回分くらいの分量になるので、興味のあるかたは Linux カーネルドキュメントの cpu-freq/ 下のテキストを読んでみてください。

※註:そして例によって例のごとく指定しても効かないだとか、指定した途端に暴走するバグ持ちの実装系もあります)。

まとめ

まとめると、Wake on Wireless という機能は

(1) CPU 休眠状態でもパケット受信し続けるため、Wi-Fi チップ側に相応の処理能力が必要(WoL より複雑)。

(2) Group Key 更新やローミング・再認証などのイベントに呼応して CPU を起床させて然るべき処理を行う必要がある(Magic Packet だけが起床条件ではない)。

(3) Linux では WoW のインタェースに iw wowlan コマンドが実装されているが、そもそもチップ・ドライバが WoW 対応か否か、対応だとしてどれだけの機能が実際に使えるのかは個別に確かめる必要がある。

(4) Linux カーネル側の休眠・起床のインタフェースは /sys/power/state だが、これが使えるかどうかもまたバージョン・BSP 毎に確かめる必要がある。

といった状態になっております。

私は IEEE 802.11ba の中身には踏み込んでいないので、そこで彼等が何をどう標準化しようとしているのかはわかりません。おそらく gtk-rekey-failure とか eap-identity-request とか iw の起床モードに関わる部分じゃないかと思いますが、WoW はプロトコル上の決めごとだけでどうにかなるものではなく、 Wi-Fi チップから CPU への割り込み配線やら CPU・カーネルのパワー制御 API まで含めて実装しなければ動きません。そして今回「例によって例のごとく」を何度も使ったように、既存の実装系は色々問題を抱えているものが多いです。
Wake on Wireless が「普通に付いていて当たり前に使える」機能になるまでは、もう少しかかりそうですね。

関連リンク

製品のご購入・サービスカスタマイズ・資料請求など
お気軽にお問い合わせください