NetBSDのカーネルモジュールサンプルでioctlできるようにする

[NetBSD Advent Calendar 2018](https://qiita.com/advent-calendar/2018/netbsd) 11日目の記事です。

[3日目の記事](https://qiita.com/furandon_pig/items/647c21cbe21eeb3aafc2)でNetBSDのカーネルモジュールを紹介していました。今日はそのサンプルをもうちょっとだけ拡張し、 `ioctl()` できるようなサンプルを作ってみようと思います。

## ioctl(2)って何？

[ioctl(2)](http://netbsd.gw.com/cgi-bin/man-cgi?ioctl+2+NetBSD-current)とはデバイスのパラメータを操作するための機能です。システムコールのシグネチャは以下のようになっています。

```
     #include <sys/ioctl.h>

     int
     ioctl(int d, unsigned long request, ...);
```

第1引数のファイルディスクリプタに対し、第2引数でリクエスト内容を指定します。必要なデータがあれば、第3引数以降に指定します。
第2引数のリクエストは、入出力の方向やサイズ、リクエスト種別等をエンコードした値になっています。
使用例としては以下のような形になります。

```c 
/usr/src/sys/tests/kernel/t_pty.c:
108 static void
109 condition(int fd)
110 {
...
115                 if (ioctl(fd, TIOCSQSIZE, &opt) == -1)
116                         err(EXIT_FAILURE, "Couldn't set tty(4) buffer size");
```

## カーネルモジュールにioctl(の受け口)を実装する

[3日目の記事](https://qiita.com/furandon_pig/items/647c21cbe21eeb3aafc2)で紹介したカーネルモジュールのサンプルは、モジュールのロードとアンロード、そして[read(2)](http://netbsd.gw.com/cgi-bin/man-cgi?read+2+NetBSD-current)システムコールの例が実装されていました。

該当のコード( `/usr/src/sys/modules/examples/readhappy/readhappy.c` )を見ると、以下のような箇所があります。どうやら `struct cdevsw.d_ioctl` に `ioctl()` の受け口となる関数を指定すれば良さそうです。

```c 
/usr/src/sys/modules/examples/readhappy/readhappy.c:
100 static struct cdevsw happy_cdevsw = {
101         .d_open = happy_open,
102         .d_close = happy_close,
103         .d_read = happy_read,
104         .d_write = nowrite,
105         .d_ioctl = noioctl,
106         .d_stop = nostop,
107         .d_tty = notty,
108         .d_poll = nopoll,
109         .d_mmap = nommap,
110         .d_kqfilter = nokqfilter,
111         .d_discard = nodiscard,
112         .d_flag = D_OTHER
113 };
```

ちなみに、 `struct cdevsw` の定義を見ると以下のようになっています。 `open()` や `close()` への関数ポインタ群をまとめた構造体になっています。
こうやってひも解いて見てみると、ユーザランド側のプログラミングと似通った箇所もあり、NetBSDカーネルとはいえ、思いのほか読み解きやすくも感じられます。

```c 
/usr/src/sys/modules/examples/readhappy/sys/conf.h:
 82 /*
 83  * Character device switch table
 84  */
 85 struct cdevsw {
 86         int             (*d_open)(dev_t, int, int, struct lwp *);
 87         int             (*d_close)(dev_t, int, int, struct lwp *);
 88         int             (*d_read)(dev_t, struct uio *, int);
 89         int             (*d_write)(dev_t, struct uio *, int);
 90         int             (*d_ioctl)(dev_t, u_long, void *, int, struct lwp *);
 91         void            (*d_stop)(struct tty *, int);
 92         struct tty *    (*d_tty)(dev_t);
 93         int             (*d_poll)(dev_t, int, struct lwp *);
 94         paddr_t         (*d_mmap)(dev_t, off_t, int);
 95         int             (*d_kqfilter)(dev_t, struct knote *);
 96         int             (*d_discard)(dev_t, off_t, off_t);
 97         int             d_flag;
 98 };
```

とりあえず、 `struct cdevsw.d_ioctl` に関数ポインタを指定すれば良さそうだという所まで見えてきました。さっそくカーネルモジュールサンプルを流用して `ioctl()` を実装してみます。

## カーネルモジュールでのioctl実装

### まずは雛型を用意する

前回のカーネルモジュールの話で使用した `readhappy.c` をもとに `ioctl()` を追加してみます。
ディレクトリごと新しいサンプルファイルとして準備します。

```
# cd /usr/src/sys/modules/examples/
# cp -r readhappy ioctl_sample
# cd ioctl_sample/
# ls -F
CVS/         Makefile     readhappy.c
```

### ioctl()の使われ方をソースコードから参照する

いきなり `ioctl()` を実装するにしても、どうすれば良いものか分かりません。まずはカーネルソースツリーの中で `ioctl()` を使用している箇所を探し、その実装方法を参考にしようと思います。
ちょうど今回のサンプルに応用できそうな例が `dev/i2c/i2c.c` にありました。

```c 
dev/i2c/i2c.c
 85 const struct cdevsw iic_cdevsw = {
 ...
 90         .d_ioctl = iic_ioctl,
 ...
 98 };
 ...
579 static int
580 iic_ioctl(dev_t dev, u_long cmd, void *data, int flag, lwp_t *l)
581 {
582         struct iic_softc *sc = device_lookup_private(&iic_cd, minor(dev));
583
584         if (sc == NULL)
585                 return ENXIO;
586
587         switch (cmd) {
588         case I2C_IOCTL_EXEC:
589                 return iic_ioctl_exec(sc, (i2c_ioctl_exec_t *)data, flag);
590         default:
591                 return ENODEV;
592         }
593 }
```

上記の部分だけでも、以下のような内容が分かります。

 * 引数 `cmd` で指定された値に応じて処理を分けるようだ。
   * というか、 `ioctl()` の第2引数の値がそのまま引数 `cmd` に渡ってくるようだ。
 * データは `void *data` として引数で渡されてくるようだ。
 * ~~他の引数はいったん見なかったことにする~~

### ioctlのリクエスト値を用意する

というわけで、引数 `cmd` で処理を分けるための値( `ioctl()` の第2引数)を定義します。
とりあえずこんな感じにしてみます。
(よく考えたら引数 `cmd` の値に相当する定数って慣習的な命名規則があった気がするけど、参考にした `dev/i2c/i2c.c` もそうなってない気がする...)

```c 
#include <sys/ioccom.h>

typedef struct happy_ioctl_exec {
        int dummy;
} happy_ioctl_exec_t;

#define HAPPY_IOCTL_STRCPY      _IOW('I', 0, happy_ioctl_exec_t)
```

### ioctlに対応する関数を用意する

`ioctl()` に対応する関数も実装します。今回の例では、渡されてきたデータをカーネルモジュール内のバッファに文字列として格納しています。

```c 
static char happy_str[32] = { '\0' };

dev_type_ioctl(happy_ioctl);

static struct cdevsw happy_cdevsw = {
        ...
        .d_ioctl = happy_ioctl,
        ...
};
...
int
happy_ioctl(dev_t dev, u_long cmd, void *data, int flag, lwp_t *l)
{
        if (data == NULL) {
                return EINVAL;
        }

        switch (cmd) {
        case HAPPY_IOCTL_STRCPY:
                printf("-=> HAPPY_IOCTL_STRCPY\n");
                memcpy(happy_str, data, 31);
                happy_str[31] = '\0';
        default:
                return ENODEV;
        }

        return 0;
}
```

そして、 `read()` に対応する関数も実装します。ユーザランド側で `ioctl()` で渡したデータが、 `read()` で読み取れるという挙動になるはずです。

```c 
int
happy_read(dev_t self __unused, struct uio *uio, int flags __unused)
{
        int e;

        if ((e = uiomove(happy_str, strlen(happy_str), uio)))
                return e;

        return 0;
}
```

ビルドしてみます。これでカーネルモジュールができあがりました。

```sh 
# make
#   compile  ioctl_sample/ioctl_sample.o
/usr/src/tooldir.NetBSD-8.0-amd64/bin/x86_64--netbsd-gcc -O2 -g   -std=gnu99    -Wall -Wstrict-prototypes -Wmissing-prototypes -Wpointer-arith -Wno-sign-compare  -Wsystem-headers   -Wno-traditional   -Wa,--fatal-warnings  -Wreturn-type -Wswitch -Wshadow -Wcast-qual -Wwrite-strings -Wextra -Wno-unused-parameter -Wno-sign-compare -Werror   -ffreestanding  -fno-strict-aliasing -Wno-pointer-sign -mno-red-zone -mno-mmx -mno-sse -mno-avx -msoft-float -mcmodel=kernel -fno-omit-frame-pointer  -I/usr/src/common/include --sysroot=/ -I/usr/src/common/include  -nostdinc -I. -I/usr/src/sys/modules/examples/ioctl_sample -isystem /usr/src/sys -isystem /usr/src/sys/arch -isystem /usr/src/sys/../common/include -D_KERNEL -D_LKM -D_MODULE -DSYSCTL_INCLUDE_DESCR -c    ioctl_sample.c
/usr/src/tooldir.NetBSD-8.0-amd64/bin/nbctfconvert -L VERSION ioctl_sample.o
#      link  ioctl_sample/ioctl_sample.kmod
/usr/src/tooldir.NetBSD-8.0-amd64/bin/x86_64--netbsd-gcc  --sysroot=/ -Wl,-z,relro -Wl,--warn-shared-textrel -nostdlib -r -Wl,-T,/usr/src/sys/../sys/modules/xldscripts/kmodule,-d  -Wl,-Map=ioctl_sample.kmod.map  -o ioctl_sample.kmod ioctl_sample.o
/usr/src/tooldir.NetBSD-8.0-amd64/bin/nbctfmerge -t -L VERSION -o ioctl_sample.kmod ioctl_sample.o
```

## ユーザランド側から試してみる

### カーネルモジュールのロード

カーネルモジュールをロードしてみます。特に問題なくロードできました。
併せて忘れずに `mknod` を実行しておきます。

<img width="361" alt="SnapCrab_netbsd8 (base) [実行中] - Oracle VM VirtualBox_2018-12-11_21-37-20_No-00.png" src="https://qiita-image-store.s3.amazonaws.com/0/61137/26430a3e-9ad2-f8d7-7317-6b055d09e1d1.png">

### サンプルプログラム

次に以下のようなサンプルプログラムを用意します。単に `/dev/happy` を `open()` し、今回実装したカーネルモジュール内の関数が呼ばれるよう、 `ioctl()` と `read()` を実行しているだけです。

```c 
#include <sys/ioctl.h>
#include <sys/ioccom.h>

#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <unistd.h>

typedef struct happy_ioctl_exec {
        int dummy;
} happy_ioctl_exec_t;

#define HAPPY_IOCTL_STRCPY      _IOW('I', 0, happy_ioctl_exec_t)

int main(int argc, char *argv[])
{
        char buf[BUFSIZ];
        int fd;

        fd = open("/dev/happy", O_RDONLY);
        if (fd == -1) {
                fprintf(stderr, "open() failed.\n");
                exit(-1);
        }

        ioctl(fd, HAPPY_IOCTL_STRCPY, "(^_^)");
        read(fd, buf, BUFSIZ);
        printf("%s", buf);

        close(fd);

        return 0;
}
```

### コンパイルして試してみる

コンパイルして実行してみると、ユーザランド側から `ioctl()` で指定した文字列が `read()` で読み出せています。~~全部読めてないのが気になりますが...~~

<img width="361" alt="SnapCrab_netbsd8 (base) [実行中] - Oracle VM VirtualBox_2018-12-11_22-35-56_No-00.png" src="https://qiita-image-store.s3.amazonaws.com/0/61137/206eb042-321f-61d0-5405-de00d6901e0b.png">

## まとめ

NetBSDのカーネルモジュールサンプルをもとにして、 `ioctl()` を処理できるようカーネルモジュール側に実装を追加する手順を紹介しました。カーネルモジュールのload/unloadやread/write/ioctlは、最小限のサンプルプログラムの形で用意しておくと、ちょっとした実験等を行いたい際に役立ちそうです。