ナビゲーションリンクをスキップ | |
印刷ビューの終了 | |
デバイスドライバの記述 Oracle Solaris 11.1 Information Library (日本語) |
パート I Oracle Solaris プラットフォーム用デバイスドライバの設計
2. Oracle Solaris カーネルとデバイスツリー
22. ドライバのコンパイル、ロード、パッケージ化、およびテスト
23. デバイスドライバのデバッグ、テスト、およびチューニング
以降のセクションでは、64 ビット環境で動作するようにドライバを変換する方法について説明します。ドライバの作成者は、次のタスクの 1 つ以上を実行する必要があることがあります。
固定幅型をハードウェアレジスタに使用します。
固定幅の共通アクセス関数を使用します。
派生型の使用を確認および拡張します。
DDI データ構造体内の変更されたフィールドを確認します。
DDI 関数の変更された引数を確認します。
必要に応じて、ユーザーデータを処理するドライバエントリポイントを変更します。
x86 プラットフォームで 64 ビットの long 型を使用する構造体を確認します。
これらの手順について、以降で詳しく説明します。
各手順の完了後に、コンパイラの警告をすべて修正し、lint を使用してほかの問題を検索します。64 ビットの問題を検索するときは、-Xarch=v9 および -errchk=longptr64 を指定して、SC5.0 (またはそれ以降の) バージョンの lint を使用してください。
注 - LP64 用の変換時のコンパイル警告を無視しないでください。以前は ILP32 環境で無視しても安全だった警告が、重大な問題を示すようになっていることがあります。
すべての手順を完了したら、32 ビットと 64 ビットの両方のモジュールとしてドライバをコンパイルおよびテストします。
ハードウェアデバイスを操作する多くのデバイスドライバは、C データ構造体を使用してハードウェアのレイアウトを記述します。LP64 データモデルでは、long が 64 ビット量になったため、long または unsigned long を使用してハードウェアレジスタを定義するデータ構造体はほぼ確実に正しくありません。最初に <sys/inttypes.h> を含め、このクラスのデータ構造体を更新して、long の代わりに int32_t または uint32_t を 32 ビットのデバイスデータに使用します。この方法により、32 ビットデータ構造体のバイナリレイアウトが保持されます。たとえば、次の構造体を変更するとします。
struct device_regs { ulong_t addr; uint_t count; }; /* Only works for ILP32 compilation */
変更後:
struct device_regs { uint32_t addr; uint32_t count; }; /* Works for any data model */
Oracle Solaris DDI では、アクセス関数でデバイスレジスタにアクセスすることにより、複数のプラットフォームでの移植性を確保できます。以前は、DDI 共通アクセス関数はバイトやワードなどでデータのサイズを指定しました。たとえば、32 ビット量にアクセスするには、ddi_getl( 9F) を使用します。この関数は 64 ビットの DDI 環境では使用できず、動作するビット数を指定するバージョンの関数に置き換えられています。
これらのルーチンは、ドライバの作成者が早期に採用できるように、Solaris 2.6 オペレーティング環境の 32 ビットカーネルに追加されました。たとえば、32 ビットと 64 ビットの両方のカーネルに移植できるようにするには、ドライバは ddi_getl(9F) ではなく ddi_get32(9F) を使用して 32 ビットのデータにアクセスする必要があります。
共通アクセスルーチンはすべて、同等の固定幅のルーチンに置き換えられています。詳細は、ddi_get8(9F)、ddi_put8(9F)、 ddi_rep_get8(9F)、および ddi_rep_put8(9F) のマニュアルページを参照してください。
可能な場合は size_t などのシステム派生型を使用して、結果として得られる変数が関数間で渡されるときに意味をなすようにしてください。新しい派生型である uintptr_t または intptr_t は、ポインタの整数型として使用してください。
固定幅の整数型は、バイナリデータ構造体またはハードウェアレジスタのサイズを明示的に表すのに便利です。一方、int などの C 言語の基本データ型は、ループカウンタまたはファイル記述子で引き続き使用できます。
一部のシステム派生型は、32 ビットシステムでは 32 ビット量を表しますが、64 ビットシステムでは 64 ビット量を表します。この方法でサイズを変更する派生型には、clock_t、 daddr_t、 dev_t、 ino_t、 intptr_t、 off_t、 size_t、 ssize_t、 time_t、 uintptr_t、 および timeout_id_t があります。
これらの派生型を使用するドライバを設計するときは、特にドライバがこれらの値を固定幅型などの別の派生型の変数に割り当てる場合、これらの型の使用に特別な注意を払ってください。
buf(9S) など、DDI データ構造体内の一部のフィールドのデータ型が変更されました。これらのデータ構造体を使用するドライバでは、これらのフィールドが適切に使用されていることを確認するようにしてください。大幅に変更されたデータ構造体とフィールドの一覧を下に示します。
下に一覧表示したフィールドは転送サイズに関係しています。転送サイズは 4G バイトを超えることができるようになりました。
size_t b_bcount; /* was type unsigned int */ size_t b_resid; /* was type unsigned int */ size_t b_bufsize; /* was type long */
ddi_dma_attr(9S) 構造体は、DMA エンジンとデバイスの属性を定義します。これらの属性はレジスタサイズを指定するため、基本型の代わりに固定幅データ型が使用されるようになりました。
uint32_t dmac_address; /* was type unsigned long */ size_t dmac_size; /* was type u_int */
ddi_dma_cookie(9S) 構造体には 32 ビットの DMA アドレスが含まれているため、アドレスの定義には固定幅データ型が使用されるようになりました。サイズは、size_t として定義し直されました。
uint_t sts_rqpkt_state; /* was type u_long */ uint_t sts_rqpkt_statistics; /* was type u_long */
構造体内のこれらのフィールドは拡張する必要がなく、32 ビット量として定義し直されました。
uint_t pkt_flags; /* was type u_long */ int pkt_time; /* was type long */ ssize_t pkt_resid; /* was type long */ uint_t pkt_state; /* was type u_long */ uint_t pkt_statistics; /* was type u_long */
scsi_pkt(9S) 構造体内の pkt_flags、pkt_state、および pkt_statistics フィールドは拡張する必要がないため、これらのフィールドは 32 ビット整数として定義し直されました。データ転送サイズの pkt_resid フィールドは拡張するため、ssize_t として定義し直されました。
このセクションでは、変更された DDI 関数の引数のデータ型について説明します。
struct buf *getrbuf(int sleepflag);
前のリリースでは、sleepflag は long 型として定義されていました。
int drv_getparm(unsigned int parm, void *value_p);
前のリリースでは、value_p は unsigned long 型として定義されていました。64 ビットカーネルでは、drv_getparm(9F) は 32 ビット量と 64 ビット量の両方を取得できます。インタフェースではこれらの量のデータ型は定義されないので、単純なプログラミングエラーが起こる可能性があります。
次の新しいルーチンのほうが安全です。
clock_t ddi_get_lbolt(void); time_t ddi_get_time(void); cred_t *ddi_get_cred(void); pid_t ddi_get_pid(void);
ドライバの作成者は、できるかぎり drv_getparm(9F) の代わりにこれらのルーチンを使用してください。
void delay(clock_t ticks); timeout_id_t timeout(void (*func)(void *), void *arg, clock_t ticks);
delay(9F) および timeout(9F) ルーチンの ticks 引数は、long から clock_t に変更されました。
struct map *rmallocmap(size_t mapsize); struct map *rmallocmap_wait(size_t mapsize);
rmallocmap(9F) および rmallocmap_wait(9F) ルーチンの mapsize 引数は、ulong_t から size_t に変更されました。
struct buf *scsi_alloc_consistent_buf(struct scsi_address *ap, struct buf *bp, size_t datalen, uint_t bflags, int (*callback )(caddr_t), caddr_t arg);
前のリリースでは、datalen は int として定義され、bflags は ulong として定義されていました。
int uiomove(caddr_t address, size_t nbytes, enum uio_rw rwflag, uio_t *uio_p);
nbytes 引数は long 型として定義されていましたが、nbytes はサイズをバイト単位で表すため、size_t のほうが適切です。
int cv_timedwait(kcondvar_t *cvp, kmutex_t *mp, clock_t timeout); int cv_timedwait_sig(kcondvar_t *cvp, kmutex_t *mp, clock_t timeout);
前のリリースでは、cv_timedwait(9F) および cv_timedwait_sig(9F) ルーチンの timeout 引数は、long 型として定義されていました。これらのルーチンは時間をティックで表すため、clock_t のほうが適切です。
int ddi_device_copy(ddi_acc_handle_t src_handle, caddr_t src_addr, ssize_t src_advcnt, ddi_acc_handle_t dest_handle, caddr_t dest_addr, ssize_t dest_advcnt, size_t bytecount, uint_t dev_datasz);
src_advcnt 、dest_advcnt、dev_datasz の各引数では、型が変更されました。これらの引数は、以前はそれぞれ long、long、および ulong_t として定義されました。
int ddi_device_zero(ddi_acc_handle_t handle, caddr_t dev_addr, size_t bytecount, ssize_t dev_advcnt, uint_t dev_datasz):
前のリリースでは、dev_advcnt は long 型として、dev_datasz は ulong_t 型として定義されていました。
int ddi_dma_mem_alloc(ddi_dma_handle_t handle, size_t length, ddi_device_acc_attr_t *accattrp, uint_t flags, int (*waitfp)(caddr_t), caddr_t arg, caddr_t *kaddrp, size_t *real_length, ddi_acc_handle_t *handlep);
前のリリースでは、 length、flags、および real_length は、uint_t、ulong_t、および uint_t * 型で定義されていました。
デバイスドライバが long またはポインタを含むデータ構造体を、ioctl(9E)、devmap(9E)、または mmap(9E) を使用する 32 ビットアプリケーションと共有し、そのドライバが 64 ビットカーネル用に再コンパイルされている場合、データ構造体のバイナリレイアウトは互換性がありません。フィールドが現在 long 型で定義され、64 ビットのデータ項目が使用されていない場合は、32 ビット量のままのデータ型を使用するようにデータ構造体を変更します (int および unsigned int)。それ以外の場合、ドライバは、ILP32 と LP64 の構造体の形状の違いを認識して、アプリケーションとカーネルの間でモデルの不一致が発生しているかどうかを判定する必要があります。
データモデルの潜在的な相違を処理するには、ユーザーアプリケーションと直接対話する ioctl()、devmap()、および mmap() の各ドライバエントリポイントを記述して、引数がカーネルと同じデータモデルを使用するアプリケーションから来たかどうかを判定する必要があります。
モデルの不一致がアプリケーションとドライバの間に存在するかどうかを判定するために、ドライバは FMODELS マスクを使用して、ioctl () mode 引数からモデル型を判定します。次の値で OR を使用してモードを有効にし、アプリケーションのデータモデルを特定します。
「64 ビットに対応したデバイスドライバに対する入出力制御のサポート」のコード例は、ddi_model_convert_from(9F) を使用してこの状況を処理する方法を示しています。
64 ビットドライバと 32 ビットアプリケーションでメモリーを共有できるようにするには、64 ビットドライバで生成されるバイナリレイアウトが 32 ビットアプリケーションで消費されるレイアウトと同じである必要があります。アプリケーションにエクスポートされるマッピング済みのメモリーには、データモデル依存のデータ構造体を含める必要があることがあります。
カーネルのデータモデルが変わってもデバイスレジスタのサイズは変わらないため、この問題に直面するメモリーマッピングデバイスはほとんどありません。ただし、ユーザーアドレス空間にマッピングをエクスポートする一部の擬似デバイスでは、別のデータ構造体を ILP32 または LP64 アプリケーションにエクスポートできます。データモデルの不一致が発生しているかどうかを判定するために、devmap(9E) は model パラメータを使用して、アプリケーションが想定するデータモデルを記述します。model パラメータは、次のいずれかの値に設定されます。
model パラメータは、変換せずに ddi_model_convert_from(9F) ルーチンまたは STRUCT_INIT() に渡すことができます。「32 ビットと 64 ビットのデータ構造体マクロ」を参照してください。
mmap(9E) にはデータモデル情報を渡すために使用できるパラメータがないため、ドライバの mmap(9E) エントリポイントを記述して、新しい DDI 関数 ddi_model_convert_from(9F) を使用できます。この関数は、次のいずれかの値を返して、アプリケーションのデータ型モデルを示します。
DDI_FAILURE – 関数は mmap(9E) から呼び出されませんでした
ioctl() および devmap() の場合と同様、モデルのビットを ddi_model_convert_from(9F) に渡して、データ変換が必要かどうかを判定できます。または、モデルを STRUCT_INIT() に渡すことができます。
または、devmap(9E) エントリポイントをサポートするようにデバイスドライバを移行します。
x86 プラットフォームでは、uint64_t などの 64 ビット long 型を使用する構造体を慎重に確認してください。配置やサイズが、32 ビットモードと 64 ビットモードのコンパイルで異なることがあります。次のような例を考えます。
#include <studio> #include <sys> struct myTestStructure { uint32_t my1stInteger; uint64_t my2ndInteger; }; main() { struct myTestStructure a; printf("sizeof myTestStructure is: %d\n", sizeof(a)); printf("offset to my2ndInteger is: %d\n", (uintptr_t)&a.bar - (uintptr_t)&a); }
32 ビットシステムでは、この例の場合は次の結果が表示されます。
sizeof myTestStructure is: 12 offset to my2ndInteger is: 4
逆に、64 ビットシステムでは、この例の場合は次の結果が表示されます。
sizeof myTestStructure is: 16 offset to my2ndInteger is: 8
したがって、32 ビットアプリケーションと 64 ビットアプリケーションでは構造体が異なります。その結果、同じ構造体を 32 ビット環境と 64 ビット環境の両方で実行しようとすると、問題が発生することがあります。この状況は、特に構造体が ioctl() 呼び出しを介してカーネルとの間で受け渡しされる場合によく起こります。