My Photo
無料ブログはココログ
September 2019
Sun Mon Tue Wed Thu Fri Sat
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30          

他のアカウント

お気に入りのもの

  • SONY: SONY PHA-2
  • iCOP: VESAPC eBOX3310 VEPCEB31
  • Windows Embedded CE 6.0組み込みOS構築技法入門 (マイクロソフト公式解説書)

Windows CE用ドライバ簡単ツールのご紹介

Windows CEのデバイスドライバを組み込むのはそれほど難易度が高いものではありません。しかし初めて取り組む人にとっては、その書式など、よくわからない事も多いですよね。一回、ひな型を作ってしまえば、後はそれをトレースするだけなのですが、その最初のひな型をどう作るかでつまづく事もあるかと思います。

そこで最初の一歩を踏み出すためのツールとしてOpen Source ComunityのCodePlexからWindows Embedded CE Stream Driver Wizard「CEDriverWiz」がリリースされています。

このCEDriverWizはドライバの名前と、3文字のプリフィックスを入力するだけで、Windows CEのストリームデバイスドライバのひな型と、周辺のファイルをサクっと作ってくれます。RETAILMSGとDEBUGMSGも最初から埋め込んでくれるので、これも案外便利かも。

ただしDEBUGMSについては、

  DEBUGMSG(1,(TEXT("~

という感じで、デバッグメッセージとしてのコントロールに全く対応していないのが泣けてきます。

イベント駆動によるデバイスドライバ - その2

さてキーボードを押下することでISTまで制御が渡されることを確認しました。このようにISTまで来てしまえば、後はデバイスドライバとしての処理をこなすことになります。その時にこのISTを動かすための設定を再確認してみます。

ISTを駆動するためには、WaitForSingleObject関数で待つためのEventオブジェクトを作成し、そしてそのEventオブジェクトをInterruptInitialize関数を使ってシステム割り込み番号と結びつけてあげる必要があります。参考にしているキーボードドライバのISTではすでにこれらの設定が終了しています。そこでこのISTを起動するにあたり、設定を行っている個所を追ってみます。

先の記事でIST内部でブレークをかけましたが、このブレークがかかった状態でISTがどのように呼び出されたのかを確認します。ブレークしている個所をだれが呼び出したのかを見るためには“デバッグ:ウィンドウ:呼び出し履歴”メニューを選択します。すると以下のようなウィンドウが開きます。

この情報を見ると、このISTはnk.exe、すなわちカーネルプロセスで動作していて次のような順序で呼び出しが行われていることがわかります。これを呼び出した順に並べてみると、以下のようになります。

  1. K.Coredll.dll:TreadBaseFunc()・・・カーネルWin32APIコール
  2. KbdMouse.dll:Ps2KeybdIsrThread()
  3. KbdMouse.dll:Ps2Keybd::IsrThreadProc()
  4. KbdMouse.dll:KeybdIstLoop()・・・現在の関数

すなわち、Ps2KeybdIsrTread関数かPs2Keybd::IsrThreadProc関数で各種設定がされていることがわかります。そしてISTに対してはpKeybdIstを通じて引数として渡されています。

BOOL
KeybdIstLoop(
   PKEYBD_IST pKeybdIst
   )

このpKeybdIstにはISTを駆動するために必要なEventオブジェクトのハンドルがpKeybdIst->hevInterruptとしてセットされています。KeybdIstLoop関数ではこのEventオブジェクトがシグナル状態に変化するのを待つことになります。

先の呼び出し履歴ウィンドウでPs2Keybd::IsrThreadProc()の行をダブルクリックすると、呼び出した位置がエディタ上に表示されます(呼び出し履歴の機能的に優れているところです)。Ps2Keybd::IsrThreadProc関数では、以下のようにCreateEvent関数でISTを駆動するためのEventオブジェクトを生成し、InterruptInitialize関数でシステム割り込み番号とEventオブジェクトを関連付けています。そしてこのシステム割り込み番号g_dwSysIntr_Keybdは、コマンドの引数としてIOCTL_HAL_REQUEST_SYSINTRを渡してKernelIoControl関数で取得しています。このようにキーボード割り込みからISR、そしてISTへの流れが出来上がっています。

  1. m_hevInterrupt = CreateEvent(NULL,FALSE,FALSE,NULL);
  2. InterruptInitialize(g_dwSysIntr_Keybd,m_hevInterrupt,NULL,0)

そして本関数ではKeybdIstLoop関数に引数として渡すために、KEYBD_IST構造体に値をセットします。ISTとしてWaitForSingleObject関数で待つためのEventオブジェクトのハンドラもここでセットしています。

    KEYBD_IST keybdIst;
    keybdIst.hevInterrupt = m_hevInterrupt;
    keybdIst.dwSysIntr_Keybd = g_dwSysIntr_Keybd;
    keybdIst.uiPddId = v_uiPddId;
    keybdIst.pfnGetKeybdEvent = KeybdPdd_GetEventEx2;
    keybdIst.pfnKeybdEvent = v_pfnKeybdEvent;

このようにISTで必要とされるEventオブジェクトと他の値をセットし、ISTを起動することで、イベント駆動のドライバとして動作させることができます。

さて、なんとなく週刊Windows CEみたいな頻度になっていますが、次回はこれらの一連の処理の中で、InterruptInitialize関数、InterruptDone関数がどのような役割を担っているのかについて説明をします。

つづく...

イベント駆動によるデバイスドライバ - その1

先の予告の通り、イベント駆動によるデバイスドライバについて説明を進めたいと思います。連載ものになりますので、ちょっと時間がかかるかもしれません(と予防線を張っておきます)。

まずイベント駆動型のドライバは割り込みによって動作するドライバです。本ブログではその昔「イベント駆動型のドライバ」という記事で、さわりだけを書いています。今日の記事では実際のイベント駆動型のドライバについて動きを確認してみます。とはいうものの、実際のハードウェアを使って確認をすることは、ブログでは難しいのでエミュレータで試してみます。また基本的な動きはWinCE5.0、WinCE6.0ともに同じなので、ここはWinCE6.0での確認とします。OSイメージはMobile Handheldで動作検証を行います。

最初にイベント駆動型デバイスドライバの動きのおさらいです。

  1. ハードウェア割り込みが発生し、割り込みハンドラとして動作するISRを起動する。
  2. ISRでは割り込み要因を判定し、割り込みのマスク、割り込み要因のクリアを行う。そして割り込み要因に対応するシステム割り込み番号“SYSTINTR_xxx”を戻り値として返す。
  3. カーネルはSYSINTR_xxxと関連付けられたEventオブジェクトをシグナル状態にする。
  4. デバイスドライバのISTではWaitForSingleObject関数で対象のEventオブジェクトがシグナル状態になったことを確認し、ドライバの処理を行う。
  5. ドライバ処理が終了するとInterruptDone関数をコールする。
  6. InterruptDoneをコールすると、OEMInterruptDone関数が呼び出される。OEMInterruptDone関数では、対象の割り込みをイネーブルにする。

それでは実際の動きを見てみることにしましょう。エミュレータで簡単に割り込みによって動きを見ることのできるドライバはキーボードドライバとなります。そこでキーボードドライバのISTにブレークポイントを設定し、キーボード割り込みによる動作を確認します。ソリューションエクスプローラーで以下のフォルダに含まれているKeybdist.cppを開いてください。

%_WINCEROOT%\PUBLIC\COMMON\OAK\DRIVERS\KEYBD\IST

このファイルの中でWaitForSingleObject関数を検索すると、KeybdIstLoop関数に含まれていることがわかります。このWaitForSingleObject関数通過後の位置にブレークポイントをセットします。

ここでわかることはpKeybdIst->hevInterruptに設定されたEventオブジェクトを待っているということです。そしてキーボードからの割り込みが入ると、cEvent変数にキーボード割り込みの回数をセットし、その回数分だけキーコードを取り出して*pKeybdIst->pfnKeybdEvent関数に渡しています。そして最後にInterruptDoneをコールして、またWaitForSingleObject関数でEvent待ちとなっています。

※この*pKeybdIst->pfnKeybdEvent関数はキーボードドライバで取得したキーコードをセットするコールバック関数です。

実際にエミュレータをアクティブにして、キーを叩くと以下のようにブレークします。

このようにキーボード押下によって、ISTまで到達することを確認することができました。次回はこのキーボードドライバのISTに関する設定等を確認したいと思います。

つづく...

WinCE6.0ポインタの受け渡し - その4

前回はアプリケーションからユーザーモードドライバ(UMドライバ)に引数としてポインタを渡した場合のException発生について確認をしました。このままではUMドライバに対してポインタ渡しで大量のデータのRead/Writeをすることができません。そこで今回はこのいかにUMドライバに対してポインタを渡すのかということについて説明をします。使用するサンプルのアプリケーションは以下にありますのでダウンロードしてください。また呼び出すUMドライバは前回使用したものをそのまま使います。

ダウンロードしたファイルを解凍し、前回使用したOSイメージのサブプロジェクトとして追加しビルドしてください。ビルドはいつものように“Build:Advanced Build Command:Build Current BSP and Subproject”メニューでnk.binを構築します。

最初にこのアプリケーションのポインタの渡し方を見てみます。ヘッダファイルDrv02_cmd.hで引数のための構造体__LS_DRV_02_BUFFERを定義し、そのメンバ変数pEmbeddedPointerEmbbedded Pointerとして使用することになります。

【Drv02_cmd.h:引数用構造体の定義】

typedef struct __LS_DRV02_BUFFER
{
    DWORD        dwSize;
    LPBYTE       
pEmbeddedPointer;
} DRV02_BUFFER, *PDRV02_BUFFER;

次にこの構造体を使ってドライバを呼び出している部分を確認します。Drv02TestMemory2.cのWinMain関数ではp->pEmbeddedPointerにこのユーザープロセスのローカルヒープをVirtualAlloc関数で割り当てています。この関数で確保して渡されるポインタはDrv02TestMemory2.exeのプロセス空間に配置されるアドレスです。

【WinMain関数:UMドライバの呼び出し】

int WINAPI
WinMain(
    HINSTANCE    hInstance,
    HINSTANCE    hPrevInstance,
    LPWSTR        lpCmdLine,
    int            nCmdShow
    )
{
    HANDLE    hDrv02;
    DWORD    dwReturnValue;
    DRV02_BUFFER    Drv02Buffer;
   
PDRV02_BUFFER    p;
    DWORD    i;

    p = &Drv02Buffer;
   
p->pEmbeddedPointer = VirtualAlloc(
                            NULL,
                            CALLER_BUFFER_SIZE,
                            MEM_COMMIT,
                            PAGE_READWRITE
                            );

    ...

    // Caller buffer update
    DeviceIoControl(
        hDrv02,
        CMD_CLEAR_BUFFER,
        (LPVOID)
p,
        sizeof(DRV02_BUFFER),
        NULL,
        0,
        &dwReturnValue,
        NULL
        );

    ...

このDeviceIoControl関数でブレークをしてこの時のEmbedded Pointerであるp->pEmbeddedPointerの値と、このポインタが指すローカルヒープの値を確認してみます。p->pEmbeddedPointerの値は0x1a070000です。この値は明らかにユーザー空間を指しています。そしてこのポインタを参照すると0x00~0xffの値がセットされています。この値はプログラムでセットした値です。

070609_01

070609_02

DeviceIOControl関数を実行するとUMドライバDriver02.dllのDRV_IOControl関数が呼び出されます。ドライバ呼び出し時に指定したコマンドCMD_CLEAR_BUFFERのコードは次の通りです。ここでいくつかWinCE6.0で追加された新しいAPIが出てきます。またCeOpenCallerBuffer関数、CeAllocAsynchronousBuffer関数の引数としてそれぞれPBYTEの型を持つg_pMappedEmbeddedg_pMappedEmbeddedを定義してあります。

  • CeOpenCallerBuffer
  • CeCloseCallerBuffer
  • CeAllocAsynchronousBuffer
  • CeFreeAsynchronousBuffer

【DRV_IOControl関数:CMD_CLEAR_BUFFER処理】

    case CMD_CLEAR_BUFFER:
        {
            PDRV02_BUFFER   
pBuffer = (PDRV02_BUFFER)pInBuf;
            DWORD    i;

            // Access check in Windows Embedded CE 6.0
            if ( FAILED(
CeOpenCallerBuffer(
                            (PVOID *)&
g_pMappedEmbedded,
                            (LPVOID)
pBuffer->pEmbeddedPointer,
                            pBuffer->dwSize,
                            ARG_O_PTR,
                            TRUE)))
            {
                RetVal = FALSE;
                break;
            }

            ASSERT(g_pMappedEmbedded);

            // Marshaler in Windows Embedded CE 6.0
            if ( FAILED(
CeAllocAsynchronousBuffer(
                            (PVOID *)&
g_Marshalled,
                            (PVOID)
g_pMappedEmbedded,
                            pBuffer->dwSize,
                            ARG_O_PTR
                            )))
            {
                RetVal = FALSE;
                break;
            }

            for ( i = 0 ; i < DRIVER_WORK_SIZE ; i++ ){
//                *( g_pMappedEmbedded + i ) = 0x00;                // Synchronous
                *(
g_Marshalled + i ) = 0x00;                    // Asynchronous
            }

            if ( FAILED(
CeFreeAsynchronousBuffer(
                            (PVOID)
g_Marshalled,
                            (PVOID)
g_pMappedEmbedded,
                            pBuffer->dwSize,
                            ARG_O_PTR
                            )))
            {
                RetVal = FALSE;
                break;
            }

            if (FAILED(
CeCloseCallerBuffer(
                        (PVOID)
g_pMappedEmbedded,
                        (PVOID)
pBuffer->pEmbeddedPointer,
                        pBuffer->dwSize,
                        ARG_O_PTR)))
            {
                ASSERT(!"Cleanup call to CeCloseCallerBuffer failed unexpectedly");
                RetVal = FALSE;
            }
        }
        break;

最初にこの処理の先頭でそれぞれの変数がどのように確認をします。まず引数として渡されたpInBufがpBufferに渡されたところでの値を確認します。ドライバのグローバル変数g_pMappedEmbeddedg_Marshalledは初期化されておらず、0x00000000<Bad Ptr>となっています。そしてEmbedded Pointerとして渡されたpBuffer->pEmbeddedPointer0x1a070000<Bad Ptr>となっていてアクセスできないことが予想されます。実際に0x1a070000でメモリを参照しても見る事はできません。このままでは前回の記事で書いたようにExceptionが発生してしまいます。

070609_03

そこで次にCeOpenCallerBufferの行を実行してみます。するとg_pMappedEmbedde0x04022240がセットされました。このアドレスはDriver02.dllをホスティングしているudevice.exeプロセスのユーザー空間です。

070609_04

ここでg_pMappedEmbeddeが示している領域を確認します。すると下記のように0x00~0xffがセットされていることがわかります。そうですCeOpenCallerBufferを呼び出すことで、引数として渡されたDrv02TestMemory2.exeのユーザー空間0x1a070000を、udevice.exe(Driver02.dll)のユーザー空間のアドレス0x04022240からアクセスできるようになったのです。

070609_05

次にその下のCeAllocAsynchronousBufferを実行します。するとg_Marshalledにg_pMappedEmbeddedの値0x04022240がコピーされます。この値はすでにudevice.exe(Driver02.dll)のプロセス空間内なのでアクセスが可能となります。

070609_06

次の一連のfor分を処理すると、0x04022240のデータが全て0クリアされます。クリアされたのはudevice.exe(Driver02.dll)のプロセス空間です。そして今度はDrv02TestMemory2.cのDeviceIoControl関数呼び出しから戻った位置でブレークを張ってみましょう。そこで元々の引数p->pEmbeddedPointerが示すDrv02TestMemory2.exeのプロセス空間0x1a070000を参照してみます。なお当然ですが、この時udevice.exe(Driver02.dll)のプロセス空間である0x04022240はアクセスする事はできません。

070609_08

このようにDrv02TestMemory2.exe → udevice.exe(Driver02.dll) → Drv02TestMemor2.exeの各プロセス間でユーザープロセスのヒープを共有することができました。もう一度おさらいになりますが、ここで使用したAPIは以下の4つです。

  • CeOpenCallerBuffer:呼び出しプロセスから渡されたポインタのアクセスチェックとマーシャリングを行います(同期)。
  • CeCloseCallerBuffer
  • CeAllocAsynchronousBuffer:CeOpenCallerBufferによってマーシャリングされているポインタの再マーシャリングを行います(非同期)。
  • CeFreeAsynchronousBuffer

その他、セキュアチェックのためのCeAllocDuplicateBuffer、CeFreeDuplicateBufferなどのAPIもWinCE6.0では追加となっています。こちらの話は折を見て。

これでWinCE6.0ポインタの受け渡しシリーズはお終いです。いやぁ書くのが大変でした。

WinCE6.0ポインタの受け渡し - その3

前回の記事ではカーネルモードドライバ(KMドライバ)からユーザープロセス空間のアクセスを確認しました。次にユーザーモードドライバ(UMドライバ)から呼び出したプロセスのメモリをアクセスしてみます。UMドライバはKMドライバとは異なり、ユーザープロセスの一つとしてロードされます。一つのUMドライバは、一つのプロセス空間を持つのです。すなわち前々回の記事でも書いたように、呼び出したプロセスとUMドライバは同じ仮想アドレスを持つことになります。

今回はサンプルとしてUMドライバ(Driver02.dll)と、それを呼び出すアプリケーション(Drv02TestMemory1.exe)を準備しました。このUMドライバは以降の記事でもそのまま使用しますので、保存しておいてください。

ダウンロードしたファイルは前回同様、プロジェクトファイルとなっています。OSイメージにこれらプロジェクトを追加し、ビルドしてください。まずはこのOSイメージを起動して、いつものようにProcess Viewerを利用してDriver02.dllの動作を確認してみます。こちらを見ると、Driver02.dllがudevice.exeにホスティングされていることがわかります。そしてDriver02.dllがこの例だと0x40f00000に配置されていて、確かにユーザープロセス空間にあります。

070602_6

さてここでUMドライバDriver02.dllを呼び出すアプリケーションのDrv02TestMemory01.cを見てみます。ちょっとコードに違いがありますが、基本的には前回の記事で示したDrv01TestApp.cとほとんど同じものです。Drv02Buffer変数を定義し、そのメンバ変数Drv02Buffer.pData&nBufferをセットしています。この&nBufferはDrv02TestMemory01.exeのプロセス空間に配置されるスタックのポインタとなります。

【WinMain関数:ドライバの呼び出し】

    DRV02_RWBUFFER Drv02Buffer;
    BYTE    nBuffer;

    // Driver Driver02 open
    hDrv02 = CreateFile (
                TEXT("DRV2:"),
                GENERIC_READ | GENERIC_WRITE,
                0,
                NULL,
                OPEN_EXISTING,
                FILE_ATTRIBUTE_NORMAL,
                NULL
                );
    if ( !hDrv02 ) {                    // don't open
        RETAILMSG (1, (TEXT("Driver02 open function is failed.\r\n")));
        return 0;
    }

    // I will set data for Driver02 work area.
   
Drv02Buffer.pData = &nBuffer;
    Drv02Buffer.dwOffset = 0;
    *Drv02Buffer.pData = 0xff;

    try{
        DeviceIoControl(
            hDrv02,
            CMD_WRITE_1BYTE,
            (LPVOID)
&Drv02Buffer,
            sizeof(DRV02_RWBUFFER),
            NULL,
            0,
            &dwReturnValue,
            NULL
            );
    } except (GetExceptionCode() == STATUS_ACCESS_VIOLATION ?
                    EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH) {
          RETAILMSG (1, (TEXT("There is exception in Driver02.dll\r\n")));
    }

    ...

この時、呼び出されるDriver02.dllのDRV_IOControl関数におけるコマンドCMD_WRITE_1BYTEのコードは以下の通りです。

【DRV_Init関数:UMドライバのプロセスヒープの取得】

 pHWHead->pWorkBase = VirtualAlloc(
                            NULL,
                            DRIVER_WORK_SIZE,
                            MEM_COMMIT,
                            PAGE_READWRITE
                            );

【DRV_IOControl関数:データのWrite】

    case CMD_WRITE_1BYTE:
        {
            PDRV02_RWBUFFER   
pBuffer = (PDRV02_RWBUFFER)pInBuf;

            if ( pBuffer->dwOffset < DRIVER_WORK_SIZE ){    // Data offset check
                *((LPBYTE)
pHWHead->pWorkBase + pBuffer->dwOffset) = *pBuffer->pData;
                                                            // Exception !!
            }
            else{
                RetVal = FALSE;
            }
        }
        break;

このCMD_WRITE_1BYTEの処理では、引数で渡されたポインタpBuffer->pDataに格納されたデータを、UMドライバのヒープであるpHWHead->pWorkBaseに書き込もうとします。この記述はKMドライバの場合は問題なく通りました。それではUMドライバではどうでしょうか?

それでは動作を検証するためにDrv02TestMemory1.exeを実行してみましょう。実行の前に以下の場所にブレークポイントを設定しておいてください。

【Drv02TestMemory1.c:WinMain関数】

    try{
        DeviceIoControl(
            hDrv02,
            CMD_WRITE_1BYTE,
            (LPVOID)&Drv02Buffer,
            sizeof(DRV02_RWBUFFER),
            NULL,
            0,
            &dwReturnValue,
            NULL
            ); ←
ブレークポイントを設定
    } except (GetExceptionCode() == STATUS_ACCESS_VIOLATION ?
                    EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH) {
        RETAILMSG (1, (TEXT("There is exception in Driver02.dll\r\n")));
    }

実行するとまずはDeviceIoControl呼び出しでブレークがかかります。この時の引数Drv02Buffer.pDataを見てみると、今回の例では0x2001fcb4となっています。ここでこの引数はDrv02AppMemory1.exeのプロセス空間に配置されていることがわかります。

070602_7

確認後、再実行をすると次のようなダイアログが表示され、実行が一時停止します。メッセージを見ると、Driver02.dllでAccess ViolationによるExceptionが発生したことがわかります。

070602_8

そして停止した箇所はDriver02.cのDRV_IOControl関数であることがわかります。

【DRV_IOControl関数:Exceptionの発生】

    case CMD_WRITE_1BYTE:
        {
            PDRV02_RWBUFFER   
pBuffer = (PDRV02_RWBUFFER)pInBuf;

            if ( pBuffer->dwOffset < DRIVER_WORK_SIZE ){    // Data offset check
               
*((LPBYTE)pHWHead->pWorkBase + pBuffer->dwOffset) = *pBuffer->pData;
                                                            // Exception !!
            }

Exceptionの発生した原因を確認するためにローカル変数をチェックします。すると引数で渡されたポインタpBuffer->pDataの値が0x2001fcb4<Bad Ptr>となっていることがわかります。そうです、このポインタはDrv02TestMemory1.exeのプロセス空間のアドレスで、Driver02.dllをホスティングするudevice.exeのプロセス空間ではアロケートされていないものです。もちろんudevice.exeのプロセス空間でもこの0x2001fcb4が配置される可能性もありますが、それはudevice.exeのプロセス空間であり、Drv02TestMemory1.exeのプロセス空間とは異なります。

したがってDriver02.dllで、この0x2001fcb4をアクセスしようとすると、不正なアクセスとしてExceptionが発生しているのです。これはプロセス保護の観点から見ると、全くもって正しい動作です。

070602_9

今日はこのExceptionが発生するところまでを確認しました。もちろんこれではUSBのMass Storage ClassなどのUMドライバに対して、アプリケーションがポインタを渡すことができないということになってしまいます。WinCE6.0ではこのようなプロセス間のバッファの扱いについて、きちんとした手段が準備されています。次回はこのプロセス間のバッファの共有について、どのようにポインタを扱うかということについて解説します。

続く...

WinCE6.0ポインタの受け渡し - その2

WinCE6.0のメモリ空間について理解が進んだところで、カーネルモードドライバ(KMドライバ)からどのようにユーザーモードのプロセス空間をアクセスするのかという事を確認します。確認のためにサンプルアプリケーションとサンプルドライバをそれぞれダウンロードしてください。このサンプルソースはとあるセミナーで使用したもので、探せば他のサイトからもダウンロードできます。

なお念のためですが、このサンプルソースの再頒布、改変等は商用・非商用にかかわらず自由です。ただし著作権は私に帰属します。もっともソース自体は何も無くてお恥ずかしい限りです。

ダウンロードしたファイルを展開するとプロジェクトファイルが展開されます。適当なOSイメージを作って、そこにこれらのサブプロジェクトを追加します。OSイメージの構築はこちらを参考にしてください。サブプロジェクトの追加は、PBのメニューから“File:Add:Existing Subproject...”を選択し、追加するプロジェクトファイルを取り込むことでできます。取り込んだサブプロジェクトは、いつものように“Buil:Advanced Build Command:Build Current BSP and Subproject”メニューでnk.binに組み込みます。

さてここで今回確認をするソースコードのポイントを説明します。まずはドライバです。このドライバはビルトインのKMドライバとして動作しますので、OSイメージを立ち上げるとそのままnk.exe(カーネルプロセス)にロードされます。実際にOSイメージを立ち上げた状態で、Process Viewerで確認すると、以下のようにDriver01.dllが0xc1070000、すなわちカーネル空間にロードされていることがわかります。

070602_1

このDriver01.dllのDRV_IOControl関数では、CMD_READ_1BYTE、CMD_WRITE_1BYTEというコマンドが準備されています。CMD_READ_1BYTEではカーネルプロセスヒープから1Byte読み込んで、ユーザープロセスから渡されたポインタにデータを書き込みます。CMD_WRITE_1BYTEではユーザープロセスから渡されたポインタのデータ1Byteをカーネルプロセスヒープに書き込みます。

【DRV_Init関数:カーネルプロセスヒープの取得】

  pHWHead->pWorkBase = VirtualAlloc(
                            NULL,
                            DRIVER_WORK_SIZE,
                            MEM_COMMIT,
                            PAGE_READWRITE
                            );

【DRV_IOControl関数:データのRead/Write】

    case CMD_READ_1BYTE:
        {
            PDRV01_RWBUFFER   
pBuffer = (PDRV01_RWBUFFER)pInBuf;
   
            if ( pBuffer->dwOffset < DRIVER_WORK_SIZE ){    // Data offset check
                *
pBuffer->pData = *((LPBYTE)pHWHead->pWorkBase + pBuffer->dwOffset);
            }
            else{
                RetVal = FALSE;
            }
        }
        break;
    case CMD_WRITE_1BYTE:
        {
            PDRV01_RWBUFFER   
pBuffer = (PDRV01_RWBUFFER)pInBuf;

            if ( pBuffer->dwOffset < DRIVER_WORK_SIZE ){    // Data offset check
                *((LPBYTE)
pHWHead->pWorkBase + pBuffer->dwOffset) = *pBuffer->pData;
            }
            else{
                RetVal = FALSE;
            }
        }
        break;

次にアプリケーションの呼び出しの部分を見てみます。ここではドライバに渡すための引数として、Drv01Buffer変数を定義し、そのメンバ変数としてのポインタDrv01Buffer.pData&nBufferをセットしています。したがってドライバDriver01.dllにはこの&nBuffer、すなわちユーザープロセス空間に確保されたスタックポインタが渡されることになります。

【WinMain関数:ドライバの呼び出し】

    DRV01_RWBUFFER Drv01Buffer;
    BYTE    nBuffer;

    ...

    // Driver Driver01 open
    hDrv01 = CreateFile (
                TEXT("DRV1:"),
                GENERIC_READ | GENERIC_WRITE,
                0,
                NULL,
                OPEN_EXISTING,
                FILE_ATTRIBUTE_NORMAL,
                NULL
                );
    if ( !hDrv01 ) {                    // don't open
        RETAILMSG (1, (TEXT("Driver01 open function is failed.\r\n")));
        return 0;
    }

    // I will set data for Driver01 work area.
   
Drv01Buffer.pData = &nBuffer;

    for ( i = 0; i < 32 ; i++ ){
        Drv01Buffer.dwOffset = i;
        *Drv01Buffer.pData = (BYTE)(i & 0xff);
        DeviceIoControl(
            hDrv01,
            CMD_WRITE_1BYTE,
            (LPVOID)
&Drv01Buffer,
            sizeof(DRV01_RWBUFFER),
            NULL,
            0,
            &dwReturnValue,
            NULL
            );
    }
    ...

実際にこのアプリケーションを実行してみましょう。“Target:Run Programs...”メニューからDrv01TestApp.exeを選択して実行します。その際にDrv01TestApp.cのDeviceIOControl呼び出しと、Driver01.cのCMD_WRITE_1BYTEでブレークポイントを設定する事を忘れないでください。

【Drv01TestApp.c:WinMain関数】

        DeviceIoControl(
            hDrv01,
            CMD_WRITE_1BYTE,
            (LPVOID)&Drv01Buffer,
            sizeof(DRV01_RWBUFFER),
            NULL,
            0,
            &dwReturnValue,
            NULL
            );
 ← ブレークポイントを設定

【Driver01.c:DRV_IOControl関数】

    case CMD_WRITE_1BYTE:
        {
            PDRV01_RWBUFFER   
pBuffer = (PDRV01_RWBUFFER)pInBuf; ← ブレークポイントを設定

実行すると最初にDeviceIOControl呼び出しでブレークがかかります。ここで引数として渡すローカル変数Drv01Bufferの値を確認します。Drv01Buffer.pDataの値はこの例では0x1801fcd4ということで、このユーザープロセスの空間を指していることがわかります。このアドレスはDrv01TestApp.exe自身のプロセス空間ですから、当然そのままでアクセスができます。

070602_4

引数を確認したらF5を押して再実行してください。次にDriver01.cのCMD_WRITE_1BYTEでブレークします。そしてF10キーを押してもう1Step実行します。。この時点でローカル変数pBufferに引数のポインタがセットされます。ここでpBuffer->pDataを確認するとユーザー空間のアドレスが入っていることがわかります。この値はテストアプリケーションDrv01TestApp.exeが渡した値そのものの0x1801fcd4がセットされています。

070602_2

ここで疑問が生じます。それはユーザーモードのプロセスは、最大で32K個のプロセスが同じ仮想アドレス空間を持っているということです。ということであれば、このKMドライバがこの“0x1801fcd4”をアクセスしようとしてもどのプロセス空間かわからないのではないか?という疑問が沸いてくるのです。確かに適当なプロセス空間をアクセスしても、呼び出したプロセスのポインタで無いとアクセス例外が出るか、たまたまアロケートされていたアドレスのデータが読み出されるはずです。

そこでCall Stack(呼び出し履歴)を見てみることにします。するとこの呼び出されたKMドライバは呼び出したプロセス(Drv01TestApp.exe)からそのまま呼び出されていることがわかります。すなわち呼び出されたKMドライバは、呼び出したユーザー空間のプロセスを特定することができているのです。

070602_3

イメージとして捕らえると下図のようになります。KMドライバは呼び出したプロセスと同一のコンテキストで、リニアにつながった仮想アドレスとして動作しているようです。

070602_5

ただこのあたりはもう少し検証が必要で、同一のドライバを異なるプロセスが同時に呼び出した場合、そのユーザー空間のアドレスはどうなるのかという疑問が残っています。

【追記】

気になったので異なるプロセスから同一のKMドライバを呼び出した場合、そのユーザープロセスの空間がどう見えるかを試してみました。すると呼び出されたDriver01.dllからはそれぞれ呼び出したプロセス空間をそのままアクセスすることができます。そして呼び出したプロセス以外の空間はアクセスすることができません。したがってKMドライバは呼び出したプロセスのコンテキストとして動作しているようで、ユーザー空間の保護が働いています。

ただしカーネル空間のヒープとしてアロケートされるKMドライバのワークエリアは、ユーザー空間とは関係無く、カーネルモードとして管理されます。すなわちそのワークエリアやマッピングされたレジスタの排他制御は、KMドライバ自身でしっかりと行わないとデータの破壊が起きるので注意が必要です。これはWinCE5.0以前も、WinCE6.0も同じです。

続く...

WinCE6.0ポインタの受け渡し - その1

さて先日 予告した通り、WinCE6.0のちょっとディープな話の連載を始めます。気力が持って、最後まで続けることができるかは怪しいのですが、今回はサンプルソースはすでにできていますので何とかなるでしょう。ということで第一回目です。

さて一番最初にWinCE6.0のメモリ空間のおさらいです。下はWinCE6.0で開発を始めようという方が一番最初の頃にお目にかかる図です。WinCE5.0からWinCE6.0への移行で、大きな違いの一つとしてこのメモリ空間の違いが挙げられます。WinCE5.0までのプロセスあたり32MBの仮想空間、最大で32プロセスまでという制限がありました。WinCE6.0ではプロセスあたり2GBのプロセス空間、最大で32Kプロセスまでというように、事実上その制限が無くなったということが大きな違いです。(なおアクセスできる物理空間はWinCE6.0もWinCE5.0までと同様29bitです。すなわち最大で512MBの空間しかアクセスすることはできません。)

070527_02

このようなアーキテクチャの下では、ユーザープロセス空間は0x0000 0000~0x7fff ffff、カーネルプロセス空間は0x8000 00000~0xffff ffffのそれぞれ2GBの空間が与えられます。従って全てのユーザープロセス空間は0x0000 0000~0x7fff ffffという仮想アドレスが与えられることになるのです。

それぞれのプロセスで確保した仮想空間は保護されていて、仮に異なるプロセスで同じ値を持つ仮想アドレスにアクセスしてもそれぞれの空間は保護されています。これは当然プロセスモデルのOSとして、それぞれのプロセス空間を保護するという視点ではセキュアレベルを保つために必要なことです。

070527_03

ところがこのProcess Bがディスクアクセスを行うようなUMドライバの場合、このプロセス空間の保護というのは必ずしもありがたい事ではありません。ディスクアクセスを行った場合、ポインタ渡しでRead/Writeのデータを、それも大量にやり取りすることが普通だからです。UMドライバはUdevice.exeでホスティングされるユーザープロセスの一つとして動作します。したがって下図のようにUdevice.exeがProcess AのHeapをアクセスしようとしても、そのままではWinCE6.0のプロセス保護によってアクセスすることができません。

070527_04

今回の連載では何回かに分けて、UMドライバが他のプロセスのHeapをどのようにアクセスするのかという事を解説していきます。WinCE6.0では新たにEmbedded Pointerという概念が取り入れられ、このようなアクセスを可能にしています。次回はUMドライバの話の前に、KMドライバではどのようにユーザーモードのプロセス空間をアクセスしているのかについて記事を投稿したいと思います。

続く(はず)

予告:WinCE6.0ドライバのちょっとディープな話

ブログの更新が止まって二週間になりました。ESECやセミナー、MEDCの準備とかが重なり合ってスケジュールがひどい事になっています。6/3(日)には演奏会もあり、こちらのプログラム作りや司会原稿作りも入っています(練習しなくていいのかという突っ込みは無しね)。

ということで予告編ですが、この状態がちょっと落ち着いたらWinCE6.0のドライバに関するちょっとだけディープな話を書きたいと考えています。おそらく連載物になりますので気が向いたら見に来てください。できれば今週末のスタートが目標かなぁ...

WinCE6.0 UMドライバのレジストリ

WinCE6.0のユーザーモードドライバ(UMドライバ)の実装について、以前ブログの記事を書いたことがあります。その中でUMドライバとして登録するために、追加するレジストリに関して以下の記述があります。この記述は間違いではないのですが、不足している情報があることに気がつきましたので追記します。

[HKEY_LOCAL_MACHINE\Drivers\BuiltIn\Driver01]
    "Flags"=dword:12
    "UserProcGroup"=dword:3

このレジストリの値「Flags」ですが、ここでは0x12を指定することになっています。この0x12はDEVFLAGS_LOADLIBRARY(=0x02)とDEVFLAGS_LOAD_AS_USERPROC(=0x10)のフラグの論理和となっています。実際にUMドライバとして組み込む場合に必要な指定はDEVFLAGS_LOAD_AS_USERPROCです。したがってレジストリとしては以下の設定でもUMドライバとしてロードされることになります。

[HKEY_LOCAL_MACHINE\Drivers\BuiltIn\Driver01]
    "Flags"=dword:
10   ; DEVFLAGS_LOAD_AS_USERPROC
    "UserProcGroup"=dword:3

以前の値でももちろん間違いではないのですが、UMドライバとしてはこちらの指定のほうが適切かと思います。ヘルプの記述もこちらでした。

ドライバのカタログへの登録(Platform Builder 6.0)

この二週間はむちゃくちゃ忙しくて、ブログに書き込む暇がぜんぜんありませんでした。こんなに忙しいと結構まずいなぁと思いながら、メールの山と格闘しています。

さて今回は作成したデバイスドラバをカタログに追加する方法について説明をします。使用するドライバは相変わらずのDriver01です。ただしPlatformディレクトリ以下にドライバを追加しますので、以前とは異なる方法で組み込むことになります。まずは新規プロジェクトを作成して新しいOSイメージを構築してみます。ただし今回の解説では、BSP名を“TrainingBSP”、プロジェクト名を“MyOSDesign”とします。前回のClone BSPの作成ではBSP名をTrainingBSP01としましたので、このBSP名を読み替えてください。

1. 新規サブプロジェクトの追加

最初にドライバを新規サブプロジェクトとして追加しますのでソリューションエクスプローラでフォルダ“C:\WINCE600\PLATFORM\TrainingBSP01\SRC\DRIVERS”を開きます。“driver”を右クリックし、“Add:New Sources Project...”をクリックします。

070421_01

Windows Embedded CE Subproject Wizardが開きますので、Available templatesとして“WCE Dynamic-Link Library”を選択し、Subproject nameをDriver01と入力し“Next”をクリックします。

070421_02

次のダイアログでは“An empty subproject”を選んで“Next”をクリックします。

070421_03

最後に“Add to the current Dirs file.”を選択し“Finish”をクリックします。

070421_04

するとソリューションエクスプローラに新たにdriver01というディレクトリが追加されたことを見ることができます。またdriversをダブルクリックするとdrivers\dirsファイルが開きます。最後の行にDriver01が追加されていることを確認します。

※この時、オリジナルのdirsファイル中の“DIRS=\”行以下が単純にディレクトリツリーで変更されてしまうようです。そのため、Device Emulatorのクローンを使用している今回の例では以降のビルドでエラーとなってしまいます。オリジナルの“C:\WINCE600\PLATFORM\DEVICEEMULATOR\SRC\DRIVERS\dirs”ファイルをコピーして、最後の行にDriver01を追加してください。

070421_05

2. ソースコードの追加

さてプロジェクトとしてドライバは追加されましたが、Emptyプロジェクトなので、ソースコードを記述することが必要です。そこで先ほどダウンロードしたファイルを使用することにします。

Windowsエクスプローラで、先ほどダウンロードしたファイルのうち以下のファイルを“C:\WINCE600\PLATFORM\TrainingBSP01\SRC\DRIVERS\Driver01”にコピーします。

  • Driver01.c
  • Driver01.h
  • Driver01.def

このDriver01のsourcesファイルを編集するために、ソリューションエクスプローラでDriver01ディレクトリをダブルクリックします。Driver01\sourcesファイルが開きますのでSOURCESとして以下のように“Driver01.c”を“Driver01.h”を追加してください。

FILE_VIEW_INCLUDES_FOLDER= \
    Driver01.h \

SOURCES= \
    Driver01.c \

そしてBIBファイルとレジストリファイルを修正します。今回はカタログに組み込むので、Platfromディレクトリのファイルに追加します。ソリューションエクスプローラで“C:\WINCE600\PLATFORM\TrainingBSP01\Parameter Files”を開きます。最初にplatform.bibをオープンしMODULESセクションの最後に次のコードを記述します。

IF BSP_DRIVER01
    driver01.dll         $(_FLATRELEASEDIR)\driver01.dll          NK SHK
ENDIF BSP_DRIVER01

次にレジストリを修正するためplatform.regをオープンし、ファイルの最後に以下のレジストリを追加します。レジストリの修正では、デフォルトでRegEditが開きます。どちらでも構わないのですが、RegEditではなくSourceでの編集のほうが使いやすいと思います。

IF BSP_DRIVER01
[HKEY_LOCAL_MACHINE\Drivers\BuiltIn\Driver01]
   "Dll"="Driver01.dll"
   "Prefix"="DRV"
   "Index"=dword:1
   "Order"=dword:1
ENDIF ; BSP_DRIVER01

3. カタログへの追加

VS2005のメニューから“File:Open:File...”を選択し“C:\WinCE600\Platform\TrainingBSP\CATALOG”フォルダに移動します。ファイルタイプを“All Files (*.*)”とし、TrainingBSP.bpcxmlファイルを開きます。ノードを開き、Device Driversを右クリックし“Add Catalog Item”を選択します。

070421_06

追加したCatalog Itemのプロパティを以下のように変更して保存します。

  • Title:Driver01
  • Unique ID:Item:Vendor01:Driver01
  • Additional Variables:BSP_DRIVER01
  • Modules:driver01.dll

これでカタログアイテムへの追加はできました。Catalog Items Viewで“Thard Party:BSP:TrainingBSP: ARMV4I:Device Drivers”を展開してみてください。Driver01が選択できるようになっているはずです。もし見つからない場合は、VS2005をいったん終了して再起動すると大丈夫です。

070421_07

4. ビルドと動作の確認

追加したDriver01を組み込んだOSイメージを作成するためには、Catalog Item ViewでDriver01にチェックを入れてイメージに追加します。この作業は他のコンポーネントと同様です。そしていつものように“Build:Advanced Build Commands:Build Current BSP and Subprojects”を選択してOSイメージのビルドを行います。そして無事にビルドが終了したら“Target:Attach Device”を選択し、エミュレータを起動してください。

OSイメージが起動したら追加したドライバが動作しているかを確認します。“Target:Remote Tools:Process Viewer”を選択し、プロセスビューアを起動します。このDriver01.dllはカーネルモードドライバですので、nk.exeにホスティングされているドライバとなります。

070421_08

実際にロードされたDriver01.dllのベースアドレスは0xc1290000とカーネル空間にロードされていることがわかります。これでデバイスドライバのカタログへの追加の話は終了です。