開発

NFC#06 通信処理実装[前編]-USB転送

通信処理実装[前編]-USB転送

前回まででUSBデバイスと通信するためのベースを実装しました。
今回から実際にUSBデバイスとの通信を実装していきます。

本開発ではNFCリーダーモジュールと通信しますが、モジュールはMacと直接接続できないため、 FT232H を介して通信します。
FT232H に対し、USBの制御転送(Control Transfer)により FT232H デバイスを設定し、バルク転送(Bulk Transfer)によりモジュールとのデータ通信をします。
今回の記事ではこれらを実現するメソッドを実装します。

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

開発環境(再掲)

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

 

FT232H デバイス設定

 USB 制御転送

FT232H デバイスの設定は、USBの制御転送(Control Transfer)により設定します。
USB 制御転送は以下の6つの要素で構成されており、これらを適宜設定して FT232H に送信することとなります。

  • bmRequestType:データ転送方向(1ビット)、タイプ(2ビット)、転送対象(5ビット)
  • bRequest:リクエストの種類
  • wValue:リクエストに応じた値
  • wIndex:ポート番号(リクエストに応じた値を含む場合あり)
  • wLength:データ長
  • data:実際のデータ(IN / OUT はデータ転送方向による)

USBDriverKit において、USB の制御転送は DeviceRequest メソッドや AsyncDeviceRequest メソッドによって実現されます。今回は同期メソッドの DeviceRequest を利用します。
また、 bmRequestType の各要素については USBDriverKit/AppleUSBDefinitions.h で定義されています。以降の記載はこの定義名で記載するものといたします。

 

 デバイス設定の種類

FT232H のデバイス設定を大別すると、共通の動作設定と、モジュールとの通信方式に応じた通信設定の2種類があります。

◇動作設定

まず、 FT232H 本体の動作設定を実行します。
この設定はどの通信プロトコルでも共通となります。

  1. デバイス初期化
  2. レイテンシタイマ設定
  3. イベントキャラクタ、エラーキャラクタ設定
  4. ビットモード設定

 

◇通信設定(UART通信の場合)

次に通信プロトコルに応じた通信設定を実行します。
RC-S660/S は UART 通信のみ対応しているため、 UART 通信のための通信設定を実行します。

  1. ボーレート設定
  2. データ特性設定
  3. フロー制御設定
  4. モデム制御信号設定(今回は使用しないため割愛)

 

 デバイス設定の実装

◇事前準備: 通信デバイスオブジェクトの準備

制御転送を実現する DeviceRequest メソッドは、USBデバイスを表す IOUSBHostDevice クラスのメソッドです。
このため、現在接続されているUSBデバイスに対して制御転送するためには、対象の IOUSBHostDevice オブジェクトを取得する必要があります。
また、取得したオブジェクトは IVars 構造体に保持し、ドライバ処理で利用できるようにしておきます。

