Go to main content
Oracle® Solaris 11.3 デバイスドライバの記述

印刷ビューの終了

更新: 2016 年 11 月
 
 

LDI カーネルインタフェースの例

このセクションでは、この章の前のセクションで説明した LDI 呼び出しを使用するカーネルデバイスコンシューマの例を示します。このセクションでは、このモジュール例の次の面について説明します。

このカーネルデバイスコンシューマの例は lyr という名前です。lyr モジュールは、LDI 呼び出しを使用してターゲットデバイスにデータを送信する階層化ドライバです。lyr ドライバは open(9E) エントリポイントで、lyr.conf 構成ファイル内の lyr_targ プロパティーによって指定されたデバイスを開きます。lyr ドライバは write(9E) エントリポイントで、lyr_targ プロパティーによって指定されたデバイスに、すべての受信データを書き込みます。

デバイス構成ファイル

次に示す構成ファイルでは、lyr ドライバの書き込み先ターゲットデバイスはコンソールです。

使用例 57  構成ファイル
#
# Use is subject to license terms.
#
#pragma ident	"%Z%%M%	%I%	%E% SMI"

name="lyr" parent="pseudo" instance=1;
lyr_targ="/dev/console";

ドライバソースファイル

次に示したドライバソースファイルで、lyr_state_t 構造体は lyr ドライバのソフト状態を保持しています。ソフト状態には、lyr_targ デバイスの階層化ドライバのハンドル (lh) と、lyr デバイスの階層化識別子 ( li) が格納されています。ソフト状態の詳細については、ドライバのソフト状態情報の取得を参照してください。

lyr_open() エントリポイントで、ddi_prop_lookup_string(9F)lyr_targ プロパティーから、lyr デバイスが開くターゲットデバイスの名前を取得しています。ldi_ident_from_dev(9F) 関数は lyr デバイスの LDI 階層化識別子を取得します。ldi_open_by_name(9F) 関数は lyr_targ デバイスを開き、lyr_targ デバイスの階層化ドライバのハンドルを取得します。

lyr_open() で何らかのエラーが発生した場合、ldi_close(9F)ldi_ident_release(9F)、および ddi_prop_free(9F) の呼び出しによって、実行されたすべてのことが取り消されます。ldi_close(9F) 関数は lyr_targ デバイスを閉じます。ldi_ident_release(9F) 関数は lyr 階層化識別子を解放します。ddi_prop_free(9F) 関数は、lyr_targ デバイス名が取得されたときに割り当てられたリソースを解放します。エラーが発生しなければ、lyr_close() エントリポイントで ldi_close(9F) 関数と ldi_ident_release(9F) 関数が呼び出されます。

ドライバモジュールの最終行では、ldi_write(9F) 関数が呼び出されています。ldi_write(9F) 関数は lyr_write() エントリポイントで lyr デバイスに書き込まれたデータを取得し、そのデータを lyr_targ デバイスに書き込みます。 ldi_write(9F) 関数は lyr_targ デバイスの階層化ドライバのハンドルを使用して、データを lyr_targ デバイスに書き込みます。

使用例 58  ドライバソースファイル
#include <sys/types.h>
#include <sys/file.h>
#include <sys/errno.h>
#include <sys/open.h>
#include <sys/cred.h>
#include <sys/cmn_err.h>
#include <sys/modctl.h>
#include <sys/conf.h>
#include <sys/stat.h>
#include <sys/ddi.h>
#include <sys/sunddi.h>
#include <sys/sunldi.h>

typedef struct lyr_state {
    ldi_handle_t    lh;
    ldi_ident_t     li;
    dev_info_t      *dip;
    minor_t         minor;
    int             flags;
    kmutex_t        lock;
} lyr_state_t;

#define LYR_OPENED      0x1     /* lh is valid */
#define LYR_IDENTED     0x2     /* li is valid */

