Archive for 2009/07

アプリケーションからリブート(または電源 OFF)~その2(2/2)

前回の続きです。WinCE 6.0 のサスペンド動作と、電源 OFF 動作について書きます。

■サスペンド動作
前回、SetSystemPowerState() の第二引数に POWER_STATE_SUSPEND を渡せば、WinCE がサスペンド状態になると書きました。つまり、WinCE をホールト(halt)するには、SetSystemPowerState() を使えばよい、ということです。デバイスの電源を切ってよい状態にすることが目的であれば、それで問題ないでしょう。

SetSystemPowerState() に POWER_STATE_SUSPEND を渡すと、POWER_STATE_RESET を渡した場合と同様、Power Management 機能によって、レジストリやファイルシステムのフラッシュ動作が発火します。その後、Power Management 機能によって、ボードや CPU 固有のサスペンド処理が実行され、電源を切ってよい状態に遷移します。なお、「電源を切ってよい状態」と書きましたが、その状態は、Linux などの UNIX 系 OS で、shutdown コマンドを -h オプション付きで実行した場合に遷移する状態とは、異なります。

上で「サスペンド状態」と書いたように、SetSystemPowerState() に POWER_STATE_SUSPEND を渡すと、いわゆるシャットダウン動作を行うのではなく、サスペンド動作、WinXP で言えば「スタンバイ」動作を行います。つまり、ここで言うサスペンド動作は、Suspend To RAM のことです。Suspend To RAM については、たとえば
 http://e-words.jp/w/STR.html
が参考になるでしょう。Linux で言えば、shutdown コマンドではなく、uswsusp パッケージの s2ram コマンドを使ってサスペンドした状態に相当するのではないかと思います:
 http://suspend.sourceforge.net/
 http://suspend.sourceforge.net/intro.shtml
 http://sourceforge.net/projects/suspend/

さて、サスペンド動作というからには、対応するレジューム動作、つまり、サスペンド状態から通常動作状態に復帰できることを期待したくなります。もちろん、WinCE でも、サスペンド動作とレジューム動作をサポートしています。サスペンド動作による、通常状態からサスペンド状態への遷移、および、レジューム動作による、サスペンド状態から通常状態への遷移(復帰)については、WinCE のリファレンスの次のページで説明されています:

 Suspend State
 http://msdn.microsoft.com/en-us/library/aa916003.aspx

 Resume State
 http://msdn.microsoft.com/en-us/library/aa914868.aspx

上の “Suspend State” のページを見ると、サスペンド動作の発火は、SetSystemPowerState() ではなく、GwesPowerOffSystem() の呼び出しが基点となっています。OS デザインに GWES を組み込んでいる場合、サスペンドするには、SetSystemPowerState() ではなく、GwesPowerOffSystem() を呼び出す方が、より適切です。単に、デバイスの電源を切ってよい状態にするためにサスペンドするのであれば、SetSystemPowerState() を呼び出すだけでも構わないと思いますが、後でレジュームする前提でサスペンドするのなら、GwesPowerOffSystem() を呼び出す必要があります。

GwesPowerOffSystem() を呼び出す代わりに、keybd_event() の第一引数を VK_OFF にして呼び出すのでも構いません。keybd_event() に VK_OFF を渡した場合は、WinCE のスタートメニューから [サスペンド] を選択した場合と、ほぼ等価な動きになります。
GWES と Power Management を組み込んだ OS デザインの場合には(※標準シェルを組み込んでいる場合が、その典型例です)、keybd_event() に VK_OFF を渡してサスペンド動作を発火するのが、GwesPowerOffSystem() を呼び出すよりも簡単かも知れません。

GwesPowerOffSystem() に話を戻します。上の “Suspend State” のページには、GwesPowerOffSystem() を呼び出すと、システムがレジュームするまで呼び出しが完了しないという説明があります。GwesPowerOffSystem() のリファレンスにも、同じ説明が書かれています:
 http://msdn.microsoft.com/en-us/library/aa912858.aspx

レジュームするまで呼び出しから戻らないという振る舞いは、実際には、SetSystemPowerState() が行う PowerOffSystem() の呼び出しにより、カーネルが呼び出す OEMPowerOff() によって達成されます。GwesPowerOffSystem() は、GWES 自身のサスペンド処理を行った後で、SetSystemPowerState() を呼び出します。つまり、keybd_event() や GwesPowerOffSystem()、または SetSystemPowerState() のどれを使ってサスペンド動作を発火しても、サスペンド動作の最終段階で OEMPowerOff() が呼び出されます。

ところで、”Suspend State” のページには、SetSystemPowerState() の呼び出しによって Power manager が行うサスペンド動作において、PowerOffSystem() を呼び出した後、カーネルがサスペンド動作の最終段階の処理を行えるようにするために Sleep(0); を実行する、と書かれています。この説明は、WinCE 6.0 に対しては正しくないように思われます。Power Management モジュール(pm.dll)は、デバイスマネージャにリンクされてロードされますが、WinCE 5.0 までとは異なり、デバイスマネージャは、カーネル(nk.exe)にロードされ、カーネルの一部として動作します。従って、Power manager による PowerOffSystem() の呼び出しは、通常の関数呼び出しと同様にカーネル本体に引き継がれます。つまり、PowerOffSystem() を呼び出したスレッドによって、カーネル本体側で実装されているサスペンド処理が実行されるのです。