現在接続されているUSBの情報は、 Start メソッドの引数 provider から取得できます。
今回は基底クラスを IOUSBHostInterface クラスとしているため、引数 providerIOUSBHostInterface オブジェクトとなります。
IOUSBHostInterface オブジェクトから IOUSBHostDevice オブジェクトを取得するためには CopyDevice メソッドを使用します。


  /// ドライバ処理で必要なデータをまとめた構造体
  struct NFCDriver_IVars
  {
      // TODO: 必要なデータを宣言していく
+     IOUSBHostInterface       *interface;
+     IOUSBHostDevice          *device;
  };
  
  kern_return_t
  IMPL(NFCDriver, Start)
  {
      // :(省略)
      // TODO: ドライバ固有の起動処理を実装する
  
+     // プロバイダとして取得される通信インターフェースオブジェクトを保持
+     ivars->interface = OSDynamicCast(IOUSBHostInterface, provider);
+     if (ivars->interface == NULL) {
+         os_log(OS_LOG_DEFAULT, "Get IOUSBHostInterface failed.");
+         return kIOReturnNoDevice;
+     }
+     // 通信インターフェースのオープン
+     ret = ivars->interface->Open(this, 0, NULL);
+     if (ret != kIOReturnSuccess) {
+         os_log(OS_LOG_DEFAULT, "IOUSBHostInterface Open failed.");
+         Stop(provider, SUPERDISPATCH);
+         return ret;
+     }
+ 
+     // デバイスオブジェクト取得
+     ret = ivars->interface->CopyDevice(&ivars->device);
+     if (ret != kIOReturnSuccess) {
+         os_log(OS_LOG_DEFAULT, "IOUSBHostDevice Copy failed.");
+         Stop(provider, SUPERDISPATCH);
+         return ret;
+     }
+     // デバイスオープン
+     ret = ivars->device->Open(this, 0, NULL);
+     if (ret != kIOReturnSuccess) {
+         os_log(OS_LOG_DEFAULT, "IOUSBHostDevice Open failed.");
+         Stop(provider, SUPERDISPATCH);
+         return ret;
+     }
      // :(省略)
  }
  
  kern_return_t
  IMPL(NFCDriver, Stop)
  {
      // :(省略)
      // TODO: ドライバ固有の停止処理を実装する
+     // Start 時にオープンしたデバイスをクローズし、生成したオブジェクトを解放する
+     ret = ivars->device->Close(this, 0);
+     if (ret != kIOReturnSuccess) {
+         os_log(OS_LOG_DEFAULT, "IOUSBHostDevice Close failed.");
+         return ret;
+     }
+     OSSafeReleaseNULL(ivars->device);
+ 
+     // Start 時にオープンしたインターフェースをクローズする
+     ret = ivars->interface->Close(this, 0);
+     if (ret != kIOReturnSuccess) {
+         os_log(OS_LOG_DEFAULT, "IOUSBHostInterface Close failed.");
+         return ret;
+     }
      // :(省略)
  }

◇共通設定 – 1. デバイス初期化


/// ベンダリクエスト:リセットコマンド(0x00)
/// @param [in] resetArg 0 : デバイスリセット、 1 : 受信バッファクリア、 2 : 送信バッファクリア
/// @return 実行結果
kern_return_t NFCDriver::ResetDevice(Ft232hReqResetArg resetArg)
{
    // FT232H Vendor Request : Reset Command (0x00)
    uint16_t bytesTransferred;
    return ivars->device->DeviceRequest(this,
                                        kIOUSBDeviceRequestDirectionOut
                                          | kIOUSBDeviceRequestTypeVendor
                                          | kIOUSBDeviceRequestRecipientDevice,
                                        (uint8_t)Ft232hReq::Reset,  // 0x00
                                        (uint16_t)resetArg,
                                        portNumber,
                                        0,
                                        nullptr,
                                        &bytesTransferred,
                                        ft232hIOTimeout);
}

  • bmRequestType
    • データ転送方向: kIOUSBDeviceRequestDirectionOut (ホスト -> デバイス)
    • タイプ: kIOUSBDeviceRequestTypeVendor (ベンダーリクエスト)
    • 転送対象: kIOUSBDeviceRequestRecipientDevice (デバイス)
  • bRequest:リセットコマンド 0x00
  • wValue:デバイス初期化においては 0
    • リセットコマンドはバッファクリアにも利用されるため、引数で指定できるようにしています
  • wIndex:ポート番号
  • wLength:転送するデータはないため 0
  • data:転送するデータはないため nullptr

 

◇共通設定 – 2. レイテンシタイマ設定

FT232H からホスト(今回は Mac)にデータ転送されるタイミングはいくつかあり、その中の1つがレイテンシタイマです。
レイテンシタイマは最後にデータ転送されてからの時間を計測しており、設定されたタイムアウト値が経過するとホストへデータ転送します。

サイズの小さいデータが連続して転送される場合、データ転送の度にレイテンシタイマの分だけ遅延時間が発生します。
しかし、レイテンシタイマを極端に短くすると、 FT232H 通信対象モジュールからのデータ転送が途中でもホストへデータ転送されてしまい、データがロストする場合があります。
このため、 FT232H を介して通信するチップ・モジュールによって適切な値を設定する必要があります。