static int lyr_info(dev_info_t *, ddi_info_cmd_t, void *, void **);
static int lyr_attach(dev_info_t *, ddi_attach_cmd_t);
static int lyr_detach(dev_info_t *, ddi_detach_cmd_t);
static int lyr_open(dev_t *, int, int, cred_t *);
static int lyr_close(dev_t, int, int, cred_t *);
static int lyr_write(dev_t, struct uio *, cred_t *);

static void *lyr_statep;

static struct cb_ops lyr_cb_ops = {
    lyr_open,            /* open */
    lyr_close,           /* close */
    nodev,               /* strategy */
    nodev,               /* print */
    nodev,               /* dump */
    nodev,               /* read */
    lyr_write,           /* write */
    nodev,               /* ioctl */
    nodev,               /* devmap */
    nodev,               /* mmap */
    nodev,               /* segmap */
    nochpoll,            /* poll */
    ddi_prop_op,         /* prop_op */
    NULL,                /* streamtab  */
    D_NEW | D_MP,        /* cb_flag */
    CB_REV,              /* cb_rev */
    nodev,               /* aread */
    nodev                /* awrite */
};

static struct dev_ops lyr_dev_ops = {
    DEVO_REV,            /* devo_rev, */
    0,                   /* refcnt  */
    lyr_info,            /* getinfo */
    nulldev,             /* identify */
    nulldev,             /* probe */
    lyr_attach,          /* attach */
    lyr_detach,          /* detach */
    nodev,               /* reset */
    &lyr_cb_ops,         /* cb_ops */
    NULL,                /* bus_ops */
    NULL,                /* power */
    ddi_quiesce_not_needed,     /* quiesce */
};

static struct modldrv modldrv = {
    &mod_driverops,
    "LDI example driver",
    &lyr_dev_ops
};

static struct modlinkage modlinkage = {
    MODREV_1,
    &modldrv,
    NULL
};

int
_init(void)
{
    int rv;

    if ((rv = ddi_soft_state_init(&lyr_statep, sizeof (lyr_state_t),
        0)) != 0) {
        cmn_err(CE_WARN, "lyr _init: soft state init failed\n");
        return (rv);
    }
    if ((rv = mod_install(&modlinkage)) != 0) {
        cmn_err(CE_WARN, "lyr _init: mod_install failed\n");
        goto FAIL;
    }
    return (rv);
    /*NOTEREACHED*/
FAIL:
    ddi_soft_state_fini(&lyr_statep);
    return (rv);
}

int
_info(struct modinfo *modinfop)
{
    return (mod_info(&modlinkage, modinfop));
}

int
_fini(void)
{
    int rv;

    if ((rv = mod_remove(&modlinkage)) != 0) {
        return(rv);
    }
    ddi_soft_state_fini(&lyr_statep);
    return (rv);
}
/*
 * 1:1 mapping between minor number and instance
 */
static int
lyr_info(dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg, void **result)
{
    int inst;
    minor_t minor;
    lyr_state_t *statep;
    char *myname = "lyr_info";

    minor = getminor((dev_t)arg);
    inst = minor;
    switch (infocmd) {
    case DDI_INFO_DEVT2DEVINFO:
        statep = ddi_get_soft_state(lyr_statep, inst);
        if (statep == NULL) {
            cmn_err(CE_WARN, "%s: get soft state "
                "failed on inst %d\n", myname, inst);
            return (DDI_FAILURE);
        }
        *result = (void *)statep->dip;
        break;
    case DDI_INFO_DEVT2INSTANCE:
        *result = (void *)inst;
        break;
    default:
        break;
    }

    return (DDI_SUCCESS);
}

