编写设备驱动程序

第 14 章 分层驱动程序接口 (Layered Driver Interface, LDI)

LDI 是一组 DDI/DKI,内核模块可以使用它来访问系统中的其他设备。另外使用 LDI 还可以确定内核模块当前使用的设备。

本章包含以下主题:

LDI 概述

LDI 包括以下两类接口:

讨论 LDI 时经常用到以下术语:

内核接口

通过某些 LDI 内核接口,LDI 可以跟踪和报告内核设备使用信息。请参见分层标识符-内核设备使用方

通过其他 LDI 内核接口,内核模块可以对目标设备执行 openreadwrite 之类的访问操作。 另外,通过这些 LDI 内核接口,内核设备使用方可以查询有关目标设备的属性和事件信息。请参见分层驱动程序句柄-目标设备

LDI 内核接口示例介绍了使用其中多个 LDI 接口的驱动程序示例。

分层标识符-内核设备使用方

通过分层标识符,LDI 可以跟踪和报告内核设备使用信息。分层标识符 (ldi_ident_t) 用于标识内核设备使用方。内核设备使用方必须先获取分层标识符,然后才能使用 LDI 打开目标设备。

分层驱动程序是唯一受支持的内核设备使用方类型。因此,分层驱动程序必须获取与设备编号、设备信息节点或分层驱动程序流关联的分层标识符。分层标识符与分层驱动程序关联。分层标识符与目标设备没有关联。

可以通过 libdevinfo(3LIB) 接口、fuser(1M) 命令或 prtconf(1M) 命令,检索通过 LDI 收集的内核设备使用信息。例如,使用 prtconf(1M) 命令可以显示分层驱动程序正在访问哪些目标设备,或者哪些分层驱动程序正在访问特定目标设备。要了解有关如何检索设备使用情况的更多信息,请参见用户接口

下面介绍了 LDI 分层标识符接口:

ldi_ident_t

分层标识符。属于不透明类型。

ldi_ident_from_dev(9F)

分配和检索与 dev_t 设备编号关联的分层标识符。

ldi_ident_from_dip(9F)

分配和检索与 dev_info_t 设备信息节点关联的分层标识符。

ldi_ident_from_stream(9F)

分配和检索与流关联的分层标识符。

ldi_ident_release(9F)

释放使用 ldi_ident_from_dev(9F)、ldi_ident_from_dip(9F) 或 ldi_ident_from_stream(9F) 分配的分层标识符。

分层驱动程序句柄-目标设备

内核设备使用方必须使用分层驱动程序句柄 (ldi_handle_t) 来通过 LDI 接口访问目标设备。ldi_handle_t 类型仅对 LDI 接口有效。当 LDI 成功打开某个设备时,将分配并返回此句柄。然后,内核设备使用方可使用此句柄通过 LDI 接口访问目标设备。LDI 在关闭设备时会取消分配该句柄。有关示例,请参见LDI 内核接口示例

本节讨论内核设备使用方如何访问目标设备并检索不同类型的信息。要了解内核设备使用方如何打开和关闭目标设备,请参见打开和关闭目标设备。要了解内核设备使用方如何对目标设备执行 readwritestrategyioctl 之类的操作,请参见访问目标设备检索目标设备信息介绍了用于检索目标设备信息(如设备打开类型和设备次要名称)的接口。检索目标设备属性值介绍了用于检索目标设备属性的值和地址的接口。要了解内核设备使用方如何接收来自目标设备的事件通知,请参见接收异步设备事件通知

打开和关闭目标设备

本节介绍用于打开和关闭目标设备的 LDI 内核接口。打开接口采用指向分层驱动程序句柄的指针。打开接口会尝试打开由设备编号、设备 ID 或路径名指定的目标设备。如果打开操作成功,则打开接口将分配并返回可用于访问目标设备的分层驱动程序句柄。关闭接口用于关闭与指定分层驱动程序句柄关联的目标设备,然后释放该分层驱动程序句柄。

ldi_handle_t

用于访问目标设备的分层驱动程序句柄。一种成功打开设备时返回的不透明数据结构。

