newbusベースのデバイスドライバソースコードのスケルトン

FreeBSDデバイスドライバ(?というか、どちらかというとカーネルモジュール)を書く機会があったのだけれど、資料が/usr/src/sys/dev 以下のソースコードくらいしかなくて、思いのほか苦戦してしまった。折角書いたので、スケルトンファイルとして公開してみる。

以下の説明では、ディレクトリ構成が以下のようになっていると仮定する。

skelton_device_driver
|-- Makefile
`-- test.c

このサンプルで何ができるか?

このサンプルソースの特徴は、以下の通り:

  • ローダブルモジュール(kldloadできる)である
  • IRQPCIから割り当ててもらおうとする
  • FreeBSD 7.0 で動作確認済み

これに似たようなことをやりたい方には、うってつけのサンプルだと思う...多分。

サンプルのビルド方法

skelton_device_driver ディレクトリの中で、

 $ make
 $ kldload ./test.ko

カーネルに読み込む。さすがにソースコードの中身全部を解説するのはしんどいので、代わりに作成する際に参考にしたサイトを以下に示しておく:

  1. FreeBSD Architecture Handbook
  2. How to Write Kernel Drivers with NEWBUS
  3. FreeBSDマニュアル検索

特に、一番下のFreeBSDマニュアル検索ページは、とても情報が新しくかなり使えるサイトである。
FreeBSDのドライバを書こうとしている方の、手助けになれれば幸いです。


以下が各ファイルの中身。

# Makefile
# Declare Name of kernel module
KMOD = test

# Enumerate Source files for kernel module
SRCS = test.c
SRCS += device_if.h bus_if.h


.include <bsd.kmod.mk>

以下に、test.c の中身を示す。おおまかにいうと、pciからIRQを分けてもらって、割り込みがそのIRQにたいしてかかれば "Wow!Interrput was occued!"などと表示されるプログラムである。

/* test.c 
 * written by big-eyed-hamster 
 * http://d.hatena.ne.jp/big-eyed-hamster */

/* including for "kldloading", "kldunloading"  */
#include <sys/param.h> 
#include <sys/module.h> 
#include <sys/kernel.h>
#include <sys/systm.h>
/* including for handling IRQ  */
#include <sys/bus.h> 
#include <machine/resource.h> /*To use SYS_RES_IRQ*/
#include <sys/rman.h> /*To use RF_ACTIVE*/

/* prototype declaration of device functions. */
static int test_probe(device_t dev);
static int test_attach(device_t dev);
static int test_detach(device_t dev);

int test_intr(void *v);

/* need for "kldloading", "kldunloading"  */
static int 
test_modevent( struct module *module, int event, void *args) 
{
        int e = 0; 

        switch (event) {
        case MOD_LOAD:
                uprintf("Hello Kernel Module in FreeBSD!\n");
                break;
        case MOD_UNLOAD:
                uprintf("Bye ,Kernel Module!\n");
                break;
        default:
                e = EOPNOTSUPP;
        }   
        return (e);
}

static moduledata_t test_conf ={
        "test",
        test_modevent,
        NULL
};

DECLARE_MODULE(test, test_conf, SI_SUB_DRIVERS, SI_ORDER_MIDDLE);

/* Interrupt Handler */
int
test_intr(void *v) 
{
        uprintf("Wow! A interrupt occured!\n");
        return (1);
}

/* need for using IRQ */
static device_method_t test_methods[] = {
        DEVMETHOD(device_probe,test_probe),
        DEVMETHOD(device_attach,test_attach),
        DEVMETHOD(device_detach,test_detach),
        { 0, 0 }
};

struct test_softc {
        device_t test_dev;
        struct resource     *irq;
        int                 irq_rid;
        void                *irq_handle;

        void                *cookie;
};

static driver_t test_driver = {
        "test",
        test_methods,
        sizeof(struct test_softc),
};

static int
test_probe(device_t self)
{
        struct resource *res;
        struct test_softc *test_softc;

        uprintf("proving...\n");

        uprintf("start to examine whether IRQ is free or not...");
        test_softc = device_get_softc(self);
        res = bus_alloc_resource_any(self,SYS_RES_IRQ,&test_softc->irq_rid,RF_ACTIVE);
        //res = BUS_ALLOC_RESOURCE_ANY(self,SYS_RES_IRQ,&test_softc->irq_rid,RF_ACTIVE); 

        if ( res == NULL ){
                uprintf("failed.\n");
                return (1);
        }

        uprintf("succeeded.\n");
        uprintf("Next, release resource...");
        bus_release_resource(self,SYS_RES_IRQ,test_softc->irq_rid,res);
        //BUS_RELEASE_RESOURCE(self,SYS_RES_IRQ,test_softc->irq_rid,res);
        uprintf("succeeded.\n");

        return (0);
}

static int
test_attach(device_t self)
{
#if 1
        struct test_softc *test_softc;

        uprintf("attacing...\n");
        test_softc = device_get_softc(self);
        test_softc->irq = bus_alloc_resource_any(self,SYS_RES_IRQ,&test_softc->irq_rid,RF_ACTIVE);

        if ( test_softc->irq == NULL ){
                uprintf("failed.\n");
                return (1);
        }
        uprintf("succeeded.\n");
        uprintf("Next, start to register interrput handler...");
        bus_setup_intr(self,test_softc->irq,INTR_TYPE_MISC,NULL,(driver_intr_t*)test_intr,test_softc,&test_softc->cookie);
        uprintf("succeeded.\n");
#endif

        return (0);
}

static int
test_detach(device_t self)
{
        struct test_softc *test_softc;
        uprintf("detaching...\n");
        test_softc = device_get_softc(self);
        uprintf("Teardowning interrput handler...\n");
        bus_teardown_intr(self,test_softc->irq,test_softc->cookie);
        uprintf("succeeded.");
        uprintf("Next, start to release IRQ...");
        bus_release_resource(self,SYS_RES_IRQ,test_softc->irq_rid,test_softc->irq);

        return (0);
}

devclass_t test_devclass;

DRIVER_MODULE(test, pci, test_driver, test_devclass, 0, 0);