static int
lyr_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
{
    int inst;
    lyr_state_t *statep;
    char *myname = "lyr_attach";

    switch (cmd) {
    case DDI_ATTACH:
        inst = ddi_get_instance(dip);

        if (ddi_soft_state_zalloc(lyr_statep, inst) != DDI_SUCCESS) {
            cmn_err(CE_WARN, "%s: ddi_soft_state_zallac failed "
                "on inst %d\n", myname, inst);
            goto FAIL;
        }
        statep = (lyr_state_t *)ddi_get_soft_state(lyr_statep, inst);
        if (statep == NULL) {
            cmn_err(CE_WARN, "%s: ddi_get_soft_state failed on "
                "inst %d\n", myname, inst);
            goto FAIL;
        }
        statep->dip = dip;
        statep->minor = inst;
        if (ddi_create_minor_node(dip, "node", S_IFCHR, statep->minor,
            DDI_PSEUDO, 0) != DDI_SUCCESS) {
            cmn_err(CE_WARN, "%s: ddi_create_minor_node failed on "
                "inst %d\n", myname, inst);
            goto FAIL;
        }
        mutex_init(&statep->lock, NULL, MUTEX_DRIVER, NULL);
        return (DDI_SUCCESS);
    case DDI_RESUME:
    case DDI_PM_RESUME:
    default:
        break;
    }
    return (DDI_FAILURE);
    /*NOTREACHED*/
FAIL:
    ddi_soft_state_free(lyr_statep, inst);
    ddi_remove_minor_node(dip, NULL);
    return (DDI_FAILURE);
}

static int
lyr_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
{
    int inst;
    lyr_state_t *statep;
    char *myname = "lyr_detach";

    inst = ddi_get_instance(dip);
    statep = ddi_get_soft_state(lyr_statep, inst);
    if (statep == NULL) {
        cmn_err(CE_WARN, "%s: get soft state failed on "
            "inst %d\n", myname, inst);
        return (DDI_FAILURE);
    }
    if (statep->dip != dip) {
        cmn_err(CE_WARN, "%s: soft state does not match devinfo "
            "on inst %d\n", myname, inst);
        return (DDI_FAILURE);
    }
    switch (cmd) {
    case DDI_DETACH:
        mutex_destroy(&statep->lock);
        ddi_soft_state_free(lyr_statep, inst);
        ddi_remove_minor_node(dip, NULL);
        return (DDI_SUCCESS);
    case DDI_SUSPEND:
    case DDI_PM_SUSPEND:
    default:
        break;
    }
    return (DDI_FAILURE);
}
/*
 * on this driver's open, we open the target specified by a property and store
 * the layered handle and ident in our soft state.  a good target would be
 * "/dev/console" or more interestingly, a pseudo terminal as specified by the
 * tty command
 */