/// ベンダリクエスト:レイテンシタイマ設定コマンド(0x09)
/// @param [in] latency レイテンシタイマ値(ミリ秒単位、1〜255)
/// @return 実行結果
kern_return_t NFCDriver::SetLatencyTimer(uint8_t latency)
{
    // FT232H Vendor Request : Set Latency Timer (0x09)
    uint16_t bytesTransferred;
    return ivars->device->DeviceRequest(this,
                                        kIOUSBDeviceRequestDirectionOut
                                          | kIOUSBDeviceRequestTypeVendor
                                          | kIOUSBDeviceRequestRecipientDevice,
                                        (uint8_t)Ft232hReq::SetLatencyTimer,    // 0x09
                                        latency,
                                        portNumber,
                                        0,
                                        nullptr,
                                        &bytesTransferred,
                                        ft232hIOTimeout);
}

  • bmRequestType
    • データ転送方向: kIOUSBDeviceRequestDirectionOut (ホスト -> デバイス)
    • タイプ: kIOUSBDeviceRequestTypeVendor (ベンダーリクエスト)
    • 転送対象: kIOUSBDeviceRequestRecipientDevice (デバイス)
  • bRequest:レイテンシタイマ設定コマンド 0x09
  • wValue:レイテンシタイマ値(実装しながら調整)
  • wIndex:ポート番号
  • wLength:転送するデータはないため 0
  • data:転送するデータはないため nullptr

 

◇共通設定 – 3. イベントキャラクタ、エラーキャラクタ設定

イベントキャラクタ・エラーキャラクタは、受信バッファの制御やエラー通知をするために使われる特殊な制御機能です。

イベントキャラクタがデータストリーム内で検出されると、バッファの内容が直ちに送信されます。
また、パリティエラー等の通信エラーが発生したとき、エラーキャラクタを挿入してエラーを通知します。

UART 通信時には、有効・無効、対象キャラクタを必要に応じて設定します。
それ以外(SPI通信やI2C通信等)では無効にします。


/// ベンダリクエスト:イベントキャラクタ設定コマンド(0x06)
/// @param [in] chr イベントキャラクタ
/// @param [in] isEnable イベントキャラクタを有効にするかどうか
/// @return 実行結果
kern_return_t NFCDriver::SetEventChr(uint8_t chr, bool isEnable)
{
    // FT232H Vendor Request : Set Event Character (0x06)
    uint16_t bytesTransferred;
    return ivars->device->DeviceRequest(this,
                                        kIOUSBDeviceRequestDirectionOut
                                          | kIOUSBDeviceRequestTypeVendor
                                          | kIOUSBDeviceRequestRecipientDevice,
                                        (uint8_t)Ft232hReq::SetEventChar,   // 0x06
                                        ((isEnable ? 0x01 : 0x00) << 8) | chr,
                                        portNumber,
                                        0,
                                        nullptr,
                                        &bytesTransferred,
                                        ft232hIOTimeout);
}

/// ベンダリクエスト:エラーキャラクタ設定コマンド(0x07)
/// @param [in] chr エラーキャラクタ
/// @param [in] isEnable エラーキャラクタを有効にするかどうか
/// @return 実行結果
kern_return_t NFCDriver::SetErrorChr(uint8_t chr, bool isEnable)
{
    // FT232H Vendor Request : Set Error Character (0x07)
    uint16_t bytesTransferred;
    return ivars->device->DeviceRequest(this,
                                        kIOUSBDeviceRequestDirectionOut
                                          | kIOUSBDeviceRequestTypeVendor
                                          | kIOUSBDeviceRequestRecipientDevice,
                                        (uint8_t)Ft232hReq::SetErrorChar,   // 0x07
                                        ((isEnable ? 0x01 : 0x00) << 8) | chr,
                                        portNumber,
                                        0,
                                        nullptr,
                                        &bytesTransferred,
                                        ft232hIOTimeout);
}

  • bmRequestType
    • データ転送方向: kIOUSBDeviceRequestDirectionOut (ホスト -> デバイス)
    • タイプ: kIOUSBDeviceRequestTypeVendor (ベンダーリクエスト)
    • 転送対象: kIOUSBDeviceRequestRecipientDevice (デバイス)
  • bRequest:イベントキャラクタ設定コマンド 0x06 / エラーキャラクタ設定コマンド 0x07
  • wValue
    • 上位1バイト:キャラクタ設定 有効(0x01) or 無効(0x00)
    • 下位1バイト:任意の1バイトデータ
  • wIndex:ポート番号
  • wLength:転送するデータはないため 0
  • data:転送するデータはないため nullptr

 

