これから作成するカーネル空間側のioctlドライバメソッドの名前を
dio_ioctl
とする。
dio_ioctl
の書式は、L.D.Dの「5.1 ioctl」に合わせて、つぎのように
する。
int dio_ioctl(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg) |
ポインタであるinodeとfilpは、アプリケーションが送ったファイ
ル記述子fdに対応する値で、openメソッドに対して渡されたパラメー
タと同じものになる。引数cmdはユーザから渡されたものがそのまま渡さ
れる。オプションの引数argは、ユーザから整数で渡されてもポインタ
で渡されても、メソッドにはunsigned longとして渡される。プログラム
が第3引数を渡さなかった場合には、ドライバば受け取るargの値には意味が
なくなる。
サンプルプログラムのpassでは、沢山用意されていたコマンドcmd
の中からTCGETAやTCSETAFなどを選んで利用したが、dio_ioctl
関数では、
新しく次の三つのコマンドを作ることにする。
IOCTL_DIO_RESET
)
IOCTL_DIO_INPUT
)
IOCTL_DIO_OUTPUT
)
この三つのコマンドは具体的には、次のような処理を行なうことにする。
IOCTL_DIO_RESET
: 汎用出力データ(OUT1 〜 OUT32)のリセット(クリア)
IOCTL_DIO_INPUT
: 開始点とデータサイズを指定して、1点単位で
のデータ入力(ランダム入力)
IOCTL_DIO_OUTPUT
: 開始点とデータサイズを指定して、1点単位で
のデータ出力(ランダム出力)
サンプルプログラムではtermio構造体を参照したが、
dio_ioctl
関数では、_DIO_POINT
構造体を定義して参照する。
この構造体と用意したコマンドを、次頁のようなヘッダファイルとしてまとめる。
このヘッダファイルはカーネル空間のデバイスドライバとユーザ空間のアプリケー
ションプログラムの両方で参照するので、dio_ioctl.h
という名前で独立
したファイルにする。このヘッダーファイルは、これをインクルードするドライ
バやアプリケーションのソースコードがあるディレクトリに置く。
_DIO_POINT
構造体のそれぞれのメンバは、start_point
が入出力
の開始点、data_count
が入出力点数(データサイズ)、data[32]
が
入出力データ配列を指定している。
ioctlのコマンドは、コマンド番号で区別される。
用意したコマンドの IOCTL_DIO_RESET
とIOCTL_DIO_INPUT
と
IOCTL_DIO_OUTPUT
は、システムの中で同じ番号にならない唯一の
番号(ユニークな番号)を割り当てなければならない。
そのためには、L.D.Dの「5.1.1 ioctlコマンドの選び方」で説明されてい
る_IO(type, nr)
、_IOR(type, nr, dataitem)
、
_IOW(type, nr, dataitem)
、
_IOWR(type, nr, dataitem)
などのマクロが使われる。
typeはマジック番号であり、ここでは'k'を使っている。これによ りカーネル空間内でユニークな番号が生成される。
本体のdio_ioctl
関数の基本的な仕事は、次のようにswitch文を使っ
て、このユニークな番号に対応するコマンドを選択することである。(次ページ
参照)
switch文で選択されたコマンドの実質的な処理はサブ関数(subioctl)で行
われる。dio_ioctl
の最終的な戻値は、各サブ関数からの戻り値をそのまま
返す。
選択されたIOCTL_DIO_RESET
コマンドは、次に示すdio_subioctl_reset
関数で処理される。
この関数は、リセットを行うためにOUT1〜OUT32(LED33〜LED64)へデータの0を出
力している。
データの出力にはoutl関数を使っている。outl関数は、I/Oポート
へロングデータ(ダブルワード)を書き込むカーネル関数である。上記の例では0
は32ビットのロングデータであり、pdio_res->io_address[0]
はI/Oポー
トアドレスである。なお、この処理では引数データのargは使っていない。
選択されたIOCTL_DIO_INPUT
コマンドは、
dio_subioctl_input
関数で処理される。この関数の基本構造を次に示す。
int dio_subioctl_input(PDIO_RESOURCE pdio_res, unsigned long arg) { DIO_POINT point; if (copy_from_user(&point, (void *)arg, sizeof(DIO_POINT))) return -EFAULT; . . . if (copy_to_user((void *)arg, &point, sizeof(DIO_POINT))) return -EFAULT; return 0; } |
copy_from_user
関数を思い出すこと、この関数の引数を
*to, *from, size
とすると、次のような意味を持っていた。
unsigned long copy_from_user( void *to // コピー先バッファアドレス(カーネル空間) const *from // コピー元バッファアドレス(ユーザ空間) unsigned long size // コピーサイズ )
ランダム入力を可能にするために、このcopy_from_user
関数は、最初に
引数*fromに対応するargからデータ構造体(_DIO_POINT
)の取
出しを行っている。この構造体のなかにランダムアクセスを行うときに必要な開始点
(start_point
)や入力点数を指定した情報(data_count
)が格納さ
れている。
copy_to_user
関数を思い出すこと、この関数の引数を
*to, *from, size
とすると、次のような意味を持っていた。
unsigned long copy_from_user( void *to // コピー先バッファアドレス(ユーザ空間) const *from // コピー元バッファアドレス(カーネル空間) unsigned long size // コピーサイズ )所定の処理を行なったあとは最後に
copy_to_user
関数により、
_DIO_POINT
構造体のデータ(data[32]
)を、引数*to
に対応するユーザ空間を示すargへコピーして処理を終了する。
ユーザ空間とのデータのやりとりには、入力も出力も共通のバッファである
argを使用する。ユーザ空間側からは、ioctlを実行する前に、
構造体(_DIO_POINT
)に開始点(start_point
)と入力点数
(data_count
)を指定してから関数を実行すると、必要なデータが
(data[32]
)に格納されて返ってくることになる。
このdio_subioctl_input
関数の基本構造を確認したら、ランダム入力の
機能を実現するために、次のような機能仕様を加えることにする。
_DIO_POINT
のメンバ変数
start_point
で指定された接点番号から、data_count
で指
定された点数分のINデータを、data
配列に格納する。
start_point
で指定した接点のデータがdata
配列の要素0に
なるようにデータを格納する。
この仕様に基づいてdio_subioctl_input
関数を作成すると次のようにな
る。
inl(pdio_res->io_address[0])
は、I/Oポートアドレスの
pdio_res->io_address[0]
からロングデータ(ダブルワード)のデータを読
み出す。
選択されたIOCTL_DIO_OUTPUT
コマンドは、
dio_subioctl_output
関数で処理される。この関数の基本構造を次に示す。
int dio_subioctl_output(PDIO_RESOURCE pdio_res, unsigned long arg) { DIO_POINT point; if (copy_from_user(&point, (void *)arg, sizeof(DIO_POINT))) return -EFAULT; . . . return 0; } |
このdio_subioctl_output
関数の基本構造を確認したら、ランダム出力の
機能を実現するために、次のような機能仕様を加えることにする。
_DIO_POINT
のメンバ変数
start_point
で指定された接点番号から、data_count
で指
定された点数分のOUTデータが、data
配列に格納されているものと
する。
start_point
で指定した接点のデータがdata
配列の要素0に
なるようにデータが格納されているものとする。
この仕様に基づいてdio_subioctl_input
関数を作成すると次のようにな
る。