IOポートを操作するデバイスドライバ(64bit)を作成しBEEP音を制御する

icon 項目のみ表示/展開表示の切り替え

概要

IOポートの操作する命令は特権命令なのでカーネルモードでしか動作しません。
IOポートを操作する64bitデバイスドライバ(beeptest64.sys)を作成し、別のプログラム(beeptestctl.exe)からデバイスドライバを制御しBEEP音(屋台のラーメン屋でおなじみのチャルメラ音)を発生させます。
BEEP音はヘッドホンを使用してもパソコンのスピーカーから音が出る機種がありますので、テストする環境には気を付けてください。
IOポートを操作するには、コンパイラの内部関数またはアセンブラを使用する必要があります。WDK7ではコンパイラの内部関数が使えず、64bitではインラインアセンブラが使えないのでアセンブラ部分をio64.asmに格納しています。
使用するには、まずデバイスドライバをインストールし、その後beeptestctl.exeを実行します。
64bitのデバイスドライバはWindowsを通常に起動した場合、証明機関の証明書で署名していない場合は、インストールできません。
詳細は、本文で触れていますが、証明機関(有料)の証明書を使用しない場合、ブート時にドライバー署名の強制を無効にするかテストモードで起動する必要があります。
チャルメラの楽譜を以下に示します。

なお、純粋にビープ音を発生させるのが目的であれば、Beep APIを使用すれば簡単に済みます。
本ページは、IOポート操作の例として標準的なIOであり動作確認が容易なビープ音を選択しました。
Windows 7等のBeep APIでは、本ページの様にタイマーICを操作するのではなく、サウンドカードによってビープ音を発生させるように変更されています。(音量調整ができる)

テスト環境

コンパイラ

ドライバの開発 WDK7
ドライバを制御するソフトの開発 Visual C++ 2013

実行環境

Windows 7 Enterprise 64bit
Windows 8.1 Enterprise 64bit

ドライバソースファイル等のフォルダー構成


d:¥beeptest64
│  
└─beeptest64
    │  beeptest64.cpp
    │  makefile
    │  sources
    │  
    └─objfre_win7_amd64
        └─amd64
                io64.asm

ドライバ開発環境の構築とビルドの方法

デバイスドライバの開発にWDK7を使用します。以下のサイトよりWDK 7.1.0をダウンロードし、インストールします。
Windows Driver Kit (WDK)https://msdn.microsoft.com/ja-jp/library/windows/hardware/ff557573(v=vs.85).aspx
WDK7にはコンパイラも含まれています。(Visual C++ 2008の英語版相当)ただし、コマンドラインでしか使用できません。
文法チェックやインテリセンス機能を使用したいので、Visual Studioでソースコードの編集を行いWDK7でデバイスドライバを開発することとします。

Visual Studio C++でプロジェクトを作成

Visual Studioで新規プロジェクトを作成を開始し、テンプレートはメイクファイルプロジェクトを選択し後は適当に操作して完了させます。
ここではd:¥ドライブにbeeptest64プロジェクトを作成しました。
プロジェクトのプロパティを表示し、インクルードの検索パスにDDKのインクルードファイルが格納されているフォルダー名を指定します。(ディフォルトではC:¥WinDDK¥7600.16385.1¥inc¥ddk)
これによりVisual Studioのエディタ上で関数名の色が変わったり、インテリセンスが使えるようになり便利です。

makefileとsourcesファイルの作成

上記のファイルを作成します。保存先は、d:¥beeptest64¥beeptest64フォルダとします。本プログラムに必要なファイルは本ページの最後のダウンロードのリンク先に含まれています。
makefileとsourcesファイルには拡張子はついていないので注意してください。

makefile

!INCLUDE $(NTMAKEENV)¥makefile.def

sources

TARGETNAMEで作成するデバイスドライバのファイル名を指定します。本例ではbeeptest64.sysとなります。
SOURCESでソースファイル名を指定します。複数のソースファイルがある場合はスペースで区切ります。
TARGETNAME=beeptest64
TARGETTYPE=DRIVER
TARGETPATH=obj
SOURCES=beeptest64.cpp
AMD64_SOURCES=io64.asm