◇共通設定 – 4. ビットモード設定

FT232H の AD7 〜 AD0 のピンごとに動作モードを設定します。
FT232H を介して通信するチップ・モジュールとの通信方式に応じて、この動作モードや設定が必須なピンが決まります。動作モードの一例を以下に示します。

  • 0x00 : リセット(UART通信、デフォルト)
  • 0x01 : 非同期 BitBang モード(GPIO)
  • 0x02 : MPSSE モード(SPI通信, I2C通信)

/// ベンダリクエスト:ビットモード設定コマンド(0x0b)
/// @param [in] bitMode 設定するビットモード
/// @param [in] pin 有効にするピンを表すビット列(最上位ビットから順に AD7 〜 AD0)
/// @return 実行結果
kern_return_t NFCDriver::SetBitMode(Ft232hBitMode bitMode, uint8_t pin)
{
    // FT232H Vendor Request : Set Bit Mode (0x0b)
    uint16_t bytesTransferred;
    return ivars->device->DeviceRequest(this,
                                        kIOUSBDeviceRequestDirectionOut
                                          | kIOUSBDeviceRequestTypeVendor
                                          | kIOUSBDeviceRequestRecipientDevice,
                                        (uint8_t)Ft232hReq::SetBitMode, // 0x0b
                                        (uint8_t)bitMode << 8 | pin,
                                        portNumber,
                                        0,
                                        nullptr,
                                        &bytesTransferred,
                                        ft232hIOTimeout);
}

  • bmRequestType
    • データ転送方向: kIOUSBDeviceRequestDirectionOut (ホスト -> デバイス)
    • タイプ: kIOUSBDeviceRequestTypeVendor (ベンダーリクエスト)
    • 転送対象: kIOUSBDeviceRequestRecipientDevice (デバイス)
  • bRequest:ビットモード設定コマンド 0x0b
  • wValue
    • 上位1バイト:動作モード
    • 下位1バイト:設定するピンに対応するビット(最上位ビットから順に AD7 〜 AD0 )を 1 にする
  • wIndex:ポート番号
  • wLength:転送するデータはないため 0
  • data:転送するデータはないため nullptr

 

◇UART通信設定 – 1. ボーレート設定

ボーレートは「単位時間に搬送波に変調される回数」と定義されており、通信速度に直結します。
シリアル通信において、通信対象デバイス間でボーレート設定が異なると通信ができないため、デバイスに合わせて適切に設定する必要があります。

FT232H のボーレート設定では、基準となるクロック周波数の除数を指定します。
除数のため正確なボーレートとならない場合もありますが、±3%の範囲であればエラーなく機能します。
基準となるクロック周波数は通常 3 MHz です。高いボーレートを設定する場合は異なる周波数となりますが、今回は通常の範囲に収まるため割愛いたします。


/// ベンダリクエスト:ボーレート設定コマンド(0x03)
/// @param [in] baudrate ボーレート値(最大 3 MBaud)
/// @return 実行結果
kern_return_t NFCDriver::SetBaudrate(int baudrate)
{
    // FT232H Vendor Request : Set Baud Rate (0x03)
    uint16_t bytesTransferred;
    uint16_t divisor = BAUDRATE_CLK_FREQ_BASE / baudrate;
    return ivars->device->DeviceRequest(this,
                                        kIOUSBDeviceRequestDirectionOut
                                          | kIOUSBDeviceRequestTypeVendor
                                          | kIOUSBDeviceRequestRecipientDevice,
                                        (uint8_t)Ft232hReq::SetBaudrate,    // 0x03
                                        divisor,
                                        portNumber,
                                        0,
                                        nullptr,
                                        &bytesTransferred,
                                        ft232hIOTimeout);
}

  • bmRequestType
    • データ転送方向: kIOUSBDeviceRequestDirectionOut (ホスト -> デバイス)
    • タイプ: kIOUSBDeviceRequestTypeVendor (ベンダーリクエスト)
    • 転送対象: kIOUSBDeviceRequestRecipientDevice (デバイス)
  • bRequest:ボーレート設定コマンド 0x03
  • wValue3,000,000 ÷ 設定したいボーレート値
  • wIndex
    • 上位1バイト:高いボーレートを設定するための情報(今回は割愛のため 0 固定)
    • 下位1バイト:ポート番号
  • `wLength`:転送するデータはないため 0
  • data:転送するデータはないため nullptr

 