ldi_open_by_dev(9F)

打开由 dev_t 设备编号参数指定的设备。

ldi_open_by_devid(9F)

打开由 ddi_devid_t 设备 ID 参数指定的设备。另外,还必须指定要打开的次要节点名称。

ldi_open_by_name(9F)

根据路径名打开设备。路径名是内核地址空间中以 NULL 结尾的字符串。路径名必须是以正斜杠字符 (/) 开头的绝对路径。

ldi_close(9F)

关闭使用 ldi_open_by_dev(9F)、ldi_open_by_devid(9F) 或 ldi_open_by_name (9F) 打开的设备。在 ldi_close(9F) 返回之后,已关闭的设备的分层驱动程序句柄不再有效。

访问目标设备

本节介绍用于访问目标设备的 LDI 内核接口。通过这些接口,内核设备使用方可以对由分层驱动程序句柄指定的目标设备执行操作。内核设备使用方可以对目标设备执行 readwritestrategyioctl 之类的操作。

ldi_handle_t

用于访问目标设备的分层驱动程序句柄。属于不透明数据结构。

ldi_read(9F)

将读取请求传递到目标设备的设备入口点。块设备、字符设备和 STREAMS 设备支持此操作。

ldi_aread(9F)

将异步读取请求传递到目标设备的设备入口点。块设备和字符设备支持此操作。

ldi_write(9F)

将写入请求传递到目标设备的设备入口点。块设备、字符设备和 STREAMS 设备支持此操作。

ldi_awrite(9F)

将异步写入请求传递到目标设备的设备入口点。块设备和字符设备支持此操作。

ldi_strategy(9F)

将策略请求传递到目标设备的设备入口点。块设备和字符设备支持此操作。

ldi_dump(9F)

将转储请求传递到目标设备的设备入口点。块设备和字符设备支持此操作。

ldi_poll(9F)

将轮询请求传递到目标设备的设备入口点。块设备、字符设备和 STREAMS 设备支持此操作。

ldi_ioctl(9F)

ioctl 请求传递到目标设备的设备入口点。块设备、字符设备和 STREAMS 设备支持此操作。LDI 支持 STREAMS 链接和 STREAMS ioctl 命令。请参见 ldi_ioctl(9F) 手册页的 "STREAM IOCTLS" 一节。另请参见 streamio(7I) 手册页中的 ioctl 命令。

ldi_devmap(9F)

devmap 请求传递到目标设备的设备入口点。块设备和字符设备支持此操作。

ldi_getmsg(9F)

从流中获取消息块。

ldi_putmsg(9F)

将消息块放在流中。

检索目标设备信息

本节介绍内核设备使用方可用于检索有关指定目标设备的设备信息的 LDI 接口。目标设备由分层驱动程序句柄指定。内核设备使用方可以接收设备编号、设备打开类型、设备 ID、设备次要名称和设备大小之类的信息。

ldi_get_dev(9F)

获取由分层驱动程序句柄指定的目标设备的 dev_t 设备编号。

ldi_get_otyp(9F)

获取用于打开由分层驱动程序句柄指定的目标设备的打开标志。此标志指示目标设备是字符设备还是块设备。

ldi_get_devid(9F)

获取由分层驱动程序句柄指定的目标设备的 ddi_devid_t 设备 ID。使用完设备 ID 后,应使用 ddi_devid_free(9F) 释放 ddi_devid_t

ldi_get_minor_name(9F)

检索包含为目标设备打开的次要节点的名称的缓冲区。使用完次要节点名称后,应使用 kmem_free(9F) 释放该缓冲区。

ldi_get_size(9F)

检索由分层驱动程序句柄指定的目标设备的分区大小。

检索目标设备属性值

本节介绍内核设备使用方可用于检索有关指定目标设备的属性信息的 LDI 接口。目标设备由分层驱动程序句柄指定。内核设备使用方可以接收属性的值和地址,以及确定某属性是否存在。

ldi_prop_exists(9F)