WDK7によるデバイスドライバの作成

スタートメニューからWindows Driver Kits・WDK 7600.16385.1・Build Environments・Windows 7¥x64 Free Build Environmentを実行します。
メニューで選択するOSは開発しているパソコンのOSではなく、ドライバを実行するOSです。XPを選択すればXP,Vista,7で使用可能です。
コマンドプロンプトのウィンドウが開くので、CDコマンドを使用してソースファイルが存在するフォルダーに移動します。
buildコマンドでコンパイルできます。
cd d:¥beeptest64¥beeptest64
build
デバイスドライバは、d:¥beeptest64¥beeptest64¥objfre_win7_amd64¥amd64 に作成されます。

デバイスドライバのインストール

署名のない64bitドライバは、Windowsを普通に起動するとインストールできません。
署名を回避する方法については、署名がないドライバをインストールする方法を参照してください。
ドライバのインストールは、ドライバのインストール/アンインストールするプログラムを参照指定ください。

ドライバ ソースの概要

beeptest64.cpp

各関数名が勝手に修飾されないように、extern "C"で定義します。
インクルードファイルconio.hはIOポートを操作する関数の定義のために使用しています。
各関数は、コンパイル時にOACRにより警告が出ないように独特の型でプロトタイプ宣言を行います。
#pragma alloc_textにより各関数をメモリ上の扱いを定義します。この定義を行った場合、各関数の入口にPAGED_CODE();を記述する必要があります。
INITを指定した場合、ページアウトしません。PAGEをした場合、ページアウト可能です。
in/out命令を実行するためにアセンブラの関数inp_asmとoutp_asmをプロトタイプ宣言しています。
32bitとソースコードを共用しやすいようにマクロで_inpと_outpをそれぞれ上記の関数に置き換えるように記述しています。

DriverEntry関数

デバイスドライバが起動されたときの1度だけ実行される関数です。
デバイスドライバの名称の定義と、 デバイスドライバを使用するプログラムで実行されるCreateFile関数・CloseFile関数・DeviceIoControl関数に対する処理先等を定義します。
以後、使用する側から呼び出される場合は、定義した処理先が呼び出されます。

DeviceControlFunction関数

DriverEntry関数で登録されたデバイスドライバを使用する上のDeviceIoControl関数から呼び出されるプログラムで実行される関数である。
直接呼び出されるのではなく、カーネルがバッファ処理等を行ってから呼び出されます。
引数より入力・出力バッファのアドレス・サイズ等が含まれる構造体をIoGetCurrentIrpStackLocation関数により呼び出しますす。
アクセスIDを調べ該当する処理に振り分けます。本ドライバはIOCTL_BEEP_CODEのみサポートしています。
入力バッファより、BEEPのカウンタ値とスピーカのon/offの指定値を取得します。
入力バッファの0~3バイトがカウンタ値(下位2バイトのみ有効)、4~7バイトがスピーカの制御値です。
カウント値(16bit)により分周されます。
スピーカー制御値が0以外の時はBEEP音を連続発生、0の時はBEEP音を停止させます。
実態は、IOポートをinp_asm・outp_asm関数により8254の読み書きします。
IoCompleteRequestにより処理したコマンドを消去します。
IO操作の概要
実際に出力されるBEEP音の基本周波数は、1193180/カウント値で決まります。
1193180はPC/AT互換機に使われているタイマーICの8254(チップセット等に互換品が集積されている)のCLK2への入力周波数であり、CLK2の立下り時に2ずつ減算されます。カウンタが0になるとカウンタに初期値がロードされます。
スピーカはカウント2を使用しており、モード3に設定しています。
OUT2端子はカウンタが0になるたびにLow、hiが交互に出力されます。カウンタ値が偶数の場合はLow、Hiとも同間隔、奇数の場合はHiがクロックの1個分増えます。
GATE2端子がHiの時、CLK2が有効になります。
GATE2端子がLowからHiになるとカウントの初期値がロードされます。
GATE2端子は出力ポート(0x61)のビット0に割り当てられているので、出力ポートによりカウンタを停止させることができます。
スピーカは、OUT2端子からANDゲートを経由して接続されています。ANDゲートのもう一方の入力は出力ポート(0x61)のビット1に接続されています。
出力ポート(0x61)のビット1をLowにするとスピーカへの出力を停止させることができます。
SVGの代替画像 GATE1 GATE1 GATE1 CLK0 CLK1 CLK2 OUT0 OUT1 OUT2 IRQ DRAM リフレッシュ スピーカーへ 8254 (0x61) PORTB XD0 XD1 XD5 1193180Hz Vcc