◇UART通信設定 – 2. データ特性設定

シリアル通信パラメータを設定します。
FT232H を介して通信するデバイスの仕様に合わせて設定します。


/// ベンダリクエスト:データ特性設定コマンド(0x04)
/// @param [in] breakType ブレーク信号(0 : Off、 1 : On)
/// @param [in] stop ストップビット(0 : 1 ビット、 1 : 1.5 ビット、 2 : 2 ビット)
/// @param [in] parity パリティビット(0 : なし、 1 : 奇数、 2 : 偶数、 3 : 常に 1、 4 : 常に 0)
/// @param [in] data データビット長
/// @return 実行結果
kern_return_t NFCDriver::SetDataCharacteristics(Ft232hReqSetDataArgBreak breakType,
                                                Ft232hReqSetDataArgStopBits stop,
                                                Ft232hReqSetDataArgParityBits parity,
                                                Ft232hReqSetDataArgDataBit data)
{
    // FT232H Vendor Request : Set the Data Characteristics (0x04)
    uint16_t bytesTransferred;
    return ivars->device->DeviceRequest(this,
                                        kIOUSBDeviceRequestDirectionOut
                                          | kIOUSBDeviceRequestTypeVendor
                                          | kIOUSBDeviceRequestRecipientDevice,
                                        (uint8_t)Ft232hReq::SetData,    // 0x04
                                        ((uint16_t)breakType << 14)
                                          | ((uint16_t)stop << 11)
                                          | ((uint16_t)parity << 8)
                                          | (uint16_t)data,
                                        portNumber,
                                        0,
                                        nullptr,
                                        &bytesTransferred,
                                        ft232hIOTimeout);
}

  • bmRequestType
    • データ転送方向: kIOUSBDeviceRequestDirectionOut (ホスト -> デバイス)
    • タイプ: kIOUSBDeviceRequestTypeVendor (ベンダーリクエスト)
    • 転送対象: kIOUSBDeviceRequestRecipientDevice (デバイス)
  • bRequest:データ特性設定コマンド 0x04
  • wValue
    • 上位1バイト:最上位ビットから順に b7 〜 b0 として以下を設定します
      • b2-b0:パリティビット
      • b5-b3:ストップビット
      • b7-b6:ブレーク信号
    • 下位1バイト:データビット長
  • wIndex:ポート番号
  • wLength:転送するデータはないため 0
  • data:転送するデータはないため nullptr

 

◇UART通信設定 – 3. フロー制御設定

UART通信では、送信開始・終了のタイミングを制御することができます。
FT232H を介して通信するデバイスの仕様に合わせて設定します。


/// ベンダリクエスト:フロー制御設定コマンド(0x02)
/// @param [in] flowSettings フロー制御設定
/// @param [in] wValue フロー制御設定によって異なる(今回はデフォルト 0x00, 必要に応じて呼び元で指定するものとする)
/// @return 実行結果
kern_return_t NFCDriver::SetFlowCtrl(Ft232hReqSetFlowCtrlArg flowSettings, uint16_t wValue)
{
    // FT232H Vendor Request : Set Flow Control (0x02)
    uint16_t bytesTransferred;
    return ivars->device->DeviceRequest(this,
                                        kIOUSBDeviceRequestDirectionOut
                                        | kIOUSBDeviceRequestTypeVendor
                                        | kIOUSBDeviceRequestRecipientDevice,
                                        (uint8_t)Ft232hReq::SetFlowCtrl,    // 0x02
                                        wValue,
                                        ((uint8_t)flowSettings << 8) | portNumber,
                                        0,
                                        nullptr,
                                        &bytesTransferred,
                                        ft232hIOTimeout);
}

  • bmRequestType
    • データ転送方向: kIOUSBDeviceRequestDirectionOut (ホスト -> デバイス)
    • タイプ: kIOUSBDeviceRequestTypeVendor (ベンダーリクエスト)
    • 転送対象: kIOUSBDeviceRequestRecipientDevice (デバイス)
  • bRequest:フロー制御設定コマンド 0x02
  • wValue:フロー制御種類によって異なる
  • wIndex
    • 上位1バイト:フロー制御種類
    • 下位1バイト:ポート番号
  • wLength:転送するデータはないため 0
  • data:転送するデータはないため nullptr

 

