開発note@パッション

開発ノート:NFC-③DriverKit 実装 中編【ドライバプログラム実装編】

開発note@パッション

引き続きベースとなる DriverKit 開発の実装を進めていきます。
前回は DriverKit プロジェクトを作成し、アプリプログラムを実装するところまで実施いたしました。

今回はドライバプログラムの構成について解説していきます。

・NFCを活用した新しいサービスや製品を企画・開発したい方
・スマートフォンやICカードとの連携機能を実装したいエンジニア
・IoT機器や非接触決済の技術に関心のある開発者

▼関連記事はこちら▼
・開発ノート NFC-① イントロダクション
・開発ノート NFC-② 機材・開発環境準備
・開発ノート NFC-③ DriverKit 実装 前編【アプリプログラム実装編】

 

開発環境(再掲)

  • OS: macOS Sonoma 14.5
  • Xcode: Xcode 15.4 (15F31d)

 

ドライバターゲットの構成

ドライバターゲットの最小構成は以下の通りとなります。

開発note@パッション

 開始点として利用される C++ ソースファイル / .iig ヘッダーファイル

ドライバの実行開始時や停止時に呼び出される処理の宣言や実コードを記述します。
.iig は IOKit interface generator の略で、 DriverKit ドライバとシステムとのインターフェースとなるコードを自動的に生成してくれます。

 

◇ちょっと寄り道:実際に生成されたコードを見てみよう

.iig ファイルからは .h ヘッダーファイルと .cpp ソースファイルが自動生成されます。
これらのファイルは DerivedData に存在します。

ターミナルで以下のコマンドを実行すると、自動生成ファイルが格納されているディレクトリが Finder で開くことができます。
なお、 <> 内は各自の環境に合わせて置き換えてください。


% open ~/Library/Developer/Xcode/DerivedData/<プロジェクト名>-<*>/Build/Intermediates.noindex/<プロジェクト名>.build/Debug-driverkit/<ドライバ名>.build/DerivedSources/<ドライバの Bundle Identifier>/

 Info.plist ファイル

通常のアプリの Info.plist と同様です。
DriverKit ドライバにおいては、 IOKitPersonalities キーの設定が重要となります。

 

 Entitlements ファイル

通常のアプリの Entitlements ファイルと同様です。
DriverKit は Entitlements ファイルにより有効化されなければならないため、 DriverKit ドライバにおいては必須となります。
その他、ドライバの目的や実装に応じて Entitlement の設定が必要となります。

なお、 Xcode のバージョンによっては DriverKit の Entitlement が自動的に有効化されてビルドされるようで、プロジェクト作成時には
ファイルが存在しない場合もあります。(筆者の環境では、プロジェクト作成時には存在しませんでした。)

 

ドライバ実装

それではシンプルな構成を例に、実際に実装を進めていきます。
なお、詳細情報・最新情報は Apple 公式ドキュメントを参照ください。

 1. カスタマイズするサービスの選択

デフォルトでは、ドライバの基底クラスは IOService クラスとなっています。

IOService クラスはすべてのドライバサービスの基底クラスとなるため、そのままでもドライバとして機能しますが、通常はニーズに特化したクラスを基底クラスにします。

今回の場合、 FT232H と USB 標準プロトコルを使用して通信するため、USBDriverKit のインターフェースサービスである IOUSBHostInterface クラス を基底クラスとします。


  #include 
- #include <DriverKit/IOService.iig>
+ #include <USBDriverKit/IOUSBHostInterface.iig>
  
- class NFCDriver: public IOService
+ class NFCDriver: public IOUSBHostInterface
  {
  public:
      virtual kern_return_t
      Start(IOService * provider) override;
  };

 

 2. カスタマイズサービスに応じた DriverKit フレームワークの追加

今回は USBDriverKit の IOUSBHostInterface クラスを基底クラスとしたため、 USBDriverKit フレームワークを追加する必要があります。

  1. プロジェクト設定を開き、ドライバターゲットを選択します
  2. ①General – ②Frameworks and Libraries の ③ + アイコンをクリックします
  3. リストに対象の DriverKit サブフレームワークが存在する場合、該当のフレームワークを選択し、 Add ボタンをクリックして完了です (以降の作業は不要です)
  4. 対象の DriverKit サブフレームワークが存在しない場合、左下のリストボックスから Add Files… を選択します
  5. ターミナルで以下のコマンドを実行することで、 DriverKit フレームワークのファイルが格納されているディレクトリを Finder で開きます
  6. 
       open /Applications/Xcode.app/Contents/Developer/Platforms/DriverKit.platform/Developer/SDKs/DriverKit.sdk/System/DriverKit/System/Library/Frameworks
    
    
  7. 追加したい DriverKit サブフレームワーク(今回は USBDriverKit.framework)をファイルピッカーにドラッグアンドドロップし、Open ボタンをクリックします

追加が完了すると、 Frameworks and Libraries に追加した DriverKit サブフレームワークが表示されます。

 

 3. デバイス一致基準の設定

DriverKit ターゲット追加直後の設定では、ドライバインストール完了時やシステム起動時にドライバが実行されます。
今回は FT232H を Mac に接続したいる間のみドライバが実行されれば良いので、そのための設定をしていきます。

 