UnloadDriverFunction関数

ドライバがアンロードされるときに呼び出されます。
IoDeleteDevice関数によりドライバを消去します。

io64.asm

inp_asm

in命令を実行し結果を返します。
あらかじめeaxレジスタをxor命令でクリアします。
第一引数はecxレジスタに保存されているので、edxレジスタにコピーします。
in命令はdxレジスタで示されるIOポートから1byte読み込みalレジスタに結果を格納します。
戻り値はeaxレジスタで返されます。

outp_asm

outp命令を実行します。
第一引数はecxレジスタに保存されています。
第二引数はedxレジスタに保存されているのでeaxレジスタに保存します。
out命令はcxで示されるIOポートにalレジスタの内容を書き込みます。

ドライバ(beeptest64.sys) ソースコード

beeptest64.cpp


//      WDMドライバサンプル(周波数を指定してBeep音を鳴らす/止める)
//      Windows 7以降の64bit OS限定

#ifdef __cplusplus
        extern "C" {
#endif

#include <wdm.h>
#include <conio.h>

#define IOCTL_BEEP_CODE 0x800

//      エントリーポイント
DRIVER_INITIALIZE DriverEntry;

__drv_dispatchType(IRP_MJ_MAXIMUM_FUNCTION) DRIVER_DISPATCH  UnSupportedFunction;

//      Create
__drv_dispatchType(IRP_MJ_CREATE) DRIVER_DISPATCH CreateFunction;
NTSTATUS CreateFunction(IN PDEVICE_OBJECT DeviceObject,IN PIRP Irp);

//      Close
__drv_dispatchType(IRP_MJ_CLOSE) DRIVER_DISPATCH CloseFunction;
NTSTATUS CloseFunction(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp);

//      DeviceControl
__drv_dispatchType(IRP_MJ_DEVICE_CONTROL) DRIVER_DISPATCH DeviceControlFunction;
NTSTATUS DeviceControlFunction(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp);

//      AddDevice
NTSTATUS AddDeviceFunction(IN PDEVICE_OBJECT DriverObject, IN PDEVICE_OBJECT DeviceObjcet);

//      UnloadDriver
DRIVER_UNLOAD UnloadDriverFunction;

#ifdef ALLOC_PRAGMA
#pragma alloc_text( INIT, DriverEntry )
#pragma alloc_text( PAGE, CreateFunction)
#pragma alloc_text( PAGE, CloseFunction)
#pragma alloc_text( PAGE, DeviceControlFunction)
#pragma alloc_text( PAGE, AddDeviceFunction)
#pragma alloc_text( PAGE, UnloadDriverFunction)
#endif

#ifdef __cplusplus
        }
#endif


extern "C" {
#ifdef _WIN64
        DWORD64 inp_asm(DWORD64 ads);
        void outp_asm(DWORD64 ads, DWORD64 data);
#endif
}

#ifdef _WIN64
        #define _inp(ads) inp_asm(ads)
        #define _outp(ads,data) outp_asm(ads,data)