如果由分层驱动程序句柄指定的目标设备的属性存在,则返回 1。如果指定目标设备的属性不存在,则返回 0

ldi_prop_get_int(9F)

搜索与由分层驱动程序句柄指定的目标设备关联的 int 整数属性。如果找到整数属性,则返回属性值。

ldi_prop_get_int64(9F)

搜索与由分层驱动程序句柄指定的目标设备关联的 int64_t 整数属性。如果找到整数属性,则返回属性值。

ldi_prop_lookup_int_array(9F)

检索由分层驱动程序句柄指定的目标设备的 int 整数数组属性值的地址。

ldi_prop_lookup_int64_array(9F)

检索由分层驱动程序句柄指定的目标设备的 int64_t 整数数组属性值的地址。

ldi_prop_lookup_string(9F)

检索由分层驱动程序句柄指定的目标设备的以 null 结尾的字符串属性值的地址。

ldi_prop_lookup_string_array(9F)

检索字符串数组的地址。字符串数组是一个指针数组,指向由分层驱动程序句柄指定的目标设备的以 null 结尾的字符串属性值。

ldi_prop_lookup_byte_array(9F)

检索字节数组的地址。字节数组是由分层驱动程序句柄指定的目标设备的属性值。

接收异步设备事件通知

通过 LDI,内核设备使用方可以注册事件通知以及接收来自目标设备的事件通知。内核设备使用方可以注册发生事件时将会调用的事件处理程序。内核设备使用方必须先打开设备并接收分层驱动程序句柄,然后才能通过 LDI 事件通知接口注册事件通知。

通过 LDI 事件通知接口,内核设备使用方可以指定事件名称以及检索关联的内核事件 cookie。然后,内核设备使用方可以将分层驱动程序句柄 (ldi_handle_t)、cookie (ddi_eventcookie_t) 及事件处理程序传递到 ldi_add_event_handler(9F) 以注册事件通知。成功完成注册后,内核设备使用方会收到一个唯一的 LDI 事件处理程序标识符 (ldi_callback_id_t)。LDI 事件处理程序标识符属于不透明类型,只能用于 LDI 事件通知接口。

LDI 提供了一个框架,以用于注册其他设备生成的事件。LDI 本身并不定义任何事件类型,也不提供用于生成事件的接口。

下面介绍了 LDI 异步事件通知接口:

ldi_callback_id_t

事件处理程序标识符。属于不透明类型。

ldi_get_eventcookie(9F)

检索由分层驱动程序句柄指定的目标设备的事件服务 cookie。

ldi_add_event_handler(9F)

添加由 ldi_callback_id_t 注册标识符指定的回调处理程序。发生由 ddi_eventcookie_t cookie 指定的事件时,将会调用该回调处理程序。

ldi_remove_event_handler(9F)

删除由 ldi_callback_id_t 注册标识符指定的回调处理程序。

LDI 内核接口示例

本节介绍了一个使用本章前面几节中讨论的一些 LDI 调用的内核设备使用方示例。本节讨论此示例模块的下列几个方面:

此内核设备使用方示例名为 lyrlyr 模块是一个分层驱动程序,它使用 LDI 调用向目标设备发送数据。在其 open(9E) 入口点中,lyr 驱动程序将打开由 lyr.conf 配置文件中的 lyr_targ 属性指定的设备。在其 write(9E) 入口点中,lyr 驱动程序将其所有传入数据写入由 lyr_targ 属性指定的设备。

设备配置文件

在下面所示的配置文件中,lyr 驱动程序向其中写入数据的目标设备为控制台。


示例 14–1 配置文件

#
# Copyright 2004 Sun Microsystems, Inc.  All rights reserved.
# 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 设备。


示例 14–2 驱动程序源文件

#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 */
};

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));
}

Procedure如何生成和装入分层驱动程序

  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 设备上。


示例 14–3 向分层设备写入一条短消息

在本示例中,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) 调用。



示例 14–4 向分层设备写入一条较长的消息

以下命令将一条较长的消息写入 lyr 设备:


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

目标控制台上将会显示以下消息:

lyr: 
lyr_open: opened target '/dev/console' successfully on inst 1
#
# Copyright 2004 Sun Microsystems, Inc.  All rights reserved.
# 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


示例 14–5 更改目标设备

要更改目标设备,请编辑 /usr/kernel/drv/lyr.conf,并将 lyr_targ 属性的值更改为指向其他目标设备的路径。例如,该目标设备可以是在本地终端执行 tty 命令后的输出结果。例如,此类设备路径可以是 /dev/pts/4

在将驱动程序更新为使用新目标设备之前,应确保 lyr 设备未被使用。


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

使用 update_drv(1M) 命令重新装入 lyr.conf 配置文件:


# update_drv lyr

再次向 lyr 设备写入一条消息,并验证该消息是否显示在新的 lyr_targ 设备上。


用户接口

LDI 中包括用户级库和命令接口,用于报告设备分层和使用信息。设备信息库接口介绍了用于报告设备分层信息的 libdevinfo(3LIB) 接口。列显系统配置命令接口介绍了用于报告内核设备使用信息的 prtconf(1M) 接口。设备用户命令接口介绍了用于报告设备使用方信息的 fuser(1M) 接口。

设备信息库接口

LDI 中包括用于报告设备分层信息快照的 libdevinfo(3LIB) 接口。如果系统中的一个设备是同一系统中另一个设备的使用方,则会发生设备分层。仅当使用方和目标都绑定到快照中包含的设备节点时,才会报告设备分层信息。

libdevinfo(3LIB) 接口以有向图的形式报告设备分层信息。lnode 是一个抽象术语,在图中表示顶点,并被绑定到设备节点。可以使用 libdevinfo(3LIB) 接口来访问 lnode 的属性,如节点的名称和设备编号。

图中的边表示链接。链接既有表示设备使用方的源 lnode,也有表示目标设备的目标 lnode。

下面介绍了 libdevinfo(3LIB) 设备分层信息接口:

DINFOLYR

通过它来捕获设备分层信息的快照标志。

di_link_t

两个端点之间的有向链接。每个端点都是一个 di_lnode_t。属于不透明结构。

di_lnode_t

链接的端点。属于不透明结构。di_lnode_t 绑定到 di_node_t

di_node_t

表示设备节点。属于不透明结构。di_node_t 不一定绑定到 di_lnode_t

di_walk_link(3DEVINFO)

遍历快照中的所有链接。

di_walk_lnode(3DEVINFO)

遍历快照中的所有 lnode。

di_link_next_by_node(3DEVINFO)

获取下一个其中指定的 di_node_t 节点是源节点或目标节点的链接的句柄。

di_link_next_by_lnode(3DEVINFO)

获取下一个其中指定的 di_lnode_t lnode 是源 lnode 或目标 lnode 的链接的句柄。

di_link_to_lnode(3DEVINFO)

获取与 di_link_t 链接的指定端点对应的 lnode。

di_link_spectype(3DEVINFO)

获取链接的规范类型。规范类型指示如何访问目标设备。目标设备由目标 lnode 表示。

di_lnode_next(3DEVINFO)

获取下一个与指定的 di_node_t 设备节点关联的指定 di_lnode_t lnode 的句柄。

di_lnode_name(3DEVINFO)

获取与指定 lnode 关联的名称。

di_lnode_devinfo(3DEVINFO)

获取与指定 lnode 关联的设备节点的句柄。

di_lnode_devt(3DEVINFO)

获取与指定 lnode 关联的设备节点的设备编号。

LDI 返回的设备分层信息可能十分复杂。因此,LDI 提供了一些接口来协助遍历设备树和设备使用情况图。通过这些接口,设备树快照的使用方可以将自定义数据指针与快照中的不同结构关联。例如,应用程序遍历 lnode 时,它可以更新与每个 lnode 关联的自定义指针,以标记已经识别的 lnode。

下面介绍了 libdevinfo(3LIB) 节点和链接标记接口:

di_lnode_private_set(3DEVINFO)

将指定的数据与指定的 lnode 关联。通过此关联,可以遍历快照中的 lnode。