◇デバイス情報確認

以降の工程の中で、ドライバの対象デバイスを検知するためにデバイスの情報が必要となります。
検知のために必要なデバイス情報を確認していきましょう。

まず、 FT232H の製品IDと製造元IDを確認します。

  1. デバイスを Mac に接続します
  2. Mac のシステム情報アプリを開きます
  3. 開発note@パッション

  4. 左メニューの「USB」を選択します
  5. USB選択ツリーの中から対象デバイスを選択します
  6. 表示されたデバイス情報の製品ID、製造元IDを確認します

さらに製品IDと製造元IDを元に、より詳細な情報を取得します。
ここでは Homebrew を利用しますが、 Homebrew のインストール方法については割愛いたします。

    1. デバイスを Mac に接続し、ターミナルを開きます
    2. usbutils または lsusb 単体をインストールします
    3. 
         brew install usbutils
      
      
    4. lsusb でデバイスの詳細情報を取得します
    
       lsusb -v -d <製造元IDの16進値>:<製品IDの16進値>
    
    

    lsusb によりデバイスの詳細情報が取得できますが、ここで必要となる情報は bConfigurationValuebInterfaceNumber となります。

    
    Device Descriptor:
      Configuration Descriptor:
        bConfigurationValue
        Interface Descriptor:
          bInterfaceNumber
    
    

     

    ◇Info.plist 設定

    システムは起動時や新しいハードウェアデバイスを検出するたび、そのデバイスをサポートするドライバを検索します。
    この時、ドライバの Info.plist ファイルの IOKitPersonalities キー情報を使用して、デバイスをサポートするドライバか特定します。

    IOKitPersonalities キーの例(初期値):

    IOKitPersonalities キーは Dictionary 型であり、ドライバのパーソナリティ(ドライバがサポートするデバイスの種類)を複数設定することができます。
    それぞれのドライバのパーソナリティも Dictionary 型で、最低限以下のキーセットを含める必要があります。

    • CFBundleIdentifier:ドライバの Bundle Identifier
    • IOClass:基本動作を含むAppleクラス
    • IOUserClass:カスタムサービスクラスの名前
    • IOUserServerName:ドライバの Bundle Identifier
    • IOProviderClass:サービスがプロバイダーオブジェクトとして必要とするクラス

    また、 IOProviderClass で指定したクラスに応じた固有のキーをパーソナリティに追加することで、特定デバイス専用のドライバを作成することができます。
    例えばUSBデバイスを対象とする場合、デバイスの種類を表す idVendoridProduct 、インターフェースを表す bInterfaceNumberbConfigurationValue などがあります。

    今回はターゲット作成時の初期値をベースとして、以下の通りに設定いたしました。

    • CFBundleIdentifier:ドライバの Bundle Identifier を設定
    • IOProviderClassIOUSBHostInterface に変更
    • idVendor:使用する FT232H の製造元IDを設定
    • idProduct:使用する FT232H の製品IDを設定
    • bInterfaceNumber:使用する FT232H の bInterfaceNumber を設定
    • bConfigurationValue:使用する FT232H の bConfigurationValue を設定

     

    ◇Entitlements ファイル設定

    今回は FT232H とUSB通信するため、 com.apple.developer.driverkit.transport.usb の Entitlement が必要となります。
    これを追加するため、対応する Capability を追加します。

    1. プロジェクト設定を開き、ドライバターゲットを選択します
    2. Signing & Capabilities を選択し、左上の + Capability をクリックします
    3. 追加したい Capability(今回は DriverKit USB Transport (development))をダブルクリックします

      ※ Entitlement の申請が通っている場合、 (development) がつかない Capability が選択でき、実製品のドライバにはそちらを選択する必要があります

    追加が完了すると、 Signing & Capabilities に追加した Capability が表示されます。

    また、ドライバのソースディレクトリに Entitlement ファイルが追加され、 com.apple.developer.driverkit.transport.usb に相当する DriverKit USB Transport が設定されていることが確認できます。

    Signing & Capabilities に表示されているメッセージの通り、追加した Capability によってはさらに設定が必要な場合があります。
    com.apple.developer.driverkit.transport.usb にはドライバがサポートするUSBデバイスを識別するための情報を設定する必要があるため、これを設定していきます。

    1. ナビゲーションから Entitlements ファイルを開くか、 Signing & Capabilities に表示されているメッセージ横のアイコンをクリックします
    2. DriverKit USB TransportDictionary 型のアイテムを追加します
    3. Dictionary 型のアイテムに2つのキーセットを追加します
      • idVendor:使用する FT232H の製造元IDを設定
      • idProduct:使用する FT232H の製品IDを設定

     

     4. バージョンと説明情報の設定

    ドライバの読み込みやインストールのためには特定の情報が必要です。
    具体的には、ドライバの Info.plist で以下のいずれかのキーを設定する必要があり、設定しないとインストールが失敗します。

    • CFBundleShortVersionString:Xcode 上では Bundle version string (short)
    • CFBundleVersion:Xcode 上では Bundle version
    • OSBundleUsageDescription:Xcode 上では Privacy – Driver Extension Usage Description

    ターゲット追加後のデフォルトで OSBundleUsageDescription が設定されているため、今回は特に設定しないものとします。

     

     5. ドライバの初期化・解放処理の実装

    ドライバのロード時、ドライバのサービスクラスがインスタンス化され、初期化のための init メソッドが呼び出されます。
    また、アンロード時には解放のための free メソッドが呼び出されます。
    これらの init / free メソッドをオーバーライドして、ドライバ固有の初期化・解放処理を実装します。
    ただし、の初期化・解放処理は通常、ドライバの変数領域の確保・解放のみを実装します。

    
    /// ドライバ処理で必要なデータをまとめた構造体
    struct NFCDriver_IVars
    {
        // TODO: 必要なデータを宣言していく
    };
    
    /// 初期化処理
    bool NFCDriver::init()
    {
        os_log(OS_LOG_DEFAULT, "init method is called."); // for debug
        if (!super::init()) {
            return false;
        }
    
        ivars = IONewZero(NFCDriver_IVars, 1);
        if (!ivars) {
            return false;
        }
    
    exit:
        return true;
    }
    
    /// 解放処理
    void NFCDriver::free()
    {
        os_log(OS_LOG_DEFAULT, "free method is called."); // for debug
        if (ivars) {
            IOFree(ivars, sizeof(NFCDriver_IVars));
        }
    
        super::free();
    }
    
    

    サービスのサブクラスにはメンバ変数を含めることができません。
    このため、ドライバに必要なデータは、構造体 <ドライバ名>_IVars を定義してそのメンバ変数として用意します。
    そして、初期化・解放時はその構造体を使って領域を確保・解放する必要があります。
    基底クラスから継承されるメンバ変数 ivars はその構造体型となり、初期化・解放時は ivars に対して領域を確保・解放します。

     

     6. ドライバサービスの開始・終了処理の実装

    デバイスからの情報処理を開始する準備が整うと、サービスの開始処理を実行するために Start メソッドが呼び出されます。
    また、サービスが不要になった時には、サービスの終了処理を実行するために Stop メソッドが呼び出されます。
    これらの Start / Stop メソッドをオーバーライドして、ドライバ固有の開始・終了処理を実装します。

    
    kern_return_t
    IMPL(NFCDriver, Start)
    {
        os_log(OS_LOG_DEFAULT, "Start method is called."); // for debug
        kern_return_t ret;
    
        // 基底クラスの停止処理を実行する
        ret = Start(provider, SUPERDISPATCH);
        if (ret != kIOReturnSuccess) {
           Stop(provider, SUPERDISPATCH);
           return ret;
        }
    
        // TODO: ドライバ固有の起動処理を実装する
    
        // サービスをシステムへ登録する
        RegisterService();
        return ret;
    }
    
    

    Start メソッドでは、基底クラスの Stop メソッドを呼び出した後、ドライバ固有で必要な起動タスクを実行します。
    例えば、データバッファ用のメモリを確保したり、コールバックメソッドを作成・登録したりします。
    起動タスクが正常に完了したら RegisterService メソッドを呼び出し、サービスが実行中であることをシステムに通知します。

    
    kern_return_t
    IMPL(NFCDriver, Stop)
    {
        os_log(OS_LOG_DEFAULT, "Stop method is called."); // for debug
        kern_return_t ret;
    
        // TODO: ドライバ固有の停止処理を実装する
    
        // 基底クラスの停止処理を実行する
        ret = Stop(provider, SUPERDISPATCH);
        return ret;
    }
    
    

    Stop メソッドでは、ドライバが安全に停止するためのタスクを実行します。
    Start メソッドで実行したすべての設定作業を戻し、進行中の非同期タスクがある場合はキャンセルして完了まで待機します。
    すべてのタスクが完了した後、基底クラスの Stop メソッドを呼び出します。
    注意点として、 Stop メソッドは ivars 構造体を解放するために使用してはいけません。 ivars 構造体を解放する際は free メソッドを使用する必要があります。

    両方のメソッドでは、 IMPLSUPERDISPATCH マクロが利用されています。 以前の記事でご説明した通り、DriverKit で開発されたドライバサービスは独立したユーザープロセスで実行されますが、デバイスとの通信等のためにカーネルと通信します。
    これらのマクロは、ドライバプロセスとカーネルとのブリッジングコードを提供します。

     

     7. ビルドアーキテクチャの追加

    DriverKit ドライバはカーネル拡張機能と連携することがよくあります。
    Appleシリコンでは、カーネル拡張機能は arm64e アーキテクチャを使用しているため、ドライバも arm64e アーキテクチャ向けにビルドする必要があります。

    1. プロジェクト設定を開き、ドライバターゲットを選択します
    2. ①Build Settings – ②Architectures – ③Architectures を選択し、 ④Others… をクリックします
    3. arm64e を追加します

     

    おわりに

    DriverKit実装-中編ということで、ドライバターゲットの構成とドライバ実装についてご紹介いたしました。

    次回はこれまで実装してきたものをビルドし、動作を見ていきたいと思います。


     

TOP