Power manager が、PowerOffSystem() を呼び出した後、Sleep(0); を実行することによって、カーネルにサスペンド動作の最終段階処理を行わせる(※スケジューラにスケジューリング処理させ、その結果として、カーネルのサスペンド動作を起動する)という説明は、WinCE 5.0 までのマイクロカーネル構造に基づく説明のように思われます。WinCE 6.0 では、デバイスマネージャはカーネルにロードされる DLL ですが(device.dll)、WinCE 5.0 までは、DLL ではなく EXE であり(device.exe)、独立したプロセスとして動作する仕組みでした。その場合、Power manager から PowerOffSystem() を呼び出すと、デバイスマネージャのプロセスからカーネルのプロセスに対する呼び出しになりますので、カーネル本体側のサスペンド処理は、PowerOffSystem() を呼び出したスレッドとは別のスレッドによって実行される筈です。このため、Power manager が PowerOffSystem() の呼び出しを行った後、カーネル本体のサスペンド処理が実行されるのを待たずに、その先の処理へ進んでしまうことがないように、Sleep() を呼び出すのではないかと思います。

ここで、「その先の処理」というのは、PowerOffSystem() の呼び出しから戻った後の処理、つまり、Power manager によるレジューム動作を指します。Power manager によるサスペンドとレジューム処理は、
 <WINCE600>/PUBLIC/COMMON/OAK/DRIVERS/PM/PDD/DEFAULT/platform.cpp
にある PlatformSetSystemPowerState() で実装されています(431行目付近)。

PowerOffSystem() に話しを戻します。PowerOffSystem() に対応するカーネル本体側のサスペンド処理は、
 <WINCE600>/PRIVATE/WINCEOS/COREOS/NK/KERNEL/kwin32.c
にある NKPowerOffSystem() で実装されています。このソースを見れば、”Suspend State” のページに書かれている、カーネルが行うサスペンド動作の最終段階処理が実際に行われているのが分かるでしょう。NKPowerOffSystem() では、まず、自身のスレッド優先度を最高値(0)に変更して、次に GWES と FileSys の Power off 動作を行った後、内部関数の CallOEMPowerOff() を介して OEMPowerOff() を呼び出します。OEMPowerOff() の呼び出しから戻ると、上の “Resume State” のページに書かれた動作を行います。

OEMPowerOff() は、OAL の関数であり、ボードや CPU ごとに異なるサスペンド処理の実装を提供します:
 http://msdn.microsoft.com/en-us/library/aa916963.aspx

OEMPowerOff() の役割は、レジュームに必要なデータ(レジスタの内容など)を RAM に退避した後、CPU をホールト(halt)状態にして、外部からの割り込みが発生するまで CPU を休眠させることです。休眠を解除するトリガとなる割り込みは、CPU をホールト状態にする前に設定します。割り込みが発生して CPU が起床すると、RAM に退避していたデータを使って、ホールト前の状態を復元し、OEMPowerOff() の呼び出し元に制御を戻す(return する)のです。

デバイスエミュレータの場合、OEMPowerOff() のソースは、
 <WINCE600>/PLATFORM/DEVICEEMULATOR/SRC/OAL/POWER/power.c
です。このソースを見ると、OEMPowerOff() の中で CPUPowerOff() という関数を呼び出しており、CPUPowerOff() が、CPU をホールト状態にする処理を行っていることが分かります。CPUPowerOff() は、
 <WINCE600>/PLATFORM/DEVICEEMULATOR/SRC/OAL/OALLIB/cpupoweroff.s
で実装されています。興味がある人は、cpupoweroff.s にある CPUPowerOff() のソースを見てみて下さい。レジュームに必要なデータを RAM へ退避した後、サスペンド状態を解除するトリガとなる割り込みに対する設定を行い、CPU に対するクロック供給を停止してホールトさせる様子が分かるでしょう。なお、CPU をホールトさせる処理自体は、cpupoweroff.s ではなく、同じディレクトリにある startup.s で実装されています。startup.s で実装されている StartUp() には、通常の呼び出しの場合に、冒頭のジャンプ命令(’b ResetHandler’)によってスキップされる箇所があるのですが、そこ(通常の呼び出しではスキップされる箇所)で、CPU をホールトさせています。

デバイスエミュレータの OEMPowerOff() の実装では、CPU をホールトさせた後(S3C2410 を SLEEP モードに設定した後)、外部割り込みによって CPU が通常モードに復帰すると、startup.s にある StartUp() のコードの、’ResetHandler’ というラベル以降を実行するようになっています。これは、ホールト状態から通常モードに復帰した後に行わなければならない、CPU 内部の再初期化処理が、パワーオンリセット時の初期化処理と共通する部分が多いため、コードを共有するための措置ではないかと思われます。StartUp() の ‘ResetHandler’ 以降のコードでは、パワーオンリセットによる呼び出しでないかどうかを要所ごとに判定しながら初期化処理を進め、レジューム用のデータが正しく退避されていると最終的に判断できたら、cpupoweroff.s の ‘Awake_address’ ラベルへジャンプするようになっています。’Awake_address’ ラベル以降のコードでは、RAM に退避していた CPU のレジスタ内容を復元した後、CPUPowerOff() の戻り先(呼び出し元)アドレスへジャンプします。これによって、CPUPowerOff() の呼び出しが完了し、OEMPowerOff() の実行が再開します。

