ioctl(9E) ルーチンは、ユーザースレッドが、デバイスに関連付けられたファイル記述子に対する ioctl(2) システムコールを発行したときに呼び出されます。入出力制御メカニズムとは、デバイス固有のパラメータを取得したり、設定したりするためのものです。このメカニズムは、内部のドライバのソフトウェアフラグを設定するか、またはデバイスにコマンドを書き込むことによって、デバイス固有のモードを設定するために頻繁に使用されます。また、この制御メカニズムは、現在のデバイスの状態に関する情報をユーザーに返すためにも使用されます。つまり、この制御メカニズムは、アプリケーションやドライバで実行する必要のあるすべてのことを実行できます。
int xxioctl(dev_t dev, int cmd, intptr_t arg, int mode, cred_t *credp, int *rvalp);
cmd パラメータは、ioctl(9E) がどのコマンドを実行するべきかを示します。慣例により、入出力制御コマンドが関連付けられているドライバは、そのコマンドのビット 8 から 15 で示されます。通常は、文字の ASCII コードがドライバを表します。ドライバ固有のコマンドはビット 0 から 7 にあります。次の例には、いくつかの入出力コマンドの作成が示されています。
#define XXIOC ('x' << 8) /* 'x' is a character that represents device xx */ #define XX_GET_STATUS (XXIOC | 1) /* get status register */ #define XX_SET_CMD (XXIOC | 2) /* send command */
arg の解釈は、コマンドによって異なります。入出力制御コマンドは、ドライバのドキュメントまたはマニュアルページで説明されています。コマンドはまた、アプリケーションがコマンドの名前、コマンドが何を実行するか、およびコマンドが arg として何を受け入れるか、または返すかを判定できるように、公開ヘッダーファイルでも定義されます。ドライバとの間の arg のデータ転送はすべて、ドライバによって実行される必要があります。
フレームバッファーやディスクなどの特定のクラスのデバイスは、標準的な一連の入出力制御要求をサポートする必要があります。これらの標準の入出力制御インタフェースは、Solaris 8 のリファレンスドキュメントコレクションで説明されています。たとえば、fbio(7I) ではフレームバッファーがサポートする必要のある入出力制御について、また dkio(7I) では標準のディスク入出力制御について説明しています。入出力制御の詳細については、その他の入出力制御を参照してください。
ドライバは、arg のデータをユーザーレベルのアプリケーションからカーネルレベルに転送するために ddi_copyin(9F) を使用する必要があります。ドライバは、カーネルレベルからユーザーレベルにデータを転送するために ddi_copyout(9F) を使用する必要があります。ddi_copyin(9F) または ddi_copyout(9F) を使用しないと、2 つの条件でパニックが発生することがあります。アーキテクチャーによってカーネルとユーザーのアドレス空間が分離されている場合、またはユーザーアドレスがスワップアウトされている場合はパニックが発生します。
ioctl(9E) は通常、サポートされている各 ioctl(9E) 要求に対応するケースを含む switch 文です。
使用例 80 ioctl (9E) ルーチンstatic int xxioctl(dev_t dev, int cmd, intptr_t arg, int mode, cred_t *credp, int *rvalp) { uint8_t csr; struct xxstate *xsp; xsp = ddi_get_soft_state(statep, getminor(dev)); if (xsp == NULL) { return (ENXIO); } switch (cmd) { case XX_GET_STATUS: csr = ddi_get8(xsp->data_access_handle, &xsp->regp->csr); if (ddi_copyout(&csr, (void *)arg, sizeof (uint8_t), mode) != 0) { return (EFAULT); } break; case XX_SET_CMD: if (ddi_copyin((void *)arg, &csr, sizeof (uint8_t), mode) != 0) { return (EFAULT); } ddi_put8(xsp->data_access_handle, &xsp->regp->csr, csr); break; default: /* generic "ioctl unknown" error */ return (ENOTTY); } return (0); }
cmd 変数は、特定のデバイス制御操作を識別します。arg にユーザー仮想アドレスが含まれていると、問題が発生することがあります。ioctl(9E) は、arg によって示されたアプリケーションプログラム内のデータ構造体とドライバの間でデータを転送するために ddi_copyin(9F) または ddi_copyout(9F) を呼び出す必要があります。使用例 80 では、XX_GET_STATUS 要求のケースに対して、xsp->regp->csr の内容が arg 内のアドレスにコピーされます。ioctl(9E) は、成功した要求を行なった ioctl(2) システムコールへの戻り値として任意の整数値を *rvalp に格納できます。負の戻り値 (-1 など) は避けるべきです。多くのアプリケーションプログラムは、負の値が失敗を示すことを前提にしています。
次の例は、前の段落で説明した入出力制御を使用するアプリケーションを示しています。
使用例 81 ioctl (9E) の使用#include <sys/types.h> #include "xxio.h" /* contains device's ioctl cmds and args */ int main(void) { uint8_t status; /* ... */ /* * read the device status */ if (ioctl(fd, XX_GET_STATUS, &status) == -1) { /* error handling */ } printf("device status %x\n", status); exit(0); }
Oracle Solaris カーネルは、32 ビットアプリケーションと 64 ビットアプリケーションの両方をサポートしている適切なハードウェア上では 64 ビットモードで動作します。両方のサイズのプログラムからの入出力制御コマンドをサポートするには、64 ビットのデバイスドライバが必要です。32 ビットプログラムと 64 ビットプログラムの違いは、C 言語の型モデルです。32 ビットプログラムは ILP32 であり、64 ビットプログラムは LP64 です。C データ型モデルについては、64 ビットデバイスドライバの準備を参照してください。
プログラムとカーネルの間を流れるデータの形式が同じでない場合は、ドライバがモデルの不一致に対処できる必要があります。モデルの不一致に対処するには、データに対して適切な調整を行う必要があります。
モデルの不一致が存在するかどうかを判定するために、ioctl(9E) のモードパラメータは、ドライバにデータモデルのビットを渡します。使用例 82 に示すように、このモードパラメータは次に、何らかのモデル変換が必要かどうかを判定するために ddi_model_convert_from(9F) に渡されます。
ioctl(9E) ルーチンにデータモデルを渡すために、モード引数のフラグサブフィールドが使用されます。このフラグは、次のいずれかの値に設定されます。
DATAMODEL_ILP32
DATAMODEL_LP64
FNATIVE は、カーネル実装のデータモデルに一致するように条件付きで定義されます。mode 引数からフラグを抽出するには、FMODELS マスクを使用します。これにより、ドライバは、明示的にデータモデルを検査してアプリケーションのデータ構造体をコピーする方法を判定できます。
DDI 関数の ddi_model_convert_from(9F) は、ioctl() 呼び出しに関して一部のドライバを支援できる簡易ルーチンです。この関数は引数としてユーザーアプリケーションのデータ型モデルを受け取り、次のいずれかの値を返します。
DDI_MODEL_ILP32 – ILP32 アプリケーションから変換する
DDI_MODEL_NONE – 変換は必要なし
アプリケーションとドライバのデータモデルが同じである場合のように、データ変換が必要ない場合は DDI_MODEL_NONE が返されます。LP64 モデルにコンパイルされていて、32 ビットアプリケーションと通信するドライバには DDI_MODEL_ILP32 が返されます。
次の例では、ドライバは、ユーザーアドレスを含むデータ構造体をコピーします。このデータ構造体は、ILP32 から LP64 にサイズが変更されます。それに応じて、64 ビットのドライバは、32 ビットアプリケーションとの通信には構造体の 32 ビットバージョンを使用します。
使用例 82 32 ビットアプリケーションと 64 ビットアプリケーションをサポートする ioctl(9E) ルーチンstruct args32 { uint32_t addr; /* 32-bit address in LP64 */ int len; } struct args { caddr_t addr; /* 64-bit address in LP64 */ int len; } static int xxioctl(dev_t dev, int cmd, intptr_t arg, int mode, cred_t *credp, int *rvalp) { struct xxstate *xsp; struct args a; xsp = ddi_get_soft_state(statep, getminor(dev)); if (xsp == NULL) { return (ENXIO); } switch (cmd) { case XX_COPYIN_DATA: switch(ddi_model_convert_from(mode)) { case DDI_MODEL_ILP32: { struct args32 a32; /* copy 32-bit args data shape */ if (ddi_copyin((void *)arg, &a32, sizeof (struct args32), mode) != 0) { return (EFAULT); } /* convert 32-bit to 64-bit args data shape */ a.addr = a32.addr; a.len = a32.len; break; } case DDI_MODEL_NONE: /* application and driver have same data model. */ if (ddi_copyin((void *)arg, &a, sizeof (struct args), mode) != 0) { return (EFAULT); } } /* continue using data shape in native driver data model. */ break; case XX_COPYOUT_DATA: /* copyout handling */ break; default: /* generic "ioctl unknown" error */ return (ENOTTY); } return (0); }
ドライバでは、32 ビットのサイズの構造体には収まらないネイティブな量のコピー出力が必要になる場合があります。この場合、ドライバは、呼び出し元に EOVERFLOW を返します。次の例に示すように、EOVERFLOW は、返される値を保持するにはインタフェース内のデータ型が小さすぎることを示すものとして機能します。
使用例 83 copyout(9F) のオーバーフローの処理int xxioctl(dev_t dev, int cmd, intptr_t arg, int mode, cred_t *cr, int *rval_p) { struct resdata res; /* body of driver */ switch (ddi_model_convert_from(mode & FMODELS)) { case DDI_MODEL_ILP32: { struct resdata32 res32; if (res.size > UINT_MAX) return (EOVERFLOW); res32.size = (size32_t)res.size; res32.flag = res.flag; if (ddi_copyout(&res32, (void *)arg, sizeof (res32), mode)) return (EFAULT); } break; case DDI_MODEL_NONE: if (ddi_copyout(&res, (void *)arg, sizeof (res), mode)) return (EFAULT); break; } return (0); }