FT232H データ通信

 USB バルク転送

適切にデバイス設定ができたら、 FT232H に対してバルク転送(Bulk Transfer)することで、 FT232H を介してモジュールとデータ通信できます。

UART 通信時には、 FT232H に対して送信したデータは、接続モジュールにそのまま送信されます。
一方、 FT232H からは、2バイトのステータスバイト+接続モジュールからの受信データが受信されます。

USBDriverKit において、 USB のバルク転送は IO メソッドや AsyncIO メソッドによって実現されます。今回は同期メソッドの IO を利用します。

 

 データ通信の実装

◇デバイス情報確認

この後の実装を進める上で必要なデバイス情報を確認します。
以前の記事 で紹介した lsusb でデバイスの詳細情報を取得します。
この中で、今回必要となるのは、エンドポイントアドレス bEndpointAddress と、それぞれのエンドポイントの最大パケットサイズ wMaxPacketSize です。


Device Descriptor:
  Configuration Descriptor:
    Interface Descriptor:
      Endpoint Descriptor:
        bEndpointAddress     0xXX  EP N IN
        wMaxPacketSize     0xXXXX
      Endpoint Descriptor:
        bEndpointAddress     0xXX  EP N OUT
        wMaxPacketSize     0xXXXX

 

◇パイプオブジェクトの準備

バルク転送を実現する IO メソッドは、USBエンドポイントとの間でデータ転送するための IOUSBHostPipe クラスのメソッドです。
このため、現在接続されているUSBデバイスに対してバルク転送するためには、対象の IOUSBHostPipe オブジェクトを取得する必要があります。
ここで、一つの IOUSBHostPipe オブジェクトは一方向のパイプを表すため、送受信のためには2つのオブジェクトが必要です。
また、取得したオブジェクトは IVars 構造体に保持し、ドライバ処理で利用できるようにしておきます。

IOUSBHostPipe オブジェクトは IOUSBHostInterface クラスの CopyPipe メソッドを呼び出すことで取得します。
先述の通り、今回の場合は Start メソッドの引数 provider が現在接続されているUSBの IOUSBHostInterface オブジェクトとなるため、 providerCopyPipe メソッドを使用して2つの IOUSBHostPipe オブジェクトを取得します。


+ static const uint8_t kInEndpointAddress = 0xXX;   // 実際のデバイスの IN エンドポイントアドレスを設定する
+ static const uint8_t kOutEndpointAddress = 0xXX;  // 実際のデバイスの OUT エンドポイントアドレスを設定する

  /// ドライバ処理で必要なデータをまとめた構造体
  struct NFCDriver_IVars
  {
      // TODO: 必要なデータを宣言していく
      IOUSBHostInterface       *interface;
      IOUSBHostDevice          *device;
+     IOUSBHostPipe            *inPipe;
+     IOUSBHostPipe            *outPipe;
  };
  
  kern_return_t
  IMPL(NFCDriver, Start)
  {
      // :(省略)
+     // データ通信のためのパイプオブジェクト取得
+     ret = ivars->interface->CopyPipe(kInEndpointAddress, &ivars->inPipe);
+     if (ret != kIOReturnSuccess) {
+         os_log(OS_LOG_DEFAULT, "CopyPipe for input failed.");
+         Stop(provider, SUPERDISPATCH);
+         return ret;
+     }
+     ret = ivars->interface->CopyPipe(kOutEndpointAddress, &ivars->outPipe);
+     if (ret != kIOReturnSuccess) {
+         os_log(OS_LOG_DEFAULT, "CopyPipe for output failed.");
+         Stop(provider, SUPERDISPATCH);
+         return ret;
+     }
+ 
      // :(省略)
  }
  
  kern_return_t
  IMPL(NFCDriver, Stop)
  {
      // :(省略)
      // TODO: ドライバ固有の停止処理を実装する
  
+     // Start 時に生成したパイプオブジェクトを解放する
+     OSSafeReleaseNULL(ivars->outPipe);
+     OSSafeReleaseNULL(ivars->inPipe);
      // :(省略)
  }

 