OEMPowerOff() は、CPUPowerOff() の呼び出しから戻った後、レジューム動作を開始します。同様に、OEMPowerOff() を呼び出した NKPowerOffSystem() はカーネル本体のレジューム動作を実行し、さらに、その呼び出し元、つまり、PlatformSetSystemPowerState() や、SetSystemPowerState() ならびに GwesPowerOffSystem() において、それぞれのレジューム動作が実行され、サスペンド前の状態に復帰する、というわけです。

なお、デバイスエミュレータをサスペンドすると、デバイスエミュレータのウィンドウが閉じてしまい、以降は操作できなくなってしまいます。その状態では、デバイスエミュレータのプロセスは終了せずに残っているため、タスクマネージャを使って kill しない限り、[ターゲット] -> [デバイスの接続] メニューで Platform Builder からデバイスエミュレータを起動し直すことすら、できないのです。デバイスエミュレータをサスペンドした後、電源ボタンやキーボードによる割り込みの発生をエミュレートできれば面白いのですが、その方法は、ないようです。とりあえずの対策として、power.c を編集し、OEMPowerOff() からの CPUPowerOff() の呼び出しをコメントアウトして使ったという例が、Bruce Eitman という方の Blog エントリで紹介されています:
 http://geekswithblogs.net/BruceEitman/archive/2008/08/11/platform-builder-getting-the-emulator-to-resume.aspx
 http://geekswithblogs.net/BruceEitman/archive/2008/05/13/profile-of-bruce-eitman.aspx

サスペンドの開始からレジュームの完了に至るまでの一連の動作を、デバイスエミュレータとカーネルデバッガを使って確認するには、上の Blog エントリで紹介されているように、OEMPowerOff() からの CPUPowerOff() の呼び出しをコメントアウトするのが簡単です。
僕も、この記事を書く際に、その方法を使ってサスペンドとレジュームの動作をカーネルデバッガで追跡しました。

サスペンド動作について、もう一つだけ書いておきます。この項の冒頭で、SetSystemPowerState() によるサスペンド動作では、レジストリやファイルシステムのフラッシュ動作を発火させ、その後で、ボードや CPU 固有のサスペンド処理を実行すると書きました。レジストリやファイルシステムのフラッシュ動作を発火させるのは、メモリ上のキャッシュのみが更新されたまま電源が切られてしまい、永続記憶域上のデータに不整合が発生することを防ぐためでした。しかし、フラッシュ動作を発火させた後、サスペンドが完了するまでの間に、書き込みモードでファイルを開いているアプリケーションがファイルに対する書き込みを行ったら、どうなるのでしょうか?

心配は、不要です。ファイルシステムを管理する File System Disk Manager (fsdmgr.dll) によって、フラッシュ動作後のファイルアクセスは、ブロックされます。WriteFile() や ReadFile() などのファイルアクセス API の全ての呼び出しは、ファイルシステムの実体(ファイルシステムのドライバ)による実装が呼び出される前に、fsdmgr.dll によるチェックが行われ、そのファイルシステムが所属するボリュームが電源 OFF 状態の場合、電源が ON されて利用可能になるまでの間、ブロックされる(待たされる)ようになっています。レジストリやファイルシステムのフラッシュ動作においては、ファイルをアクセスしているスレッドが全てアクセスし終えるのを待ち、アクセスし終わった時点で、メモリ上のファイルキャッシュを全てコミットしてから、マウントされている全てのボリュームを電源 OFF 状態にします。それ以降にファイルアクセスを行おうとしたスレッドは、ファイルアクセス API 内部でブロックされますので、フラッシュ動作後に再度メモリ上のファイルキャッシュ内容が更新されてしまう、ということは起きないのです。

fsdmgr.dll による、ボリュームの電源 OFF 時の保護動作に興味がある人は、ソースをご覧になってみて下さい。fsdmgr.dll のソースは、
 <WINCE600>/PRIVATE/WINCEOS/COREOS/STORAGE/FSDMGR/
にあります。このディレクトリの、mountedvolume.{hpp,cpp} や {file,fsdmgr,volume}api.{cpp,hpp} が、関連するソースです。

■電源 OFF について
サスペンド動作の項が、だいぶ長くなってしまいました。電源 OFF については、短めにします。

実は、WinCE の Power Management では、電源 OFF 状態のサポートは、標準では提供されていません。つまり、デフォルトの実装では電源 OFF 状態がサポートされていないため、WinCE デバイスを実装する側(OEM)での対応が必要です。実際、SetSystemPowerState() に POWER_STATE_OFF を渡しても、エラーが返ります。エラーが返る直接の原因は、Power Management に関するレジストリ設定で、POWER_STATE_OFF に対応する電源状態の指定が存在しないからです。デバイスが取りうる電源状態は、D0~D4 がありますが、電源 OFF 状態を指す D4 と、POWER_STATE_OFF とを関連づける指定が存在していないため、SetSystemPowerState() に POWER_STATE_OFF を渡すと、無効な状態が指定されたものとして扱われ、エラーになるのです。