#endif
//      ドライバーがロードされたときに呼び出される関数
NTSTATUS DriverEntry(IN PDRIVER_OBJECT pDriverObject,IN PUNICODE_STRING pRegistryPath){
        UNICODE_STRING usDeviceName,usDosDeviceName;
        PAGED_CODE();
        RtlInitUnicodeString( &usDeviceName, L"¥¥Device¥¥beeptest" );
        RtlInitUnicodeString(&usDosDeviceName, L"¥¥DosDevices¥¥beeptest");

        PDEVICE_OBJECT pDeviceObject = NULL;
        NTSTATUS NtStatus=IoCreateDevice( pDriverObject, 0, &usDeviceName, FILE_DEVICE_SECURE_OPEN, 0, FALSE, &pDeviceObject);
        if( NtStatus != STATUS_SUCCESS){
                return NtStatus;
        }
        pDriverObject->MajorFunction[IRP_MJ_CREATE]=CreateFunction;  //      CreateFile呼び出し時の処理を登録
        pDriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = DeviceControlFunction; //      DeviceControl呼び出し時の処理を登録
        pDriverObject->MajorFunction[IRP_MJ_CLOSE]=CloseFunction;    //      CloseFile呼び出し時の処理を登録
        
        pDriverObject->DriverUnload = UnloadDriverFunction;  //      ドライバがアンロードされる時の処理を登録

        NTSTATUS status = IoCreateSymbolicLink(&usDosDeviceName, &usDeviceName);

        return STATUS_SUCCESS;
}

//      Create
NTSTATUS CreateFunction(IN PDEVICE_OBJECT DeviceObject,IN PIRP Irp){
        PAGED_CODE();
        return STATUS_SUCCESS;
}

//      Close
NTSTATUS CloseFunction(IN PDEVICE_OBJECT DeviceObject,IN PIRP Irp){
        PAGED_CODE();
        return STATUS_SUCCESS;
}

//      DeviceControl   周波数,on/off
NTSTATUS DeviceControlFunction(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp){
        PAGED_CODE();
        int dwDataRead = 0, dwDataWritten = 0;
        NTSTATUS NtStatus = STATUS_NOT_SUPPORTED;
        PIO_STACK_LOCATION pIoStackIrp = IoGetCurrentIrpStackLocation(Irp);
        if (pIoStackIrp){
                // アクセスIDを調べる
                if (pIoStackIrp->Parameters.DeviceIoControl.IoControlCode == IOCTL_BEEP_CODE){
                        // バッファポインタ取得
                        PCHAR pInputBuffer = (PCHAR)Irp->AssociatedIrp.SystemBuffer;
                        PCHAR pOutputBuffer = (PCHAR)Irp->AssociatedIrp.SystemBuffer;
                        // バッファサイズ取得
                        int InSize = pIoStackIrp->Parameters.DeviceIoControl.InputBufferLength;
                        int OutSize = pIoStackIrp->Parameters.DeviceIoControl.OutputBufferLength;
                        DWORD32* dwp = (DWORD32*)pInputBuffer;
                        DWORD32 count = dwp[0]; //      カウント値
                        DWORD32 flag = dwp[1];  //      スピーカーon/off
                        if (flag){      //      bepp音を発生
                                _outp(0x43, 0xb6);
                                _outp(0x42, count & 0xff);
                                _outp(0x42, count / 256);
                                _outp(0x61, _inp(0x61) | 0x3);
                        }
                        else{   //      bepp音を停止
                                _outp(0x61, _inp(0x61) & 0xfc);
                        }
                        NtStatus = STATUS_SUCCESS;
                }
        }
        Irp->IoStatus.Status = NtStatus;
        Irp->IoStatus.Information = dwDataWritten;
        IoCompleteRequest(Irp, IO_NO_INCREMENT);
        return STATUS_SUCCESS;
}

//      AddDevice
NTSTATUS AddDeviceFunction(IN PDEVICE_OBJECT DriverObject, IN PDEVICE_OBJECT DeviceObjcet){
        PAGED_CODE();
        return STATUS_SUCCESS;
}

//      UnloadDriver
VOID UnloadDriverFunction(IN PDRIVER_OBJECT DriverObject){
        PAGED_CODE();
        IoDeleteDevice(DriverObject->DeviceObject);
}

io64.asm


        
        .code

        public  inp_asm
        public  outp_asm

inp_asm         PROC
        xor     eax,eax
        mov     edx,ecx
        in      al,dx
        ret