◇データ授受のためのバッファ準備

DriverKit ドライバは、他のプロセスとデータを共有するために IOBufferMemoryDescriptor を利用します。
カーネルとのデータ共有でも同様で、デバイスとの通信データを格納するために IOBufferMemoryDescriptor を利用します。


+ static const uint16_t inMaxPacketSize = 0xXXXX;   // 実際のデバイスの IN エンドポイントの最大パケットサイズを設定する
+ static const uint16_t outMaxPacketSize = 0xXXXX;  // 実際のデバイスの OUT エンドポイントの最大パケットサイズを設定する

  /// ドライバ処理で必要なデータをまとめた構造体
  struct NFCDriver_IVars
  {
      // TODO: 必要なデータを宣言していく
      IOUSBHostInterface       *interface;
      IOUSBHostDevice          *device;
      IOUSBHostPipe            *inPipe;
      IOUSBHostPipe            *outPipe;
+     IOBufferMemoryDescriptor *inData;
+     uint8_t                  *inDataAddr;
+     IOBufferMemoryDescriptor *outData;
+     uint8_t                  *outDataAddr;
  };
  
  kern_return_t
  IMPL(NFCDriver, Start)
  {
      // :(省略)
  
+     // デバイスからの入力バッファ生成
+     ret = ivars->interface->CreateIOBuffer(kIOMemoryDirectionIn,
+                                            ivars->maxPacketSize,
+                                            &ivars->inData);
+     if (ret != kIOReturnSuccess) {
+         os_log(OS_LOG_DEFAULT, "CreateIOBuffer for output failed.");
+         Stop(provider, SUPERDISPATCH);
+         return ret;
+     }
+ 
+     // デバイスへの出力バッファ生成
+     // 送信データをプログラムで設定するため、入出力(kIOMemoryDirectionInOut)として生成する
+     ret = ivars->interface->CreateIOBuffer(kIOMemoryDirectionInOut,
+                                            ivars->maxPacketSize,
+                                            &ivars->outData);
+     if (ret != kIOReturnSuccess) {
+         os_log(OS_LOG_DEFAULT, "CreateIOBuffer for output failed.");
+         Stop(provider, SUPERDISPATCH);
+         return ret;
+     }
+ 
+     // 入出力バッファへアクセスするためのアドレスを取得しておく
+     IOAddressSegment range;
+     ivars->inData->GetAddressRange(&range);
+     ivars->inDataAddr = reinterpret_cast<uint8_t*>(range.address);
+     ivars->outData->GetAddressRange(&range);
+     ivars->outDataAddr = reinterpret_cast<uint8_t*>(range.address);
      // :(省略)
  }
  
  kern_return_t
  IMPL(NFCDriver, Stop)
  {
      // :(省略)
      // TODO: ドライバ固有の停止処理を実装する
  
+     // Start 時に生成したバッファオブジェクトを解放する
+     OSSafeReleaseNULL(ivars->outData);
+     OSSafeReleaseNULL(ivars->inData);
      // :(省略)
  }

 

◇データ送信メソッドの実装

デバイスへの出力バッファにデータを書き込み、出力用パイプを通じてデバイスにデータ送信します。
この時、一度のデータ転送では、エンドポイントのパケット最大サイズを超えないようにする必要があります。