D0~D4 の電源状態の説明は、WinCE のリファレンスで “Device Power States” を見て下さい:
 http://msdn.microsoft.com/en-us/library/aa932261.aspx

Power Management に関するデフォルトのレジストリ設定は、
 <WINCE600>/PUBLIC/COMMON\OAK/FILES/common.reg
にあります。common.reg をテキストエディタで開き、’HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Power\State’ で検索してみて下さい。このレジストリキーによる、電源状態の指定は、WinCE のリファレンスの “Mapping of System Power States to Device Power States” で説明されています:
 http://msdn.microsoft.com/en-us/library/aa920052.aspx

common.reg での指定を見ると、”Flags” キーに POWER_STATE_OFF を指定した有効な設定が、存在しません。POWER_STATE_OFF と D4 を関連づけた、ShutDown という名前の指定があるのですが、コメントアウトされています。コメントアウトされた ShutDown に対しては、以下のコメントが記されています:

;;;; Entering this system power state shuts down the system. All devices are powered off,
;;;; resuming may require user intervention. The system will cold boot on resume.
;;;; Supporting this power state requires that the OEM customize the Power Manager
;;;; to recognize POWER_STATE_OFF and take platform-specific action to remove
;;;; power.

ここで、ShutDown の指定を有効にするには、Power Manager のカスタマイズが必要だと書かれています。これに対応するのが、Power Manager (pm.dll)のソースのうち、
 <WINCE600>/PUBLIC/COMMON/OAK/DRIVERS/PM/PDD/DEFAULT/platform.cpp
です。platform.cpp にある PlatformSetSystemPowerState() の本体で、POWER_STATE_RESET に対するチェックを行っている箇所に、以下のコメントがあります(358行目付近):

// Handle resets and shutdowns here, after flushing files. Since Windows CE does
// not define a standard mechanism for handling shutdown (via POWER_STATE_OFF),
// OEMs will need to fill in the appropriate code here. Similarly, if an OEM does
// not support IOCTL_HAL_REBOOT, they should not support POWER_STATE_RESET.

あなたがアプリケーションの開発を担当していて、お使いの WinCE デバイスがソフトウェアによる電源 OFF 機能をサポートしている場合には、SetSystemPowerState() に POWER_STATE_OFF を渡すことによって、アプリケーションから電源 OFF できるかも知れません。それができるかどうかは、お使いの WinCE デバイスのハードウェアが、ソフトウェアによる電源 OFF 機能を持っているか(※CPU から電源回路を OFF するための支援機能がハードウェアに組み込まれているか)と、その WinCE デバイスの OAL が、POWER_STATE_OFF に対する KernelLibIoControl() をサポートしているかにかかっています。

SetSystemPowerState() で POWER_STATE_OFF を使えるかどうかを簡単に調べる方法は、実際にテストコードを書いて動かしてみるか、または、レジストリの HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Power\State キー配下を調べ、”Flags” キーに POWER_STATE_OFF (0×00020000) を指定したエントリがあるかどうかを確認することだと思います。

POWER_STATE_OFF の指定を使えない場合は、手作業でデバイスの電源を切るしかありません。もちろん、電源を切る前には、デバイスをサスペンド状態にして下さい。サスペンド状態への遷移をアプリケーションから行う場合は、「サスペンド動作」の項で述べたように、keybd_event(VK_OFF, 0, 0, 0); を実行するのが簡単でしょう。OS デザインに Power Management と GWES を組み込んでいない場合は、WinCE のリファレンスの次のページが参考になるでしょう:

・OEM Power Off State
 http://msdn.microsoft.com/en-us/library/aa913544.aspx

このページの説明には、GWES や Power Management が組み込まれていない場合、サスペンドするには、次の呼び出しを実行すればよいと書かれています:

 FileSystemPowerFunction(FSNOTIFY_POWER_OFF);
 PowerOffSystem();

Add comment 2009/07/21 koga

アプリケーションからリブート(または電源 OFF)~その1(1/2)

「WinCE では、Linux のように、リブートやシャットダウンを行うコマンドは無いのですか?」
先日、こんな質問を某所でもらいました。「シェルのコマンドとしては、存在していないはずですが、リブートしたりホールト(halt)するコマンドプログラムは、作れると思います。必要な API などを調べてみますので、少し時間を下さい。」そう回答して帰ってきたので、調べてみました。

なお、回答した際には、「ただし、PC Linux のように自動で電源 OFF する機能は、ハードウェアの支援が必要なので、デバイスによっては、シャットダウンしても電源は切れず、電源を切れる状態になるだけです。」と言い添えてあります。

調べた結果、OS デザインに Power Management が組み込まれている場合、リブートするには、SetSystemPowerState() を使うのが最も簡単だと分かりました。SetSystemPowerState() については、WinCE のリファレンスをご覧ください:
 http://msdn.microsoft.com/en-us/library/aa929708.aspx