di_lnode_private_get(3DEVINFO)

检索指向通过调用 di_lnode_private_set(3DEVINFO) 而与 lnode 关联的数据的指针。

di_link_private_set(3DEVINFO)

将指定的数据与指定的链接关联。通过此关联,可以遍历快照中的链接。

di_link_private_get(3DEVINFO)

检索指向通过调用 di_link_private_set(3DEVINFO) 而与链接关联的数据的指针。

列显系统配置命令接口

prtconf(1M) 命令已得到增强,可以显示内核设备使用信息。缺省的 prtconf( 1M) 输出没有变化。如果在 prtconf(1M) 命令中指定详细选项 (-v),则会显示设备使用信息。如果在 prtconf(1M) 命令行上指定特定设备的路径,则会显示有关该设备的使用信息。

prtconf -v

显示设备次要节点和设备使用信息。显示内核使用方和每个内核使用方当前打开的次要节点。

prtconf path

显示由 path 指定的设备的设备使用信息。

prtconf -a path

显示由 path 指定的设备的设备使用信息,以及作为 path 的祖先的所有设备节点。

prtconf -c path

显示由 path 指定的设备的设备使用信息,以及作为 path 的子节点的所有设备节点。


示例 14–6 设备使用信息

如果需要有关特定设备的使用信息,path 参数的值可以是任何有效的设备路径。


% prtconf /dev/cfg/c0
SUNW,isptwo, instance #0


示例 14–7 祖先节点使用信息

要显示有关特定设备的使用信息以及作为其祖先的所有设备节点,请在 prtconf(1M) 命令中指定 -a 标志。祖先包括直到设备树的根的所有节点。如果在 prtconf(1M) 命令中指定 -a 标志,则还必须指定设备的 path 名称。


% prtconf -a /dev/cfg/c0
SUNW,Sun-Fire
    ssm, instance #0
        pci, instance #0
            pci, instance #0
                SUNW,isptwo, instance #0


示例 14–8 子节点使用信息

要显示有关特定设备的使用信息以及作为其子节点的所有设备节点,请在 prtconf(1M) 命令中指定 -c 标志。如果在 prtconf(1M) 命令中指定 -c 标志,则还必须指定设备的 path 名称。


% prtconf -c /dev/cfg/c0
SUNW,isptwo, instance #0
    sd (driver not attached)
    st (driver not attached)
    sd, instance #1
    sd, instance #0
    sd, instance #6
    st, instance #1 (driver not attached)
    st, instance #0 (driver not attached)
    st, instance #2 (driver not attached)
    st, instance #3 (driver not attached)
    st, instance #4 (driver not attached)
    st, instance #5 (driver not attached)
    st, instance #6 (driver not attached)
    ses, instance #0 (driver not attached)
    ...


示例 14–9 分层和设备次要节点信息-键盘

要显示有关特定设备的设备分层和设备次要节点信息,请在 prtconf(1M) 命令中指定 -v 标志。


% prtconf -v /dev/kbd
conskbd, instance #0
    System properties:
        ...
    Device Layered Over:
        mod=kb8042 dev=(101,0)
            dev_path=/isa/i8042@1,60/keyboard@0
    Device Minor Nodes:
        dev=(103,0)
            dev_path=/pseudo/conskbd@0:kbd
                spectype=chr type=minor
                dev_link=/dev/kbd
        dev=(103,1)
            dev_path=/pseudo/conskbd@0:conskbd
                spectype=chr type=internal
            Device Minor Layered Under:
                mod=wc accesstype=chr
                    dev_path=/pseudo/wc@0

本示例中,/dev/kbd 设备所在层位于硬件键盘设备 (/isa/i8042@1,60/keyboard@0) 之上。另外,本示例中,/dev/kbd 设备具有两个设备次要节点。第一个次要节点具有可用于访问该节点的 /dev 链接。第二个次要节点是一个无法通过文件系统访问的内部节点。wc 驱动程序(即工作站控制台)已经打开了第二个次要节点。请将本示例的输出与示例 14–12 的输出进行比较。