/*ARGSUSED*/
static int
lyr_open(dev_t *devtp, int oflag, int otyp, cred_t *credp)
{
    int rv, inst = getminor(*devtp);
    lyr_state_t *statep;
    char *myname = "lyr_open";
    dev_info_t *dip;
    char *lyr_targ = NULL;

    statep = (lyr_state_t *)ddi_get_soft_state(lyr_statep, inst);
    if (statep == NULL) {
        cmn_err(CE_WARN, "%s: ddi_get_soft_state failed on "
            "inst %d\n", myname, inst);
        return (EIO);
    }
    dip = statep->dip;
    /*
     * our target device to open should be specified by the "lyr_targ"
     * string property, which should be set in this driver's .conf file
     */
    if (ddi_prop_lookup_string(DDI_DEV_T_ANY, dip, DDI_PROP_NOTPROM,
        "lyr_targ", &lyr_targ) != DDI_PROP_SUCCESS) {
        cmn_err(CE_WARN, "%s: ddi_prop_lookup_string failed on "
            "inst %d\n", myname, inst);
        return (EIO);
    }
    /*
     * since we only have one pair of lh's and li's available, we don't
     * allow multiple on the same instance
     */
    mutex_enter(&statep->lock);
    if (statep->flags & (LYR_OPENED | LYR_IDENTED)) {
        cmn_err(CE_WARN, "%s: multiple layered opens or idents "
            "from inst %d not allowed\n", myname, inst);
        mutex_exit(&statep->lock);
        ddi_prop_free(lyr_targ);
        return (EIO);
    }
    rv = ldi_ident_from_dev(*devtp, &statep->li);
    if (rv != 0) {
        cmn_err(CE_WARN, "%s: ldi_ident_from_dev failed on inst %d\n",
            myname, inst);
        goto FAIL;
    }
    statep->flags |= LYR_IDENTED;
    rv = ldi_open_by_name(lyr_targ, FREAD | FWRITE, credp, &statep->lh,
        statep->li);
    if (rv != 0) {
        cmn_err(CE_WARN, "%s: ldi_open_by_name failed on inst %d\n",
            myname, inst);
        goto FAIL;
    }
    statep->flags |= LYR_OPENED;
    cmn_err(CE_CONT, "\n%s: opened target '%s' successfully on inst %d\n",
        myname, lyr_targ, inst);
    rv = 0;

FAIL:
    /* cleanup on error */
    if (rv != 0) {
        if (statep->flags & LYR_OPENED)
            (void)ldi_close(statep->lh, FREAD | FWRITE, credp);
        if (statep->flags & LYR_IDENTED)
            ldi_ident_release(statep->li);
        statep->flags &= ~(LYR_OPENED | LYR_IDENTED);
    }
    mutex_exit(&statep->lock);
    if (lyr_targ != NULL)
        ddi_prop_free(lyr_targ);
    return (rv);
}
/*
 * on this driver's close, we close the target indicated by the lh member
 * in our soft state and release the ident, li as well.  in fact, we MUST do
 * both of these at all times even if close yields an error because the
 * device framework effectively closes the device, releasing all data
 * associated with it and simply returning whatever value the target's
 * close(9E) returned.  therefore, we must as well.
 */
/*ARGSUSED*/
static int
lyr_close(dev_t devt, int oflag, int otyp, cred_t *credp)
{
    int rv, inst = getminor(devt);
    lyr_state_t *statep;
    char *myname = "lyr_close";
    statep = (lyr_state_t *)ddi_get_soft_state(lyr_statep, inst);
    if (statep == NULL) {
        cmn_err(CE_WARN, "%s: ddi_get_soft_state failed on "
            "inst %d\n", myname, inst);
        return (EIO);
    }
    mutex_enter(&statep->lock);
    rv = ldi_close(statep->lh, FREAD | FWRITE, credp);
    if (rv != 0) {
        cmn_err(CE_WARN, "%s: ldi_close failed on inst %d, but will ",
            "continue to release ident\n", myname, inst);
    }
    ldi_ident_release(statep->li);
    if (rv == 0) {
        cmn_err(CE_CONT, "\n%s: closed target successfully on "
            "inst %d\n", myname, inst);
    }
    statep->flags &= ~(LYR_OPENED | LYR_IDENTED);
    mutex_exit(&statep->lock);
    return (rv);
}
/*
 * echo the data we receive to the target
 */
/*ARGSUSED*/
static int
lyr_write(dev_t devt, struct uio *uiop, cred_t *credp)
{
    int rv, inst = getminor(devt);
    lyr_state_t *statep;
    char *myname = "lyr_write";

    statep = (lyr_state_t *)ddi_get_soft_state(lyr_statep, inst);
    if (statep == NULL) {
        cmn_err(CE_WARN, "%s: ddi_get_soft_state failed on "
            "inst %d\n", myname, inst);
        return (EIO);
    }
    return (ldi_write(statep->lh, uiop, credp));
}