SetSystemPowerState() の第二引数に POWER_STATE_RESET を渡せば、WinCE がリブートします。また、POWER_STATE_RESET ではなく、POWER_STATE_SUSPEND を渡すと、サスペンド状態となりますので、安全に電源を切ることができます。

では、OS デザインに Power Management を組み込んでいない場合には、どうすればよいのでしょうか?以下に、リブート動作とサスペンド動作について、もう少し詳しく書きます。ただし、以下で述べるのは、Windows Embedded CE 6.0(WinCE 6.0)に対するものです。WinCE 5.0 以前のバージョンについては、確認していませんが、以下で述べることがそのまま当てはまるわけではないと考えて下さい。
(※サスペンド動作については、次回に書くことにします。今回は、少々長くなるので、二回に分けて書くことにしました。)

■リブート動作
リブート動作の詳細は、ボードや CPU ごとに異なります。従って、その部分は、OAL で実装されます。具体的には、OALIoCtlHalReboot() が、リブート動作の実装です:
 http://msdn.microsoft.com/en-us/library/aa910452.aspx
デバイスエミュレータの場合、OALIoCtlHalReboot() のソースは、
 <WINCE600>/PLATFORM/DEVICEEMULATOR/SRC/OAL/OALLIB/reboot.c
です。この reboot.c を見ると、デバイスエミュレータの CPU(S3C2410) のウォッチドッグ・タイマの発火時間を短い値にセットして、CPU にリセットがかかるようにしているのが分かります。

さて、単純に CPU にリセットをかけ、OS をリブートした場合、レジストリやファイルシステムに不整合が生じる可能性があります。レジストリやファイルシステムに対する更新が、メモリ上のキャッシュに対してのみ行われ、永続記憶域に反映されていない状態でリセットがかかれば、更新内容が失われてしまうからです。従って、OALIoCtlHalReboot() の呼び出しを行う前に、レジストリやファイルシステムのフラッシュ動作を行わなければいけません。

また、OALIoCtlHalReboot() を呼び出せるのは、カーネルだけです。つまり、アプリケーションから呼び出すことは、できません。アプリケーションからデバイスをリブートする場合は、カーネルに対して OALIoCtlHalReboot() の呼び出しを要求し、さらに、OALIoCtlHalReboot() が呼び出される前に、レジストリやファイルシステムのフラッシュ動作を発火させなければいけません。これらの処理は、SetSystemPowerState() を使えるのであれば、意識する必要は、ありません。Power Management 機能が、必要な処理を行ってくれるからです。

では、Power Management を OS デザインに組み込んでおらず、使えない場合は、どうすればよいのでしょうか。その場合は、カーネルに対して、リブート動作を要求します。制御コードに IOCTL_HAL_REBOOT を指定して KernelIoControl() を呼び出すことにより、カーネルにリブート動作を行わせることが出来ます。

ただし、KernelIoControl() で IOCTL_HAL_REBOOT を指定することは、アプリケーションからは出来ません。OAL 側でサポートを追加しない限り、アプリケーションによる KernelIoControl() の呼び出しでは、IOCTL_HAL_REBOOT は未サポートの制御コードとして扱われます。

アプリケーションから 、つまり、ユーザモードから KernelIoControl() を呼び出した場合、その応答処理は、
 <WINCE600>/PUBLIC/COMMON/OAK/OALIOCTL/oalioctl.cpp
で実装されている IOControl() で処理されます。ソース(oalioctl.cpp)を見れば分かるように、IOControl() では、以下の制御コードのみに対して応答動作を行い、それ以外の制御コードに対しては、ERROR_NOT_SUPPORTED エラーとして処理するのです。

IOCTL_HAL_GET_CACHE_INFO
IOCTL_HAL_GET_DEVICE_INFO
IOCTL_HAL_GET_DEVICEID
IOCTL_HAL_GET_UUID
IOCTL_PROCESSOR_INFORMATION

なお、oalioctl.cpp のコメントに書かれていますが、OEM、つまり WinCE デバイスを実装する側では、oalioctl.cpp を編集して、IOControl() が応答する制御コードを追加/削除することができます。上で、「OAL 側でサポートを追加しない限り」と書いたのは、IOControl() が応答する制御コードとして IOCTL_HAL_REBOOT が追加されていれば、アプリケーションから KernelIoControl() を呼び出してリブートできる、という意味なのです。

OAL 側でサポートを追加していない場合は、アプリケーションから KernelIoControl() を呼び出して IOCTL_HAL_REBOOT を指定しても、エラーになってしまうので、リブートできません。その場合は、専用のデバイスドライバを作り、デバイスドライバから KernelIoControl() を呼び出せば、IOCTL_HAL_REBOOT を指定できます。上記の oalioctl.cpp が関与するのは、ユーザモードから KernelIoControl() が呼び出された場合であり、カーネルモードからの呼び出しでは、全ての制御コードが処理されます。従って、カーネルモードのデバイスドライバから KernelIoControl() を呼び出せばよい、というわけです。

ところで、リブートする際に、KernelIoControl() を呼び出すだけでよいのでしょうか?IOCTL_HAL_REBOOT を渡して KernelIoControl() を呼び出せば、カーネルによって OALIoCtlHalReboot() が呼び出されます。しかし、リブートの際には、OALIoCtlHalReboot() の呼び出しを行う前に、レジストリやファイルシステムのフラッシュ動作を行わなければいけないと書きました。