示例 14–10 分层和设备次要节点信息-网络设备

本示例说明哪些设备正在使用当前检测到的网络设备。


% prtconf -v /dev/iprb0
pci1028,145, instance #0
    Hardware properties:
        ...
    Interrupt Specifications:
        ...
    Device Minor Nodes:
        dev=(27,1)
            dev_path=/pci@0,0/pci8086,244e@1e/pci1028,145@c:iprb0
                spectype=chr type=minor
                alias=/dev/iprb0
        dev=(27,4098)
            dev_path=<clone>
            Device Minor Layered Under:
                mod=udp6 accesstype=chr
                    dev_path=/pseudo/udp6@0
        dev=(27,4097)
            dev_path=<clone>
            Device Minor Layered Under:
                mod=udp accesstype=chr
                    dev_path=/pseudo/udp@0
        dev=(27,4096)
            dev_path=<clone>
            Device Minor Layered Under:
                mod=udp accesstype=chr
                    dev_path=/pseudo/udp@0

本示例中,在采用 udpudp6 的情况下链接了 iprb0 设备。请注意,此处并未显示指向采用 udpudp6 的次要节点的任何路径。本示例中未显示任何路径是因为次要节点是通过对 iprb 驱动程序执行 clone 打开操作创建的,因此不存在可以访问这些节点的文件系统路径。请将本示例的输出与示例 14–11 的输出进行比较。


设备用户命令接口

fuser(1M) 命令已得到增强,可以显示设备使用信息。仅当 path 表示设备次要节点时,fuser(1M) 命令才会显示设备使用信息。仅当指定了表示设备次要节点的 path 时,在 fuser(1M) 命令中使用 -d 标志才会有效。

fuser path

显示有关应用程序设备使用方和内核设备使用方的信息(如果 path 表示设备次要节点)。

fuser -d path

显示与 path 表示的设备次要节点关联的基础设备的所有用户。

报告内核设备使用方时采用以下四种格式之一。内核设备使用方始终用方括号 ([]) 括起来。


        [kernel_module_name]
        [kernel_module_name,dev_path=path]
        [kernel_module_name,dev=(major,minor)]
        [kernel_module_name,dev=(major,minor),dev_path=path]

如果 fuser(1M) 命令显示的是文件或设备用户,则输出由 stdout 中的进程 ID 后跟 stderr 中的字符组成。stderr 中的字符描述如何使用文件或设备。stderr 中会显示所有内核使用方信息。而 stdout 中不会显示任何内核使用方信息。

如果不使用 -d 标志,则 fuser(1M) 命令仅报告由 path 指定的设备次要节点的使用方。如果使用 -d 标志,则 fuser(1M) 命令会报告由 path 指定的次要节点的基础设备节点的使用方。以下示例说明了这两种情况下报告输出的差别。


示例 14–11 基础设备节点的使用方

大多数网络设备在打开时都会克隆其次要节点。如果请求克隆次要节点的设备使用信息,则该使用信息可能会表明没有任何进程在使用该设备。而如果请求基础设备节点的设备使用信息,则该使用信息可能会表明某个进程正在使用该设备。在本示例中,如果仅将设备 path 传递到 fuser(1M) 命令,则不会报告任何设备使用方。如果使用 -d 标志,则输出表明正在采用 udpudp6 \uc2\u26469 来访问该设备。


% fuser /dev/iprb0
/dev/iprb0:
% fuser -d /dev/iprb0
/dev/iprb0:  [udp,dev_path=/pseudo/udp@0] [udp6,dev_path=/pseudo/udp6@0]

请将本示例的输出与示例 14–10 的输出进行比较。



示例 14–12 键盘设备的使用方

在本示例中,某个内核使用方正在访问 /dev/kbd。正在访问 /dev/kbd 设备的内核使用方是工作站控制台驱动程序。


% fuser -d /dev/kbd
/dev/kbd:  [genunix] [wc,dev_path=/pseudo/wc@0]

请将本示例的输出与示例 14–9 的输出进行比较。