階層化ドライバを構築してロードする方法

  1. ドライバをコンパイルします。

    –D_KERNEL オプションを使用して、これがカーネルモジュールであることを示します。

    • SPARC アーキテクチャー向けにコンパイルする場合は、–xarch=v9 オプションを使用します。

      % cc -c -D_KERNEL -xarch=v9 lyr.c
    • 32 ビット x86 アーキテクチャー向けにコンパイルする場合は、次のコマンドを使用します。

      % cc -c -D_KERNEL lyr.c
  2. ドライバをリンクします。
    % ld -r -o lyr lyr.o
  3. 構成ファイルをインストールします。

    root ユーザーとして、システムのカーネルドライバ領域に構成ファイルをコピーします。

    # cp lyr.conf /usr/kernel/drv
  4. ドライババイナリをインストールします。
    • root ユーザーとして、SPARC アーキテクチャー上の sparcv9 ドライバ領域にドライババイナリをコピーします。

      # cp lyr /usr/kernel/drv/sparcv9
    • root ユーザーとして、32 ビット x86 アーキテクチャー上の drv ドライバ領域にドライババイナリをコピーします。

      # cp lyr /usr/kernel/drv
  5. ドライバをロードします。

    root ユーザーとして、add_drv(1M) コマンドを使用してドライバをロードします。

    # add_drv lyr

    疑似デバイスを一覧表示して、lyr デバイスが存在するようになったことを確認します。

    # ls /devices/pseudo | grep lyr
    lyr@1
    lyr@1:node

階層化ドライバのテスト

lyr ドライバをテストするには、lyr デバイスにメッセージを書き込み、メッセージが lyr_targ デバイスに表示されることを確認します。

使用例 59  階層化デバイスへの短いメッセージの書き込み

この例では、lyr_targ デバイスが、lyr デバイスがインストールされているシステムのコンソールです。

参照している表示が、lyr デバイスがインストールされているシステムのコンソールデバイスの表示でもある場合は、コンソールに書き込むと表示が壊れることに注意してください。コンソールメッセージはウィンドウシステムの外側に表示されます。lyr ドライバのテスト後、表示を再描画またはリフレッシュする必要があります。

参照している表示が、lyr デバイスがインストールされているシステムのコンソールデバイスの表示ではない場合、ログインするなどして、ターゲットコンソールデバイスの表示内容を取得します。

次のコマンドでは、非常に短いメッセージが lyr デバイスに書き込まれます。

# echo "\n\n\t===> Hello World!! <===\n" > /devices/pseudo/lyr@1:node

ターゲットコンソールに次のメッセージが表示されます。

console login:

    ===> Hello World!! <===

lyr: 
lyr_open: opened target '/dev/console' successfully on inst 1
lyr: 
lyr_close: closed target successfully on inst 1

lyr_open() および lyr_close() からのメッセージは、lyr_open() および lyr_close() のエントリポイントの cmn_err(9F) 呼び出しからのものです。

使用例 60  階層化デバイスへの長いメッセージの書き込み

次のコマンドでは、長いメッセージが lyr デバイスに書き込まれます。

# cat lyr.conf > /devices/pseudo/lyr@1:node

ターゲットコンソールに次のメッセージが表示されます。

lyr: 
lyr_open: opened target '/dev/console' successfully on inst 1
#


# Use is subject to license terms.
#
#pragma ident   "%Z%%M% %I%     %E% SMI"

name="lyr" parent="pseudo" instance=1;
lyr_targ="/dev/console";
lyr: 
lyr_close: closed target successfully on inst 1
使用例 61  ターゲットデバイスの変更

ターゲットデバイスを変更するには、/usr/kernel/drv/lyr.conf を編集し、lyr_targ プロパティーの値を、異なるターゲットデバイスへのパスに変更します。たとえば、ローカル端末での tty コマンドの出力をターゲットデバイスにできます。/dev/pts/4 はそうしたデバイスパスの例です。

新しいターゲットデバイスを使用するようにドライバを更新する前に、lyr デバイスを使用中でないことを確認してください。

# modinfo -c | grep lyr
174          3 lyr                              UNLOADED/UNINSTALLED

lyr.conf 構成ファイルを再ロードするには、update_drv(1M) コマンドを使用します。

# update_drv lyr

もう一度 lyr デバイスにメッセージを書き込み、メッセージが新しい lyr_targ デバイスに表示されることを確認します。