実は、デバイスドライバから呼び出す場合は、KernelIoControl() を呼び出すだけで、必要な処理が行われます。ただし、ここで述べるのはドキュメント化された情報ではなく、WinCE 6.0 のカーネルのソースを追跡して判断したことですので、ご注意下さい。念のため、アプリケーションから呼び出す場合のように(※これについては、後で述べます)、KernelIoControl() の前に FileSystemPowerFunction() を呼び出す方が良いでしょう。

カーネルモードから KernelIoControl() を呼び出した場合ですが、k.coredll.dll を経由して、カーネル内部の同じ名前の関数が呼び出されます。カーネル内部の KernelIoControl() は、
 <WINCE600>/PRIVATE/WINCEOS/COREOS/NK/KERNEL/kwin32.c
で実装されています。このソースを見れば、IOCTL_HAL_REBOOT が渡された場合、KernelIoControl() から CallOEMIoControl() を経由して、NKReboot() が呼び出されることが分かります。NKReboot() は、IOCTL_HAL_REBOOT を渡して OEMIoControl() を呼び出すことにより、OALIoCtlHalReboot() の呼び出しを行うのですが、OEMIoControl() の前に、FSNotifyMountedFS() というカーネル内部関数を呼び出します。この FSNotifyMountedFS() によって、レジストリやファイルシステムのフラッシュ動作が実行されるのです。

一方、OAL 側でサポートが追加されていて、アプリケーションから KernelIoControl() に IOCTL_HAL_REBOOT を渡した場合、上述した oalioctl.cpp の IOControl() では、そのまま OEMIoControl() を呼び出します。そのため、レジストリやファイルシステムのキャッシュ動作は、行われません。従って、アプリケーション自身が、レジストリやファイルシステムのキャッシュ動作を発火させる必要があります。具体的には、FileSystemPowerFunction() の引数に FSNOTIFY_POWER_OFF を渡して呼び出します。FileSystemPowerFunction() のリファレンスは、
 http://msdn.microsoft.com/en-us/library/aa914779.aspx
です。

次は、「サスペンド動作」と、「電源 OFF について」という項を書くつもりでしたが、少々長くなってしまいました。この二つについては、次回に書くことにします。

1 comment 2009/07/20 koga

USB キーボードと kbdmouse.dll

WinCE の OS イメージに USB キーボードのサポートを追加する場合、何が必要なのでしょうか?USB ホストコントローラのドライバと、USB HID キーボードのドライバを追加すれば OK、というのは、残念ながら間違いです。

USB マウスの場合は、USB ホストコントローラのドライバと USB HID マウスのドライバを OS イメージに追加すれば使えるようになりますが、キーボードの場合は、若干事情が異なります。その違いは、入力処理の複雑さにおける、マウスとキーボードの違いに由来しています。具体的には、キーボード配列(キーボードレイアウト)に対する処理が必要なために、同じ USB HID ドライバであっても、キーボードの方が、マウスよりもドライバの構成が複雑なのです。

両者の違いを知るには、ソースを見るのが手っ取り早いやり方です。USB HID のマウスとキーボードのドライバは、それぞれ、以下のディレクトリ配下にソースがあります:

 ・USB HID マウス
 <WINCE600>/PUBLIC/COMMON/OAK/DRIVERS/USB/CLASS/HID/CLIENTS/MOUHID/

 ・USB HID キーボード
 <WINCE600>/PUBLIC/COMMON/OAK/DRIVERS/USB/CLASS/HID/CLIENTS/KBDHID/

ドライバのバイナリは、マウスが mouhid.dll、キーボードが kbdhid.dll です。お手元に、USB のマウスとキーボードが使える OS イメージ(nk.bin)があれば、それを Platform Builder で開き、内容を見てみて下さい。mouhid.dll と kbdhid.dll が入っているはずです。

さて、mouhid.dll と kbdhid.dll を確認した際に、注意深い人なら、kbdmouse.dll という DLL があることに気付くでしょう。ファイル名からすると、キーボードとマウスの面倒を見るドライバのようですが、実は、このドライバが、キーボード配列に対する処理を行うドライバなのです。

kbdmouse.dll について見る前に、mouhid.dll と kbdhid.dll のソースに話しを戻しましょう。これら二つのドライバの複雑さの違いは、ソースのサイズを比べてみても分かります。MOUHID/ ディレクトリ配下のソースと KBDHID/ ディレクトリ配下のソースのサイズを比べれば、KBDHID/ ディレクトリ配下のソースの方が大きいですし、バイナリのサイズも、kbdhid.dll の方が mouhid.dll よりも大きくなっています。また、これらのドライバが DLL として export している関数を比べても、kbdhid.dll の方が多いことが分かります。MOUHID/mouhid.def と、KBDHID/kbdhid.def の内容を見比べてみて下さい。

mouhid.def の方は、export するシンボルとして、HID クライアントドライバとしての、次の二つの関数が書かれています:
 HIDDeviceAttach
 HIDDeviceNotifications

