| ナビゲーションリンクをスキップ | |
| 印刷ビューの終了 | |
|
デバイスドライバの記述 Oracle Solaris 11.1 Information Library (日本語) |
パート I Oracle Solaris プラットフォーム用デバイスドライバの設計
2. Oracle Solaris カーネルとデバイスツリー
22. ドライバのコンパイル、ロード、パッケージ化、およびテスト
23. デバイスドライバのデバッグ、テスト、およびチューニング
デバイスコンテキスト管理を実行する場合の基本手順は、次のとおりです。
devmap_callback_ctl(9S) 構造体を定義します。
必要であれば、デバイスコンテキストを保存するための領域を割り当てます。
devmap_devmem_setup(9F) を使用して、デバイスへのユーザーマッピングとドライバ通知を設定します。
devmap_load(9F) と devmap_unload(9F) を使用してデバイスへのユーザーアクセスを管理します。
必要であれば、デバイスコンテキスト構造体を解放します。
デバイスドライバは、デバイスコンテキスト管理用のエントリポイントルーチンに関する情報をシステムに知らせるために、devmap_callback_ctl(9S) 構造体を割り当てて初期化する必要があります。
この構造体で使用される構文は、次のとおりです。
struct devmap_callback_ctl {
int devmap_rev;
int (*devmap_map)(devmap_cookie_t dhp, dev_t dev,
uint_t flags, offset_t off, size_t len, void **pvtp);
int (*devmap_access)(devmap_cookie_t dhp, void *pvtp,
offset_t off, size_t len, uint_t type, uint_t rw);
int (*devmap_dup)(devmap_cookie_t dhp, void *pvtp,
devmap_cookie_t new_dhp, void **new_pvtp);
void (*devmap_unmap)(devmap_cookie_t dhp, void *pvtp,
offset_t off, size_t len, devmap_cookie_t new_dhp1,
void **new_pvtp1, devmap_cookie_t new_dhp2,
void **new_pvtp2);
};devmap_callback_ctl 構造体のバージョン番号。このバージョン番号は DEVMAP_OPS_REV に設定する必要があります。
ドライバの devmap_map(9E) エントリポイントのアドレスに設定する必要があります。
ドライバの devmap_access(9E) エントリポイントのアドレスに設定する必要があります。
ドライバの devmap_dup(9E) エントリポイントのアドレスに設定する必要があります。
ドライバの devmap_unmap(9E) エントリポイントのアドレスに設定する必要があります。
デバイスコンテキストの管理に使用されるエントリポイントは、次のとおりです。
devmap(9E) の構文は次のとおりです。
int xxdevmap_map(devmap_cookie_t handle, dev_t dev, uint_t flags,
offset_t offset, size_t len, void **new-devprivate);
devmap_map() エントリポイントは、ドライバがその devmap() エントリポイントから返り、システムがデバイスメモリーへのユーザーマッピングを確立したあとで呼び出されます。devmap() エントリポイントを使用すると、ドライバは追加の処理を実行したり、マッピング固有の非公開データを割り当てたりできます。たとえば、コンテキストの切り替えをサポートするには、ドライバはコンテキスト構造体を割り当てる必要があります。続いてドライバは、そのコンテキスト構造体をマッピングに関連付ける必要があります。
システムは、ドライバが割り当て済みの非公開データへのポインタを *new-devprivate に返すことを予期します。ドライバは、マッピングの範囲を定義する offset と len をその非公開データ内に格納する必要があります。あとでシステムが devmap_unmap(9E) を呼び出したときに、ドライバはこの情報に基づいて、マッピングの対応づけを解除する量がどれだけになるのかを判断します。
flags は、ドライバがマッピングの非公開コンテキストを割り当てるかどうかを示します。たとえば、flags が MAP_PRIVATE に設定されている場合、ドライバはデバイスコンテキストを格納するためのメモリー領域を割り当てることができます。MAP_SHARED が設定されている場合、ドライバは共有領域へのポインタを返します。
次の例は、devmap() エントリポイントを示したものです。ドライバは新しいコンテキスト構造体を割り当てています。続いてドライバは、エントリポイントから渡された関連パラメータを保存しています。次に、マッピングに新しいコンテキストを割り当てるために、コンテキストの割り当てを行うか、すでに存在している共有コンテキストにマッピングを関連付けています。マッピングがデバイスにアクセスする最小時間間隔は、1 ミリ秒に設定されています。
例 11-1 devmap() ルーチンの使用
static int
int xxdevmap_map(devmap_cookie_t handle, dev_t dev, uint_t flags,
offset_t offset, size_t len, void **new_devprivate)
{
struct xxstate *xsp = ddi_get_soft_state(statep,
getminor(dev));
struct xxctx *newctx;
/* create a new context structure */
newctx = kmem_alloc(sizeof (struct xxctx), KM_SLEEP);
newctx->xsp = xsp;
newctx->handle = handle;
newctx->offset = offset;
newctx->flags = flags;
newctx->len = len;
mutex_enter(&xsp->ctx_lock);
if (flags & MAP_PRIVATE) {
/* allocate a private context and initialize it */
newctx->context = kmem_alloc(XXCTX_SIZE, KM_SLEEP);
xxctxinit(newctx);
} else {
/* set a pointer to the shared context */
newctx->context = xsp->ctx_shared;
}
mutex_exit(&xsp->ctx_lock);
/* give at least 1 ms access before context switching */
devmap_set_ctx_timeout(handle, drv_usectohz(1000));
/* return the context structure */
*new_devprivate = newctx;
return(0);
}
devmap_access(9E) エントリポイントは、変換が無効になっているマッピングへのアクセスが発生した場合に呼び出されます。マッピングの変換が無効化されるのは、devmap_devmem_setup(9F) への応答として mmap(2) でマッピングが作成された場合、fork(2) によってマッピングが複製された場合、または devmap_unload(9F) 呼び出しによってマッピングが明示的に無効化された場合です。
devmap_access() の構文は次のとおりです。
int xxdevmap_access(devmap_cookie_t handle, void *devprivate,
offset_t offset, size_t len, uint_t type, uint_t rw);
各表記の意味は次のとおりです。
ユーザープロセスからアクセスされたマッピングのマッピングハンドル。
マッピングに関連付けられたドライバ非公開データへのポインタ。
アクセスされたマッピング内のオフセット。
アクセス対象となるメモリーのバイト単位の長さ。
アクセス操作のタイプ。
アクセスの向きを指定します。
システムは、devmap_access(9E) が devmap_do_ctxmgt(9F) と devmap_default_access(9F) のいずれかを呼び出してメモリーアドレス変換をロードしたあとで、devmap_access() が復帰することを想定します。コンテキスト切り替えをサポートするマッピングの場合、デバイスドライバは devmap_do_ctxmgt() を呼び出します。このルーチンには、devmap_access(9E) からのすべてのパラメータと、コンテキスト切り替えを処理するドライバエントリポイント devmap_contextmgt(9E) へのポインタが渡されます。コンテキスト切り替えをサポートしないマッピングの場合、ドライバは devmap_default_access(9F) を呼び出します。devmap_default_access() の目的は、devmap_load(9F) を呼び出してユーザー変換をロードすることです。
次の例は、devmap_access(9E) エントリポイントを示したものです。マッピングは 2 つの領域に分割されています。オフセット OFF_CTXMG から CTXMGT_SIZE バイトの長さの領域は、コンテキスト管理をサポートします。 マッピングの残りの部分はデフォルトアクセスをサポートします。
例 11-2 devmap_access() ルーチンの使用
#define OFF_CTXMG 0
#define CTXMGT_SIZE 0x20000
static int
xxdevmap_access(devmap_cookie_t handle, void *devprivate,
offset_t off, size_t len, uint_t type, uint_t rw)
{
offset_t diff;
int error;
if ((diff = off - OFF_CTXMG) >= 0 && diff < CTXMGT_SIZE) {
error = devmap_do_ctxmgt(handle, devprivate, off,
len, type, rw, xxdevmap_contextmgt);
} else {
error = devmap_default_access(handle, devprivate,
off, len, type, rw);
}
return (error);
}
devmap_contextmgt(9E) の構文は次のとおりです。
int xxdevmap_contextmgt(devmap_cookie_t handle, void *devprivate,
offset_t offset, size_t len, uint_t type, uint_t rw);
devmap_contextmgt() は、デバイスに現在アクセスしているマッピングのハンドルを指定して devmap_unload(9F) を呼び出します。このアプローチにより、そのマッピングの変換が無効化されます。このアプローチにより、現在のマッピングへのアクセスが次回発生したときに、そのマッピングで devmap_access(9E) の呼び出しが発生するようになります。アクセスイベントが発生する原因となったマッピングのマッピング変換を有効化する必要があります。したがって、ドライバは、アクセスを要求しているプロセスのデバイスコンテキストを復元する必要があります。さらにドライバは、このエントリポイントへの呼び出しを生成したマッピングの handle を指定して devmap_load(9F) を呼び出す必要もあります。
devmap_load() 呼び出しによってマッピング変換が有効化されたマッピング部分へのアクセスが発生しても、devmap_access() への呼び出しは生成されません。その後、devmap_unload() を呼び出すと、マッピング変換が無効化されます。この呼び出しによって、devmap_access () がふたたび呼び出されるようになります。
devmap_load() または devmap_unl ad() のいずれかからエラーが返された場合、devmap_contextmgt() はただちにそのエラーを返します。デバイスドライバがデバイスコンテキストの復元中にハードウェア障害を検出した場合、-1 が返されます。それ以外の場合、アクセス要求の処理が成功したら、devmap_contextmgt() はゼロを返します。devmap_contextmgt() からゼロ以外の値が返された場合、SIGBUS または SIGSEGV がプロセスに送信されます。
次の例は、ページ数 1 のデバイスコンテキストを管理する方法を示したものです。
注 - xxctxsave() と xxctxrestore() は、コンテキストを保存および復元するためのデバイス依存関数です。xxctxsave() は、レジスタからデータを読み取り、そのデータをソフト状態構造体に保存します。 xxctxrestore() は、ソフト状態構造体に保存されたデータを取得し、そのデータをデバイスのレジスタに書き込みます。読み取り、書き込み、保存はすべて、DDI/DKI データアクセスルーチンを使用して実行します。
例 11-3 devmap_contextmgt() ルーチンの使用
static int
xxdevmap_contextmgt(devmap_cookie_t handle, void *devprivate,
offset_t off, size_t len, uint_t type, uint_t rw)
{
int error;
struct xxctx *ctxp = devprivate;
struct xxstate *xsp = ctxp->xsp;
mutex_enter(&xsp->ctx_lock);
/* unload mapping for current context */
if (xsp->current_ctx != NULL) {
if ((error = devmap_unload(xsp->current_ctx->handle,
off, len)) != 0) {
xsp->current_ctx = NULL;
mutex_exit(&xsp->ctx_lock);
return (error);
}
}
/* Switch device context - device dependent */
if (xxctxsave(xsp->current_ctx, off, len) < 0) {
xsp->current_ctx = NULL;
mutex_exit(&xsp->ctx_lock);
return (-1);
}
if (xxctxrestore(ctxp, off, len) < 0){
xsp->current_ctx = NULL;
mutex_exit(&xsp->ctx_lock);
return (-1);
}
xsp->current_ctx = ctxp;
/* establish mapping for new context and return */
error = devmap_load(handle, off, len, type, rw);
if (error)
xsp->current_ctx = NULL;
mutex_exit(&xsp->ctx_lock);
return (error);
}
devmap_dup(9E) エントリポイントは、fork(2) を呼び出すユーザープロセスなどによってデバイスマッピングが複製されるときに呼び出されます。ドライバからは、新しいマッピングのための新しいドライバ非公開データが生成されることが予期されます。
devmap_dup() の構文は次のとおりです。
int xxdevmap_dup(devmap_cookie_t handle, void *devprivate,
devmap_cookie_t new-handle, void **new-devprivate);
各表記の意味は次のとおりです。
複製されるマッピングのマッピングハンドル。
複製されたマッピングのマッピングハンドル。
複製されるマッピングに関連付けられたドライバ非公開データへのポインタ。
新しいマッピングの新しいドライバ非公開データを指すように設定します。
devmap_dup() で作成されたマッピングではデフォルトで、マッピング変換が無効化されています。マッピング変換が無効になっていると、マッピングへのアクセスがはじめて発生したときに devmap_access(9E) エントリポイントが強制的に呼び出されます。
次の例は、典型的な devmap_dup() ルーチンを示したものです。
例 11-4 devmap_dup() ルーチンの使用
static int
xxdevmap_dup(devmap_cookie_t handle, void *devprivate,
devmap_cookie_t new_handle, void **new_devprivate)
{
struct xxctx *ctxp = devprivate;
struct xxstate *xsp = ctxp->xsp;
struct xxctx *newctx;
/* Create a new context for the duplicated mapping */
newctx = kmem_alloc(sizeof (struct xxctx), KM_SLEEP);
newctx->xsp = xsp;
newctx->handle = new_handle;
newctx->offset = ctxp->offset;
newctx->flags = ctxp->flags;
newctx->len = ctxp->len;
mutex_enter(&xsp->ctx_lock);
if (ctxp->flags & MAP_PRIVATE) {
newctx->context = kmem_alloc(XXCTX_SIZE, KM_SLEEP);
bcopy(ctxp->context, newctx->context, XXCTX_SIZE);
} else {
newctx->context = xsp->ctx_shared;
}
mutex_exit(&xsp->ctx_lock);
*new_devprivate = newctx;
return(0);
}
devmap_unmap(9E) エントリポイントは、マッピングの対応づけを解除するときに呼び出されます。対応づけの解除が発生する可能性があるのは、ユーザープロセスが終了するときと、munmap(2) システムコールが呼び出されたときです。
devmap_unmap() の構文は次のとおりです。
void xxdevmap_unmap(devmap_cookie_t handle, void *devprivate,
offset_t off, size_t len, devmap_cookie_t new-handle1,
void **new-devprivate1, devmap_cookie_t new-handle2,
void **new-devprivate2);
各表記の意味は次のとおりです。
解放されるマッピングのマッピングハンドル。
マッピングに関連付けられたドライバ非公開データへのポインタ。
対応づけ解除の開始位置となる、論理デバイスメモリー内のオフセット。
対応づけ解除の対象となるメモリーのバイト単位の長さ。
off - 1 で終わる新しい領域を記述するためにシステムが使用するハンドル。new-handle1 の値は NULL でもかまいません。
ドライバによる、off -1 で終わる新しい領域の非公開ドライバマッピングデータの設定先となるポインタ。new-handle1 が NULL の場合、new-devprivate1 は無視されます。
off + len から始まる新しい領域を記述するためにシステムが使用するハンドル。new-handle2 の値は NULL でもかまいません。
ドライバによる、off + len から始まる新しい領域のドライバ非公開マッピングデータの設定先となるポインタ。new-handle2 が NULL の場合、new-devprivate2 は無視されます。
マッピングの作成時には、devmap_unmap() ルーチンが devmap_map(9E) または devmap_dup(9E) のいずれかによって割り当てられたすべてのドライバ非公開リソースを解放することが予期されます。対応づけ解除の対象がマッピングの一部のみである場合、ドライバは、残りのマッピングに対する新しい非公開データを割り当てたあとで、古い非公開データを解放する必要があります。解放されるマッピングのハンドルが、有効な変換を含むマッピングを指している場合でも、そのハンドルを指定して devmap_unload(9F) を呼び出す必要はありません。ただし、あとで devmap_access(9E) で問題が発生しないように、現在のマッピング表現は「現在のマッピングがない」状態に設定されていることを、デバイスドライバ内で確認するようにしてください。
次の例は、典型的な devmap_unmap() ルーチンを示したものです。
例 11-5 devmap_unmap() ルーチンの使用
static void
xxdevmap_unmap(devmap_cookie_t handle, void *devprivate,
offset_t off, size_t len, devmap_cookie_t new_handle1,
void **new_devprivate1, devmap_cookie_t new_handle2,
void **new_devprivate2)
{
struct xxctx *ctxp = devprivate;
struct xxstate *xsp = ctxp->xsp;
mutex_enter(&xsp->ctx_lock);
/*
* If new_handle1 is not NULL, we are unmapping
* at the end of the mapping.
*/
if (new_handle1 != NULL) {
/* Create a new context structure for the mapping */
newctx = kmem_alloc(sizeof (struct xxctx), KM_SLEEP);
newctx->xsp = xsp;
if (ctxp->flags & MAP_PRIVATE) {
/* allocate memory for the private context and copy it */
newctx->context = kmem_alloc(XXCTX_SIZE, KM_SLEEP);
bcopy(ctxp->context, newctx->context, XXCTX_SIZE);
} else {
/* point to the shared context */
newctx->context = xsp->ctx_shared;
}
newctx->handle = new_handle1;
newctx->offset = ctxp->offset;
newctx->len = off - ctxp->offset;
*new_devprivate1 = newctx;
}
/*
* If new_handle2 is not NULL, we are unmapping
* at the beginning of the mapping.
*/
if (new_handle2 != NULL) {
/* Create a new context for the mapping */
newctx = kmem_alloc(sizeof (struct xxctx), KM_SLEEP);
newctx->xsp = xsp;
if (ctxp->flags & MAP_PRIVATE) {
newctx->context = kmem_alloc(XXCTX_SIZE, KM_SLEEP);
bcopy(ctxp->context, newctx->context, XXCTX_SIZE);
} else {
newctx->context = xsp->ctx_shared;
}
newctx->handle = new_handle2;
newctx->offset = off + len;
newctx->flags = ctxp->flags;
newctx->len = ctxp->len - (off + len - ctxp->off);
*new_devprivate2 = newctx;
}
if (xsp->current_ctx == ctxp)
xsp->current_ctx = NULL;
mutex_exit(&xsp->ctx_lock);
if (ctxp->flags & MAP_PRIVATE)
kmem_free(ctxp->context, XXCTX_SIZE);
kmem_free(ctxp, sizeof (struct xxctx));
}
ユーザープロセスが mmap(2) を使用してデバイスへのマッピングを要求すると、ドライバの segmap(9E) エントリポイントが呼び出されます。ドライバでデバイスコンテキストを管理する必要がある場合、ドライバはメモリーマッピングの設定時に ddi_devmap_segmap(9F) または devmap_setup(9F) を使用する必要があります。どちらの関数もドライバの devmap(9E) エントリポイントを呼び出しますが、このエントリポイントでは、devmap_devmem_setup(9F) を使用してデバイスメモリーとユーザーマッピングが関連付けられます。デバイスメモリーのマッピング方法の詳細については、第 10 章デバイスメモリーおよびカーネルメモリーのマッピングを参照してください。
ドライバは、ユーザーマッピングへのアクセスの通知を受け取ることができるように、devmap_callback_ctl(9S) のエントリポイントの情報をシステムに知らせる必要があります。ドライバは、システムに情報を知らせるために、devmap_callback_ctl(9S) 構造体へのポインタを devmap_devmem_setup(9F) に指定します。devmap_callback_ctl(9S) 構造体は、一連のコンテキスト管理用エントリポイントを記述します。これらのエントリポイントがシステムによって呼び出され、デバイスマッピングに関するイベントを管理するようにデバイスドライバに通知されます。
システムは、各マッピングにマッピングハンドルを関連付けます。このハンドルは、コンテキスト管理用の各エントリポイントに渡されます。マッピングハンドルを使用すると、マッピング変換を無効化および有効化できます。ドライバがマッピング変換を無効化すると、その後マッピングへのアクセスが発生するたびに、それがドライバに通知されます。ドライバがマッピング変換を有効化すると、マッピングへのアクセスが発生してもドライバにはそれが通知されなくなります。マッピングの作成時には常にマッピング変換が無効化されますが、これは、マッピングへのアクセスがはじめて発生したときにドライバに通知が届くようにするためです。
次の例は、デバイスコンテキスト管理インタフェースを使用してマッピングを設定する方法を示したものです。
例 11-6 コンテキスト管理サポート付きの devmap (9E) エントリポイント
static struct devmap_callback_ctl xx_callback_ctl = {
DEVMAP_OPS_REV, xxdevmap_map, xxdevmap_access,
xxdevmap_dup, xxdevmap_unmap
};
static int
xxdevmap(dev_t dev, devmap_cookie_t handle, offset_t off,
size_t len, size_t *maplen, uint_t model)
{
struct xxstate *xsp;
uint_t rnumber;
int error;
/* Setup data access attribute structure */
struct ddi_device_acc_attr xx_acc_attr = {
DDI_DEVICE_ATTR_V0,
DDI_NEVERSWAP_ACC,
DDI_STRICTORDER_ACC
};
xsp = ddi_get_soft_state(statep, getminor(dev));
if (xsp == NULL)
return (ENXIO);
len = ptob(btopr(len));
rnumber = 0;
/* Set up the device mapping */
error = devmap_devmem_setup(handle, xsp->dip, &xx_callback_ctl,
rnumber, off, len, PROT_ALL, 0, &xx_acc_attr);
*maplen = len;
return (error);
}
有効なマッピング変換を持たないメモリーマッピングされた領域内のアドレスにユーザープロセスがアクセスすると、デバイスドライバに通知されます。アクセスイベントが発生した場合、デバイスに現在アクセスしているプロセスのマッピング変換を無効化する必要があります。デバイスへのアクセスを要求したプロセスのデバイスコンテキストを復元する必要があります。さらに、アクセスを要求しているプロセスのマッピング変換を有効化する必要があります。
マッピング変換を有効化および無効化するには、関数 devmap_load(9F) および devmap_unload(9F) を使用します。
devmap_load(9F) の構文は次のとおりです。
int devmap_load(devmap_cookie_t handle, offset_t offset,
size_t len, uint_t type, uint_t rw);
devmap_load() は、handle、offset、および len で指定されるマッピングページのマッピング変換を有効化します。ドライバは、これらのページのマッピング変換を有効化することで、これらのマッピングのページへのアクセスを横取りしないようシステムに指示しています。さらにシステムは、デバイスドライバへの通知を行わないでアクセスの処理続行を許可してはいけません。
アクセスが完了するためには、アクセスイベントを生成したマッピングのオフセットとハンドルを指定して devmap_load() を呼び出す必要があります。このハンドルで devmap_load(9F) が呼び出されなかった場合、マッピング変換が有効化されないため、プロセスは SIGBUS を受信します。
devmap_unload(9F) の構文は次のとおりです。
int devmap_unload(devmap_cookie_t handle, offset_t offset, size_t len);
devmap_unload() は、handle、offset、および len で指定されるマッピングページのマッピング変換を無効化します。デバイスドライバは、マッピングのこれらのページのマッピング変換を無効化することで、これらのページへのアクセスを横取りするようシステムに指示しています。さらにシステムは次回、devmap_access(9E) エントリポイントを呼び出すことで、これらのマッピングページへのアクセスが発生したことをデバイスドライバに通知する必要があります。
どちらの関数の場合も、要求が影響を与えるのは、offset を含むページの全体、offset + len で示される最後のバイトを含むページの全体、およびその 2 つのページの間にあるすべてのページです。デバイスドライバは、マッピングされるデバイスメモリーの各ページについて、ある任意の時点で有効な変換を持つプロセスは 1 つのみであることを確認する必要があります。
成功時には、どちらの関数からもゼロが返されます。一方、マッピング変換を有効化または無効化するときにエラーが発生した場合、そのエラーがデバイスドライバに返されます。デバイスドライバはこのエラーをシステムに返す必要があります。