ナビゲーションリンクをスキップ | |
印刷ビューの終了 | |
![]() |
デバイスドライバの記述 Oracle Solaris 11.1 Information Library (日本語) |
パート I Oracle Solaris プラットフォーム用デバイスドライバの設計
2. Oracle Solaris カーネルとデバイスツリー
22. ドライバのコンパイル、ロード、パッケージ化、およびテスト
23. デバイスドライバのデバッグ、テスト、およびチューニング
開発者は、SCSA HBA のエントリポイント、構造体、および関数をドライバに組み込むだけでなく、ドライバの依存性と構成に関する問題にも対処する必要があります。これらの問題は、構成プロパティー、依存性宣言、状態構造体とコマンド別構造体、モジュール初期化用のエントリポイント、および自動構成エントリポイントに関係しています。
HBA ドライバには、次のヘッダーファイルを含める必要があります。
#include <sys/scsi/scsi.h> #include <sys/ddi.h> #include <sys/sunddi.h>
モジュールが SCSA ルーチンに依存していることをシステムに通知するには、次のコマンドを使用してドライバのバイナリを生成する必要があります。SCSA ルーチンの詳細については、「SCSA HBA インタフェース」を参照してください。
% ld -r xx.o -o xx -N "misc/scsi"
サンプルコードは、QLogic Intelligent SCSI Peripheral デバイス用の簡略化された isp ドライバから入手できます。isp ドライバは、最大 15 台のターゲットデバイスを接続可能で、1 つのターゲットに 8 台までの論理ユニット (LUN) を接続できるワイド SCSI をサポートしています。
HBA ドライバは通常、ターゲットドライバが実行するコマンドごとに状態を保持する構造体を定義する必要があります。このコマンド別構造体のレイアウトは、デバイスドライバの作成者がすべて管理します。レイアウトに必要なのは、ドライバで使用されるハードウェアの機能とソフトウェアのアルゴリズムを反映させることです。
コマンド別構造体の例を次に示します。この章の残りのコードフラグメントは、この構造体を使って HBA インタフェースを示しています。
struct isp_cmd { struct isp_request cmd_isp_request; struct isp_response cmd_isp_response; struct scsi_pkt *cmd_pkt; struct isp_cmd *cmd_forw; uint32_t cmd_dmacount; ddi_dma_handle_t cmd_dmahandle; uint_t cmd_cookie; uint_t cmd_ncookies; uint_t cmd_cookiecnt; uint_t cmd_nwin; uint_t cmd_curwin; off_t cmd_dma_offset; uint_t cmd_dma_len; ddi_dma_cookie_t cmd_dmacookies[ISP_NDATASEGS]; u_int cmd_flags; u_short cmd_slot; u_int cmd_cdblen; u_int cmd_scblen; };
このセクションでは、SCSI HBA ドライバによって実行される操作のエントリポイントについて説明します。
次の SCSI HBA ドライバ用のコードは、代表的な dev_ops(9S) 構造体を示しています。ドライバは、この構造体の devo_bus_ops フィールドを NULL に初期化する必要があります。SCSI HBA ドライバは、特別な目的でリーフドライバインタフェースを提供することがあります。その場合、devo_cb_ops フィールドは cb_ops(9S) 構造体を指すことがあります。この例では、リーフドライバインタフェースはエクスポートされないため、devo_cb_ops フィールドは NULL に初期化されます。
_init(9E) 関数は、ロード可能なモジュールを初期化します。_init() は、ロード可能なモジュール内のほかのすべてのルーチンの前に呼び出されます。
SCSI HBA では、_init() 関数は、mod_install(9F) を呼び出す前に、scsi_hba_init(9F) を呼び出して、HBA ドライバの存在をフレームワークに通知する必要があります。scsi_hba__init() ルーチンがゼロ以外の値を返す場合、_init() はこの値を返します。それ以外の場合、_init() は mod_install(9F) によって返された値を返す必要があります。
ドライバは、mod_install(9F) を呼び出す前に、必要なグローバル状態を初期化します。
mod_install() が失敗した場合、_init() 関数は割り当てられているグローバル資源をすべて解放する必要があります。_init() は、復帰する前に scsi_hba_fini(9F) を呼び出す必要があります。
次の例では、グローバルな mutex を使用して、ドライバのすべてのインスタンスにグローバルなデータの割り当て方法を示しています。このコードでは、グローバルな mutex とソフト状態構造体の情報を宣言しています。グローバルな mutex とソフト状態は、_init() の実行中に初期化されます。
_fini(9E) 関数は、システムが SCSI HBA ドライバをアンロードしようとするときに呼び出されます。_fini() 関数は、mod_remove(9F) を呼び出して、ドライバがアンロード可能かどうかを判定する必要があります。mod_remove() が 0 を返した場合、このモジュールはアンロード可能です。HBA ドライバは、_init(9E) で割り当てられたグローバル資源をすべて解放する必要があります。また、scsi_hba_fini(9F) も呼び出す必要があります。
_fini() は、mod_remove() によって返された値を返す必要があります。
例 18-1 は、SCSI HBA 用のモジュールの初期化を示しています。
例 18-1 SCSI HBA 用のモジュールの初期化
static struct dev_ops isp_dev_ops = { DEVO_REV, /* devo_rev */ 0, /* refcnt */ isp_getinfo, /* getinfo */ nulldev, /* identify */ nulldev, /* probe */ isp_attach, /* attach */ isp_detach, /* detach */ nodev, /* reset */ NULL, /* driver operations */ NULL, /* bus operations */ isp_power, /* power management */ isp_quiesce, /* quiesce */ }; /* * Local static data */ static kmutex_t isp_global_mutex; static void *isp_state; int _init(void) { int err; if ((err = ddi_soft_state_init(&isp_state, sizeof (struct isp), 0)) != 0) { return (err); } if ((err = scsi_hba_init(&modlinkage)) == 0) { mutex_init(&isp_global_mutex, "isp global mutex", MUTEX_DRIVER, NULL); if ((err = mod_install(&modlinkage)) != 0) { mutex_destroy(&isp_global_mutex); scsi_hba_fini(&modlinkage); ddi_soft_state_fini(&isp_state); } } return (err); } int _fini(void) { int err; if ((err = mod_remove(&modlinkage)) == 0) { mutex_destroy(&isp_global_mutex); scsi_hba_fini(&modlinkage); ddi_soft_state_fini(&isp_state); } return (err); }
各デバイスドライバには dev_ops(9S) 構造体が関連付けられています。この構造体により、カーネルはドライバの自動構成エントリポイントを見つけることができます。これらの自動構成ルーチンについては、第 6 章ドライバの自動構成に詳しく説明されています。このセクションでは、そのようなエントリポイントのうち、SCSI HBA ドライバで実行される操作に関連付けられたものについてのみ説明します。このようなエントリポイントには、attach(9E) と detach(9E) があります。
SCSI HBA ドライバの attach(9E) エントリポイントは、デバイスに対してこのドライバのインスタンスを構成および接続するときにいくつかのタスクを実行します。実際のデバイスの一般的なドライバでは、次のオペレーティングシステムとハードウェアに関する問題に対処する必要があります。
ソフト状態構造体
DMA
トランスポート構造体
HBA ドライバの接続
レジスタマッピング
割り込み仕様
割り込み処理
電源管理可能なコンポーネントの作成
接続ステータスのレポート
デバイスインスタンスごとのソフト状態構造体を割り当てる際にエラーが発生した場合、ドライバは慎重にクリーンアップを行う必要があります。
HBA ドライバは、ddi_dma_attr_t 構造体を正しく初期化することで、その DMA エンジンの属性を記述する必要があります。
static ddi_dma_attr_t isp_dma_attr = { DMA_ATTR_V0, /* ddi_dma_attr version */ 0, /* low address */ 0xffffffff, /* high address */ 0x00ffffff, /* counter upper bound */ 1, /* alignment requirements */ 0x3f, /* burst sizes */ 1, /* minimum DMA access */ 0xffffffff, /* maximum DMA access */ (1<<24)-1, /* segment boundary restrictions */ 1, /* scatter-gather list length */ 512, /* device granularity */ 0 /* DMA flags */ };
また、DMA を提供する場合は、そのハードウェアが DMA 対応スロットに取り付けられていることも確認するべきです。
if (ddi_slaveonly(dip) == DDI_SUCCESS) { return (DDI_FAILURE); }
HBA ドライバは、このインスタンスにさらにトランスポート構造体を割り当て、初期化します。tran_hba_private フィールドは、このインスタンスのソフト状態構造体を指すように設定します。特別なプローブカスタマイズが必要ない場合は、tran_tgt_probe フィールドを NULL に設定して、デフォルトの動作を実行できます。
tran = scsi_hba_tran_alloc(dip, SCSI_HBA_CANSLEEP); isp->isp_tran = tran; isp->isp_dip = dip; tran->tran_hba_private = isp; tran->tran_tgt_private = NULL; tran->tran_tgt_init = isp_tran_tgt_init; tran->tran_tgt_probe = scsi_hba_probe; tran->tran_tgt_free = (void (*)())NULL; tran->tran_start = isp_scsi_start; tran->tran_abort = isp_scsi_abort; tran->tran_reset = isp_scsi_reset; tran->tran_getcap = isp_scsi_getcap; tran->tran_setcap = isp_scsi_setcap; tran->tran_init_pkt = isp_scsi_init_pkt; tran->tran_destroy_pkt = isp_scsi_destroy_pkt; tran->tran_dmafree = isp_scsi_dmafree; tran->tran_sync_pkt = isp_scsi_sync_pkt; tran->tran_reset_notify = isp_scsi_reset_notify; tran->tran_bus_quiesce = isp_tran_bus_quiesce tran->tran_bus_unquiesce = isp_tran_bus_unquiesce tran->tran_bus_reset = isp_tran_bus_reset tran->tran_interconnect_type = isp_tran_interconnect_type
HBA ドライバは、デバイスのこのインスタンスを接続し、必要があれば、エラーのクリーンアップを実行します。
i = scsi_hba_attach_setup(dip, &isp_dma_attr, tran, 0); if (i != DDI_SUCCESS) { /* do error recovery */ return (DDI_FAILURE); }
HBA ドライバは、そのデバイスのレジスタをマップします。ドライバでは次の項目を指定する必要があります。
レジスタセットインデックス
デバイスのデータアクセス特性
マップされるレジスタのサイズ
ddi_device_acc_attr_t dev_attributes; dev_attributes.devacc_attr_version = DDI_DEVICE_ATTR_V0; dev_attributes.devacc_attr_dataorder = DDI_STRICTORDER_ACC; dev_attributes.devacc_attr_endian_flags = DDI_STRUCTURE_LE_ACC; if (ddi_regs_map_setup(dip, 0, (caddr_t *)&isp->isp_reg, 0, sizeof (struct ispregs), &dev_attributes, &isp->isp_acc_handle) != DDI_SUCCESS) { /* do error recovery */ return (DDI_FAILURE); }
ドライバはまず、iblock cookie を取得して、ドライバハンドラで使用される mutex をすべて初期化する必要があります。それらの mutex の初期化が完了している場合にのみ、割り込みハンドラを追加できます。
i = ddi_get_iblock_cookie(dip, 0, &isp->iblock_cookie}; if (i != DDI_SUCCESS) { /* do error recovery */ return (DDI_FAILURE); } mutex_init(&isp->mutex, "isp_mutex", MUTEX_DRIVER, (void *)isp->iblock_cookie); i = ddi_add_intr(dip, 0, &isp->iblock_cookie, 0, isp_intr, (caddr_t)isp); if (i != DDI_SUCCESS) { /* do error recovery */ return (DDI_FAILURE); }
高レベルのハンドラが必要な場合、そのようなハンドラを提供するようにドライバをコーディングします。それ以外の場合、ドライバはその接続に失敗できる必要があります。高レベルの割り込み処理については、「高レベルの割り込みの処理」を参照してください。
電源管理を使用すると、すべてのターゲットアダプタの電源レベルが 0 のときにホストバスアダプタの電源のみを切る必要がある場合、HBA ドライバは power(9E) エントリポイントを提供するだけで済みます。第 12 章電源管理を参照してください。HBA ドライバは、デバイスが実装するコンポーネントについて記述するための pm-components(9P) プロパティーも作成する必要があります。
これ以上は何も必要ありません。コンポーネントがデフォルトでアイドル状態になり、電源管理フレームワークのデフォルトの依存性処理によって、ターゲットアダプタの電源が入ると、ホストバスアダプタの電源も確実に入るようになるためです。自動電源管理が自動的に使用可能になる場合、この処理では、すべてのターゲットアダプタの電源が切れると、ホストバスアダプタの電源も切れます。
最後に、HBA ドライバは、デバイスのこのインスタンスが接続され、成功を返すことをレポートします。
ddi_report_dev(dip); return (DDI_SUCCESS);
HBA ドライバは、scsi_hba_detach(9F) の呼び出しなど、標準的な切り離し操作を実行します。