一方、kbdhid.def の方には、これら二つに加え、ストリームインタフェースドライバの六つの関数が書かれています:
 HIDDeviceAttach
 HIDDeviceNotifications
 KBD_Init
 KBD_PreDeinit
 KBD_Deinit
 KBD_Open
 KBD_Close
 KBD_IOControl

mouhid.dll と kbdhid.dll は、どちらも内部にスレッドを持ち、自身が制御する USB デバイス(マウスやキーボード)から、インタラプト転送でデータを読み出し、読み出したデータを処理して GWES に通知する、という構造です。全体の構造の大枠は違わないのですが、kbdhid.dll の方が、export している関数の個数を見ても分かるように、より複雑です。

さて、kbdhid.dll が、HID クライアントの関数に加え、ストリームインタフェースドライバの関数を export しているのは、実は、他のドライバから呼び出されるためです。その他のドライバというのが、上で出てきた、kbdmouse.dll なのです。この kbdmouse.dll のソースがどこにあるのかというのは、おそらく、WinCE のソースディレクトリを眺めてみても分からないでしょう。kbdhid.dll や mouhid.dll であれば、DLL の名前と同じディレクトリがありますから、比較的分かりやすいのですが、kbdmouse.dll は、対応するディレクトリが見当たりません。それもそのはず、この DLL は、OS イメージのビルド時に、他の DLL をコピーして作られるのです。

どうやって kbdmouse.dll が生成されるのかは、platform.bib を見て下さい。次のような記述が見つかるでしょう:

; @CESYSGEN IF CE_MODULES_KEYBD || CE_MODULES_POINTER
#if ! (defined BSP_NOKEYBD && defined BSP_NOMOUSE)
#if ! (defined IMGPPC || defined IMGTPC)
IF LOCALE=0411 !
IF LOCALE=0412 !
IF BSP_KEYBD_NOP
; @CESYSGEN IF CE_MODULES_NOPKEYBOARD
kbdmouse.dll        $(_FLATRELEASEDIR)\KbdNopUs.dll         NK SHK
; @CESYSGEN ENDIF CE_MODULES_NOPKEYBOARD
ENDIF   ; BSP_KEYBD_NOP

ENDIF   ; LOCALE != 0412
ENDIF   ; LOCALE != 0411
IF LOCALE=0411
IF BSP_KEYBD_JPN1
IF BSP_KEYBD_NOP
; @CESYSGEN IF CE_MODULES_NOPKEYBOARD
kbdmouse.dll        $(_FLATRELEASEDIR)\KbdNopJpn1.dll       NK SHK
; @CESYSGEN ENDIF CE_MODULES_NOPKEYBOARD
ENDIF   ; BSP_KEYBD_NOP

これは、デバイスエミュレータの platform.bib から抜き出したものですが、カタログ項目の選択により、KbdNopUs.dll や KbdNopJpn1.dll など、複数存在する DLL のうちのどれかをコピーして、kbdmouse.dll が生成されることが分かります。コピー元の DLL は、名前の末尾が ‘Us’ や ‘Jpn1′ など、言語/国を識別する文字列になっていますが、これらの DLL は、キーボードから入力された scan code を virtual key code に変換する処理を実装しています。言語設定ごとのキーボード配列を、個々の DLL に埋め込み、どの DLL を kbdmouse.dll にするかで、キーボード配列を切り替える、というわけです。

キーボード配列に関する仕組みについては、WinCE のリファレンスで、キーボードドライバの “Layout Manager” の項を参照して下さい:
 http://msdn.microsoft.com/en-us/library/aa927162.aspx

キーボードドライバについて、長々と書きましたが、最初の問題に戻りましょう。USB キーボードのサポートを追加するには、USB Host コントローラのドライバと、USB HID キーボードのドライバ(kbdhid.dll)を追加するだけでは不足。では、何が必要か、という問題です。正解は、kbdmouse.dll でした。では、kbdmouse.dll を追加しないと、何が起きるのでしょう?

kbdmouse.dll が OS イメージに含められていないと、USB キーボードを USB ポートに繋いでも、キーボードとして認識されるものの、キーボードをタイプしても、文字入力されません。USB キーボードから scan code が入力されても、kbdmouse.dll が存在しないために virtual key code に変換できず、GWES がキーイベントを発生させることが出来ないからです。

kbdhid.dll のソースを見ると、keybd_event() を呼び出している箇所があるのですが(kbdhid.cpp の KeyboardEvent() です)、keybd_event() の第一引数に渡す virtual key code の値は、MapVirtualKey() を呼び出して scan code から変換したものを使っています。ここで、kbdmouse.dll が存在しないと、MapVirtualKey() が呼び出された際、GWES が virtual key code への変換を行えず、MapVirtualKey() が 0 を返します。この結果、keybd_event() の第一引数が 0、つまり不正な値になるので(※virtual key code の値は、[1..254] の範囲の値でなければいけません)、キーイベントが発生しません。

キーボードドライバ(kbdmouse.dll)による MapVirtualKey() の機能の実装については、WinCE のリファレンスで、キーボードドライバの “Keyboard Driver MDD Functions” の項を参照して下さい。PFN_KEYBD_DRIVER_MAP_VIRTUAL_KEY が、MapVirtualKey() に対して、キーボードドライバの MDD(Model Device Driver) レイヤが実装する関数です:
 http://msdn.microsoft.com/en-us/library/aa927863.aspx

