2.9K Views
June 17, 22
スライド概要
次のイベントの講演資料です。
https://sciencepark.connpass.com/event/246432/
サイエンスパークの勉強会の資料を公開します。勉強会は2022/3現在、connpassで公開しています。 https://sciencepark.connpass.com
WDDDS2022 Windows/Mac対応 デバイス向けドライバ開発 中級編 ~ クラスを使ったソースの共有~ 2022/6/17 デバイスドライバ課 大島 光人 © SCIENCEPARK CORPORATION. 1 SP2206ーE21
WDDDS 2022 自己紹介 名前 :大島 光人(オオシマ アキト) 所属部署:プラットフォーム部 デバイスドライバ課 業務内容: ・Windows・Macドライバ開発 ・Windowsアプリの開発 ・Macアプリの開発
WDDDS2022 目次 1. はじめに 2. 開発環境 3. C++によるWindowsドライバ開発 4. C++によるWindows/Macドライバ開発 3
WDDDS2022 1. はじめに
WDDDS2022 1. はじめに • デバイスドライバ開発者の育成が目的 • 安価にドライバを学習できる環境を紹介 Arduinoを利用 • マルチプラットフォーム対応の開発手法の紹介 5
WDDDS2022 1.2 対象デバイス デバイスは「Arduino Uno Rev3」を使用します。 Arduino Uno Rev3はCDCクラスの標準ドライバに対応しますが、ドライバ 開発を目的とするため、vendor-specificとみなして開発を行います。
WDDDS2022 2. 開発環境
WDDDS2022 2.1 開発対象ソフトウェア 開発対象ソフトウェア • Arduino用のドライバ • デバイス制御アプリ 実装機能 • LED点灯情報を複数保持 • Arduinoのボタンを押すと点灯情報を切り替える アプリ ドライバ 8
WDDDS2022 2.2 開発環境 項目 Windows Mac OS • Windows 10 21H2 • macOS Monterey(12) 開発環境(IDE) • Visual Studio 2019 • Windows 11 SDK • Windows 11 WDK • Xcode 13 フレームワーク • UMDF V2 • DriverKitフレームワーク 署名用アカウント※ • Microsoft Developer アカウント 登録 • Apple Developer Program登録(有 料) その他 • 署名には別途EV証明書が必要 (有料) ※ テスト環境ではテスト署名によってアカウント未登録でも動作可能 9
WDDDS2022 2.3 Windowsドライバ開発概要(1) 【開発モデル】 WDM • カーネル空間で動作 • 旧式の開発モデルでフル機能を実装する必要がある WDF • WDMの上で動作するフレームワーク • 定型的な処理はフレームワークが受け持ってくれるため、コード量が少なくて済む • カーネル空間で動作するKMDFとユーザー空間で動作するUMDFがある →開発コストや品質面ですべてWDFのほうがいい! 10
WDDDS2022 2.3 Windowsドライバ開発概要(2) 開発モデル 実行形式 説明 WDM カーネルドライバ 旧式の開発モデル。フル実装が必要なため、開発コスト が高い。 UMDF(WDF) ユーザードライバ PnPや電源管理などカスタマイズできない処理がある。 ユーザーモードであるため、開発・デバッグが容易。 KMDF(WDF) カーネルドライバ PnPや電源管理などUMDFではできない処理に対応可能。 BSODが発生しうるのでデバッグに時間がかかりがち。 WinUSB 汎用ドライバ+アプリ ケーション Microsoft社製の汎用のUSBドライバを利用。 単純なデバイスとの通信であれば気軽に開発・デバッグ が可能。 LibUSB 汎用ドライバ+アプリ ケーション Linuxなどでも使われるOSS。 マルチプラットフォーム開発が可能。 11
WDDDS2022 2.4 Macドライバ開発概要(1) カーネル拡張 (kext) • カーネル空間で動作 →問題が発生するとカーネルパニックが発生 • macOS 10.15以降は非推奨 • macOS 11から使用するIOKit毎にロード可否が異なる(USBなどはロード不可) SystemExtension / DriverKitフレームワーク (dext) • ユーザー空間で動作 → システムの安定性とセキュリティの向上、ユーザー空間からデバッグが可能 • Mac OS 10.15で導入 • 全てのIOKit(kext)に対応するわけではなく、macOSのバージョンアップに合わせて機能が追加 されている 12
WDDDS2022 2.4 Macドライバ開発概要(2) フレームワーク カーネル拡張(kext) System Extension / DriverKit OSバージョン 説明 macOS 10.x 10.14まではこの方式のみ 10.15以降は公証(Notarization)が必要 10.15.4以降は非推奨として警告が出る macOS 11 DriverKitに含まれる機能はロード不可 OSのセキュリティレベルを下げることで一部ロード可能 macOS 10.15以降 HIDDriverKit、NetworkingDriverKit、SerialDriverKit、USBDriverKit、 USBSerialDriverKit macOS 11 AudioDriverKit、BlockStorageDeviceDriverKit、PCIDriverKit、 SCSIControllerDriverKit 新しいフレームワークのDriverKitを使用した開発が必須に! 13
WDDDS2022 2.5 ハードウェア仕様 -Arduino Uno R3概要⚫ ATmega328PチップおよびATmega16u2チップ搭載。 ⚫ ATmega16u2 は標準ではUSB-シリアルとして認識し、ファームのカスタムが可能。 ⚫ ATmega16u2のファームをカスタムすることで他のUSBクラスでの使用が可能 → 本講演では標準のUSB-シリアルとして使用する。 ⚫ アナログ入力ピン:6個 ⚫ デジタル入出力ピン(3.3V):14個 →デジタル入出力ピンを使用してLEDの明るさを変える(点滅させる) ⚫ I2C、SPI、UARTに対応 14
WDDDS2022 2.6 ハードウェア仕様 - USBディスクリプタ -(1) Arduino Uno R3のハードウェア仕様をUSBViewを使って解析します。 【USBView】 ■デバイスディスクリプタ No 名称 値 概要 1 bDeviceClass 0x02 デバイス種別 2 idVendor 0x2341 ベンダーID 3 idProduct 0x0043 プロダクトID 4 bNumConfigurations 0x01 Configurationの数 ※ ベンダーID/プロダクトIDは互換デバイスに応じて異なる 15
WDDDS2022 2.6 ハードウェア仕様 - USBディスクリプタ - (2) 【USBView】 ■コンフィギュレーションディスクリプタ No 1 名称 bNumInterfaces 値 0x02 概要 インターフェース数 16
WDDDS2022 2.6 ハードウェア仕様 - USBディスクリプタ - (3) 【USBView】 ■インターフェースディスクリプタ(1) No 名称 値 概要 1 bInterfaceNumber 0x00 インターフェースの番号 2 bNumEndpoints 0x01 エンドポイントの数 3 bInterfaceClass 0x02 インターフェースの種別 ■エンドポイントディスクリプタ(1) No 名称 値 概要 1 bEndpointAdd ress 0x82 エンドポイントアドレス (Interrupt) 2 bmAttributes 0x03 通信方式 (Interrupt転送) 3 wMaxPacketSi 0x0008 ze パケット通信最大サイズ ※ インタラプト転送は本講座では使用しない 17
WDDDS2022 2.6 ハードウェア仕様 - USBディスクリプタ - (4) 【USBView】 ■インターフェースディスクリプタ(2) N o 名称 値 概要 1 bInterfaceNumber 0x01 インターフェースの番号 2 bNumEndpoints 0x02 エンドポイントの数 3 bInterfaceClass 0x0A インターフェースの種別 ■エンドポイントディスクリプタ_(3) ■エンドポイントディスクリプタ(2) No 名称 値 概要 No 名称 値 概要 1 bEndpointAddress 0x04 エンドポイントアドレス (Bulk OUT) 1 bEndpointAddress 0x83 エンドポイントアドレス (Bulk IN) 2 bmAttributes 0x02 通信方式(Bulk転送) 2 bmAttributes 0x02 通信方式(Bulk転送) 3 wMaxPacketSize 0x0040 パケット通信最大サイズ 3 wMaxPacketSize 0x0040 パケット通信最大サイズ 18
WDDDS2022 2.7 ハードウェア仕様 - USB通信仕様 本講座のドライバでは、LEDの明るさ(点滅間隔)を設定する機能を実装します。 そのため、初期化に使用するコントロール転送およびデータ送信をするために必 要となるバルク転送のみ使用します。 【コントロール転送】 No. リクエスト bmRequestType bRequest wValue wIndex wLength データ 1 SetLineCoding 0x21 0x20 0 0 7 {0x80, 0x25, 0x00, 0x00, 0x00, 0x00, 0x80} 2 SetControlLineState 0x21 0x22 0 0 0 - 19
WDDDS2022 2.7 ハードウェア仕様 - USB通信仕様 【バルク転送】 • • • • EndpointAddress 0x04のBulk Outをデバイスへのデータ送信に使用 EndpointAddress 0x83のBulk INをデバイスからのデータ受信に使用 バルク転送の開始は、デフォルトのUSBリクエストより実行する 送信データは先頭8バイトがコマンドヘッダ、後続にコマンドデータ コマンドヘッダ (8byte) バルク転送データ フォーマット Signature (4byte) Length (1byte) CmdId (1byte) コマンドデータ (コマンドに準じたサイズ) Data Length (1byte) Reserved (1byte) TransferData (0~56byte) 20
WDDDS2022 3. C++によるWindowsドライバ開発
WDDDS2022 3.1 C言語によるWindowsドライバ開発の問題点 • コード量が肥大化する • スコープの先頭で変数宣言をする必要があるなど可読性が悪い • VisualStudioのインテリセンスがほぼ役に立たない 22
WDDDS2022 3.2 C++をWindowsドライバ開発に利用するメリット • オブジェクト指向ネイティブな実装が可能 • クラス単位でインテリセンスによる補完ができる! • コンストラクタにより初期化漏れを防げる • デストラクタにより解放処理の呼び忘れが防げる • 途中で変数宣言ができる → 処理途中のワーク変数などでautoを使いやすい 23
WDDDS2022 3.3 C++を使用する場合のテクニック ■概要 • カーネルモードはメモリ管理が重要。PoolTagやメモリタイプの指定を考慮する • WDMでも可能だが、KMDFを使用したほうが容易 • UMDFであれば、STLなどを使用することが可能 • WDKのファンクションテーブルやWDF系コールバックはグローバル変数またはク ラスの静的メンバにてクラスメソッドを呼び出す 24
WDDDS2022 3.3 C++を使用する場合のテクニック ■メモリ確保(カーネルモード) • newは標準では使用できないため、独自にoperator new/deleteを定義する • PoolTypeとPoolTagを使用できるoperatorを用意する void* __cdecl operator new(size_t size); void* __cdecl operator new(size_t size, POOL_TYPE poolType); void* __cdecl operator new(size_t size, POOL_TYPE poolType, ULONG tag); void* __cdecl operator new[](size_t size); void* __cdecl operator new[](size_t size, POOL_TYPE poolType); void* __cdecl operator new[](size_t size, POOL_TYPE poolType, ULONG tag); void __cdecl operator delete(void* object); void __cdecl operator delete(void* object, size_t size); void __cdecl operator delete[](void* object); void __cdecl operator delete[](void* pObject, size_t size); 25
WDDDS2022
3.3 C++を使用する場合のテクニック
■ファンクションテーブル/WDFコールバック
• IOManagerやWDFからのコールバックはC言語の構造であるため、ラッパーとなる
関数を定義し、その関数からクラスインスタンスのメソッドを呼び出す
• グローバル、静的メンバー、WdfContextを使用してクラスインスタンスを特定する
auto pDriver = new W3DSDriver();
if (pDriver != nullptr) {
status = pDriver->DriverEntry(DriverObject, RegistryPath);
g_pDriver = pDriver;
}
auto pDriver = W3DSDriver::GetSingleton();
if (pDriver != nullptr) {
status = pDriver->DriverEntry(DriverObject, RegistryPath);
}
26
WDDDS2022
3.3 C++を使用する場合のテクニック
■WDF_DECLARE_CONTEXT_TYPE_WITH_NAME
• WDFOBJECTのContextに設定する際に使用する
• 自動的にメモリが確保されるがコンストラクタは呼び出されず、0埋めされる
→ 継承クラスやクラスメンバに0埋めされると困るクラスには使用できない
• 0埋め不可のクラスを指定する場合は、ポインタを格納する構造体を作成し、
WdfXXXCreate呼び出し後に取り出してnewを処理する
typedef struct _WDFCONTEXT_W3DSDevice
{
W3DSDevice* pDevice;
} W3DSDeviceContext, DEVICE_CONTEXT, * PDEVICE_CONTEXT;
WDF_DECLARE_CONTEXT_TYPE_WITH_NAME(W3DSDeviceContext, GetDeviceContext)
#define GetW3DSDevice(wdfobject) GetDeviceContext(wdfobject)->pDevice
WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&deviceAttributes,
W3DSDeviceContext);
status = WdfDeviceCreate(&DeviceInit, &deviceAttributes, &device);
if (NT_SUCCESS(status)) {
auto pDeviceContext = GetDeviceContext(device);
pDeviceContext->pDevice = new W3DSDevice(device);
27
WDDDS2022 デモ • ソースコードの確認 28
WDDDS2022 3.4 C++を利用する場合の注意点 • C言語に比べてオーバーヘッドが掛かるので高速化が求められるところでは使わな いほうが良い →ただし、オーバーヘッドよりもIO待ちの方が大きい場合は誤差レベルの想定 • テンプレートなどを使用するとデバッグがやりにくい。特にカーネルデバッグは Try&Errorに時間がかかる → テンプレート自体をユーザーモードレベルの単体試験でバグのない状態にしておく • WDKヘッダを使用する際にextern “C”が必要。 • WDKのファンクションテーブルやコールバックはC言語が必要なため、ラッパー層 が必要となる • STLなどの外部ライブラリはカーネルモードでは使用不可 29
WDDDS2022 4. C++によるWindows/Macドライバ開発
WDDDS2022 4.1 共通化方法 • OS依存処理と依存しない制御ロジックに分割し、制御ロジックを制御クラスとし て抜き出す • OS依存処理はインターフェースの抽出を行い、InterfaceクラスとしてOS依存処理 の実装を隠蔽化し、OSごとにクラス実装する • 制御ロジック内の各OS固有な処理はプリプロセッサで処理を分離する • 変数の型の定義が異なるのでint64_tなどどちらでも使える型名を使用する → または、typedefやdefineで独自の型を定義を作成し、プリプロセッサで切り替える • エラー値などは独自に定義し、OS固有の変換関数を作成する 31
WDDDS2022 4.2 機能構成図 – 機能ブロック ① OS(Framework) ド ラ イ バ No ブロック ① OSやフレームワーク ② ドライバのエントリ処理。ドライバの 初期化やPnP、IOをディスパッチし、共 通処理に中継する。 ③ 共通処理(共通クラス) ③ OSに依存しない共通の制御ロジック部。 抽象化されたI/Fを通じてOSとのやり取 りやデバイスの制御を行う。 ④ デバイス制御処理 ④ デバイスを制御するための処理。I/Fク ラスにより共通部分にI/Fを提供し、OS/ フレームワークを抽象化する。 ② ドライバエントリ処理 ⑥ ⑤ 制御対象のデバイス ⑤ デバイス OS内部 OS独自処理 共通処理 独自I/Fと共通I/Fを結ぶ処理 32
WDDDS2022 4.2 機能構成図 - Windows WDF / WDM ド ラ イ バ ① ② No ① フレームワークとOS固有のドライバク ラスを繋ぐI/F (IOUSBHostDeviceのI/Fを使用) ② OS固有のドライバクラスとデバイスク ラスを繋ぐI/F ③ ドライバクラスと共通処理を繋ぐI/F ④ 共通処理からデバイスクラスを繋ぐI/F (IDeviceCtrlクラスのI/Fを使用) ⑤ デバイスクラスとフレームワークを繋 ぐI/F ⑥ USB Bulk通信IF W3DSDriver ③ W3DSManager I/F ④ W3DSDevice :public IDeviceCtrl ⑤ ⑥ ⑥ USB (Arduino) OS内部 OS独自処理 共通処理 独自I/Fと共通I/Fを結ぶ処理 33
WDDDS2022 4.2 機能構成図 - Mac No DriverKit ド ラ イ バ ① フレームワークとOS固有のドライバク ラスを繋ぐI/F (IOUSBHostDeviceのI/Fを使用) ② OS固有のドライバクラスとデバイスク ラスを繋ぐI/F ③ ドライバクラスと共通処理を繋ぐI/F ④ 共通処理からデバイスクラスを繋ぐI/F (IDeviceCtrlクラスのI/Fを使用) ⑤ デバイスクラスとフレームワークを繋 ぐI/F ⑥ フレームワークとmac固有のサービスを 繋ぐI/F ⑦ USB Bulk通信IF ① ② arduinoDriver ③ W3DSManager I/F ④ W3DSDevice :public IDeviceCtrl ⑤ ⑥ IOService ⑦ USB (Arduino) OS内部 OS独自処理 共通処理 独自I/Fを繋ぐ処理 Mac固有のサービス 34
WDDDS2022 デモ • 共通ソースコードの確認 • OS固有処理の確認 • 実動作の確認 35
WDDDS2022 4.3 共通化の注意点 • 使用する開発モデルに応じて、使用可能な標準APIやライブラリが異なるため、無 理に共通化するよりは、共通化しないことを検討する • OSやコンパイラ依存の処理は、OS専用のヘッダを用意して __noop や空定義にて コードを無効化する • 制御ロジック内でOS判定箇所が増えると共通化の意味合いがなくなるので、イン ターフェス設計を見直し、依存処理を分離する 36
WDDDS2022 ご清聴ありがとうございました 37