inp_asm ENDP

outp_asm        PROC
        mov     eax,edx
        mov     edx,ecx
        out     dx,al
        ret
outp_asm ENDP


        END

制御ソフト ソースの概要

beeptestctl.cpp

WinMain関数

最初に呼び出される関数です。
CreateFile関数によりデバイスドライバをオープンします。
デバイスドライバが見つからない場合は、エラーメッセージを出力しプログラムを終了します。
周波数と音の長さを指定して、TestBeep 関数を呼び出しビープ音を発生させます。
CloseHandle関数を呼び出してデバイスドライバをクローズします。
オクターブ4の各音階の周波数は以下のとおりです。
オクターブを1つ上げる場合は周波数を倍に、下げる場合は、周波数を半分にします。
音階 ラ# ド# レ# ファ ファ# ソ#
周波数(Hz) 220 233 247 262 277 293 311 330 349 370 392 415 440

TestBeep関数

第1引数が周波数、第2引数がビープ音の発生時間です。時間の単位は千分の1秒です。
DeviceIoControl経由でデバイスドライバのDeviceControlFunction関数を呼び出し、ビープ音のスタートさせSleep関数で待機させた後、ビープ音を止めます。 1オクターブの中に12個の音階がありますが、以下の式の様に等倍となっています。

上式はオクターブ4を示している。Nは0~11で音階を示している。

#include <windows.h>
#include <tchar.h>

#define IOCTL_BEEP_CODE 0x800


void TestBeep(HANDLE h, int f, int w){
        DWORD dwReturn;
        DWORD buf[2];
        buf[0] = 1193180 / f;
        buf[1] = 0xffffffff;
        DeviceIoControl(h, IOCTL_BEEP_CODE, buf, sizeof(buf),
                buf, sizeof(buf), &dwReturn, NULL);

        Sleep(w);

        buf[1] = 0;
        DeviceIoControl(h, IOCTL_BEEP_CODE, buf, sizeof(buf),
                buf, sizeof(buf), &dwReturn, NULL);
}


int WINAPI WinMain(HINSTANCE hCurInst, HINSTANCE hPrevInst, LPSTR lpsCmdLine, int nCmdShow){

        HANDLE hFile;
        hFile = CreateFile(_TEXT("¥¥¥¥.¥¥beeptest"),
                GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
        if (hFile == INVALID_HANDLE_VALUE){
                MessageBox(0,_TEXT("CreateFileが失敗しました"),_TEXT("エラー"),MB_OK);
                return 1;
        }
//      チャルメラ
        TestBeep(hFile, 440, 143);      //      ラ
        TestBeep(hFile, 494, 143);      //      シ
        TestBeep(hFile, 277 * 2, 286 * 2);      //      ド#
        TestBeep(hFile, 494, 143);      //      シ
        TestBeep(hFile, 440, 143);      //      ラ
        Sleep(286 * 4);

        TestBeep(hFile, 440, 143 );     //      ラ
        TestBeep(hFile, 494, 143 );     //      シ
        TestBeep(hFile, 277 * 2, 143 ); //      ド#
        TestBeep(hFile, 494, 143 );     //      シ

        TestBeep(hFile, 440, 143);      //      ラ
        TestBeep(hFile, 494, 246);      //      シ

        CloseHandle(hFile);

        return 0;
}

実行ファイルとソースファイルのダウンロード

実行ファイルとソースファイルのダウンロード(beeptest64.zip)
beeptest64.zipの内容は以下のとおりです。
beeptest64.sysデバイスドライバは署名されておりません。
本文の署名されていないドライバをインストールする方法を参照して実行してください。
    beeptest64
    ├─beeptest64
    │  └─beeptest64
    │      │  beeptest64.cpp
    │      │  makefile
    │      │  sources
    │      │  
    │      └─objfre_win7_amd64
    │          └─amd64
    │                  beeptest64.sys
    │                  io64.asm
    │                  
    └─beeptestctl
            beeptestctl.cpp
            beeptestctl.exe
            beeptestctl64.exe