/// データを送信する
/// @param [in] buffer 送信データ
/// @param [in] size 送信サイズ
/// @return 実行結果
kern_return_t NFCDriver::SendData(const uint8_t *buffer, size_t size)
{
    // for debug 書き込みデータのダンプ
    os_log(OS_LOG_DEFAULT, "SendData Size: %{public}zu bytes", size);
    DumpBuffer(buffer, size);

    // 引数チェック
    if (buffer == nullptr) {
        os_log(OS_LOG_DEFAULT, "SendData: input buffer is nullptr.");
        return kIOReturnBadArgument;
    }

    // データ送信
    kern_return_t ret = kIOReturnSuccess;
    uint32_t bytesTransferredPipe = 0;
    size_t cursor = 0;
    while (cursor < size) {
        // 一度の転送データはパケット最大サイズを超えないようにする
        uint32_t sendSize = fmin(outMaxPacketSize, size - cursor);
        memcpy(ivars->outDataAddr, &(buffer[cursor]), sendSize);
        ret = ivars->outPipe->IO(ivars->outData,
                                 sendSize,
                                 &bytesTransferredPipe,
                                 ft232hIOTimeout);
        if (ret != kIOReturnSuccess) {
            os_log(OS_LOG_DEFAULT, "Error! ret:0x%{public}08x", ret);
            return ret;
        }
        cursor += bytesTransferredPipe;
    }
    return ret;
}

ここで、 DumpBuffer メソッドは、バッファデータをログ出力するための自作メソッドです。
(実装コードについては割愛させていただきます。)

 

◇データ受信メソッドの実装

入力用パイプを通じてデバイスからデータ受信します。
FT232H では、受信データの先頭に、2バイトのステータスコードが必ず付加されます。
今回は簡易的に、このステータスコードを無視して、単純に受信データのみを取り扱うものとします。


/// データを受信する
/// @param [out] buffer 受信データ
/// @param [out] size 受信サイズ
/// @return 実行結果
/// @attention buffer は本メソッド内で new するため、呼び元で解放処理すること
kern_return_t NFCDriver::RecvData(uint8_t **buffer, size_t *size)
{
    kern_return_t ret = kIOReturnSuccess;
    uint32_t bytesTransferredPipe = 0;

    // 引数チェック
    if (buffer == nullptr || size == nullptr) {
        os_log(OS_LOG_DEFAULT, "RecvData: input buffer is nullptr.");
        return kIOReturnBadArgument;
    }

    // データ受信
    std::vector v;
    bool isComplete = false;
    while (!isComplete) {
        ret = ivars->inPipe->IO(ivars->inData,
                                inMaxPacketSize,
                                &bytesTransferredPipe,
                                ft232hIOTimeout);
        if (ret != kIOReturnSuccess) {
            os_log(OS_LOG_DEFAULT, "RecvData Error! ret:0x%{public}08x", ret);
            return ret;
        }
        // 2バイトのステータスコード以降に受信データがあれば保持しておく
        if (bytesTransferredPipe > 2) {
            v.insert(v.end(), ivars->inDataAddr + 2, ivars->inDataAddr + bytesTransferredPipe);
        }
        // ラインステータスのエラーチェック
        if ((ivars->inDataAddr[1] & 0x1E) != 0x00) {
            // MEMO: 今回はエラー処理は割愛
            os_log(OS_LOG_DEFAULT, "RecvData Line Status Error! Line Status:0x%{public}02x", ivars->inDataAddr[1]);
            break;
        }
        // 完了チェック
        if ((ivars->inDataAddr[1] & 0x60) != 0x00) {
            isComplete = true;
        }
    }

    // 受信データ、サイズをメソッド引数に設定して呼び元へ返却する
    *size = v.size();
    if (v.size() == 0) {
        return ret;
    }
    *buffer = new uint8_t[v.size()];
    std::copy(v.begin(), v.end(), *buffer);

    // for debug 受信データのダンプ
    os_log(OS_LOG_DEFAULT, "RecvData Size: %{public}zu bytes", *size);
    DumpBuffer(*buffer, *size);

    return ret;
}

 

おわりに

今回はドライバからUSBデバイス(FT232H)に通信するための処理を実装しました。
これらのメソッドを使用し、適切な設定及び仕様に合わせたデータ通信することで、 FT232H を介して RC-S660/S と通信することができます。
次回は RC-S660/S との通信処理を実装し、動作を確認していきたいと思います。

 


 

TOP