この関数を実装したソースファイルは、
 <WINCE600>/PUBLIC/COMMON/OAK/DRIVERS/KEYBD/LAYMGR/
にある laymgr.cpp です。お手元に、USB キーボードを使える WinCE 6.0 の開発ボードがあれば、カーネルデバッガで追跡してみるのも面白いでしょう。laymgr.cpp で実装されている KeybdDriverMapVirtualKey() に、カーネルデバッガでブレークポイントをセットして、USB キーボードのキーをタイプしてみて下さい。kbdhid.cpp の GenerateKeyInfo() が MapVirtualKey() を呼び出し、その結果、GWES を経由して KeybdDriverMapVirtualKey() が呼び出されるのが分かるでしょう。

ちなみに、KbdNopUs.dll が export するシンボルを定義しているのは、
 <WINCE600>/PUBLIC/COMMON/OAK/DRIVERS/KEYBD/DLL/KBDNOP/
にある kbdnop.def です。このファイルに、 KeybdDriverMapVirtualKey も書かれています。

だいぶ長くなってしまいましたが、最後に、もう一つおまけです。WinCE のリファレンスには、HID キーボードのドライバが、キーボードの LED の点灯制御を行うために必要な手順の説明があります(”Adding Keyboard LED Support to the HID Keyboard Driver”):
 http://msdn.microsoft.com/en-us/library/aa918138.aspx

ターゲットボードにキーボード相当のハードウェアが搭載されておらず、キーボードを使うには USB キーボードを繋ぐしかなく、そして、USB キーボードは一つしか繋がない、という場合には(※一般的には、それが普通でしょう)、KbdNopUs.dll などの、WinCE 付属のキーボードドライバを kbdmouse.dll として OS イメージに組み込めば十分です。

しかし、たとえば、ターゲットボードにキーパッドが搭載されており(※評価ボードの場合だと、そういうことがありますね)、キーパッドでも文字入力できて、Caps Lock キーや Num Lock キー機能も使える場合には、そのボードに USB キーボードを繋げた際、ボード搭載のキーパッドと外付けの USB キーボードとの間で、Caps Lock や Num Lock の状態を同期させなければいけません。そのような場合には、上で説明されている処理を実装する必要がある、というわけです。その場合は、WinCE 付属のキーボードドライバをそのまま使うのではなく、自前で実装しなければいけません。たぶん、
 <WINCE600>/PUBLIC/COMMON/OAK/DRIVERS/KEYBD/LAYMGR/
にある LayoutManager.lib のソースと、
 <WINCE600>/PUBLIC/COMMON/OAK/DRIVERS/KEYBD/HIDIOCTL/
にある hidioctl.{cpp,h} を別の場所にコピーしたうえで、laymgr.cpp を改変して使うことになるでしょう。そして、改変版の LayoutManager.lib を組み込んだ自前のキーボードドライバをビルドして、その DLL を kbdmouse.dll として OS イメージに組み込む、というわけです。

Add comment 2009/07/10 koga

Lilas-am500-6

一昨日(2009/07/07)の夜、弊社で開発した Windows Embedded CE 6.0 の BSP の無償版を公開しました。ターゲットボードは、アットマークテクノ社Armadillo-500 開発セットで、BSP の製品名は “Lilas-am500-6″ です(※”Lilas” は、「リラ」と発音します)。今回公開した無償版が “Basic Version” で、別途、有償版の “Comfort Version” を準備中です。詳細は、弊社のニュースページをご覧ください:
 http://www.stprec.co.jp/news/20090707.html

Lilas-am500-6 Basic Version の初版リリースでは、Ethernet ドライバが動作していないので、Ethernet ポートを必ず一つは備えている Armadillo の魅力を、十分に活かすことができません。また、OS イメージのビルド手順や、ビルドした OS イメージを Armadillo-500 のオンボード Flash に書き込む手順を説明した資料も付けていませんので、できるだけ早急に、対応を追加します。

なお、Basic Version のディスプレイドライバは、DirectDraw や Direct 3D Mobile に対応しておらず、Armadillo-500 に搭載されている Freescale 社の i.MX31/i.MX31L プロセッサが持つグラフィクス処理能力を活かしきれていません。DirectDraw や Direct 3D Mobile に対応したディスプレイドライバは、有償版となります。ただし、「Windows Embedded CE 6.0 の開発環境を入手していないが、どんな感じなのか、とりあえず Armadillo-500 で動かしてみたい」という方のために、デモ用のビルド済み OS イメージを配布できないか、検討中です。もし、デモ用のビルド済み OS イメージを配布できることになった場合には、DrectDraw 対応のディスプレイドライバも含めてみたいと考えています。

ともあれ、まだまだやることが残っていますので、引き続き開発を継続し、品質を向上させていきます。

Add comment 2009/07/09 koga


Categories

Links

Posts by Authors

Recent Posts

Calendar

2009年7月
« 12月   8月 »
 1234
567891011
12131415161718
19202122232425
262728293031  

Posts by Month

Posts by Category

Meta