本节介绍了一个使用本章前面几节中讨论的一些 LDI 调用的内核设备使用方示例。本节讨论此示例模块的下列几个方面:
此内核设备使用方示例名为 lyr。lyr 模块是一个分层驱动程序,它使用 LDI 调用向目标设备发送数据。在其 open(9E) 入口点中,lyr 驱动程序将打开由 lyr.conf 配置文件中的 lyr_targ 属性指定的设备。在其 write(9E) 入口点中,lyr 驱动程序将其所有传入数据写入由 lyr_targ 属性指定的设备。
在下面所示的配置文件中,lyr 驱动程序向其中写入数据的目标设备为控制台。
示例 14-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_state_t 结构保存 lyr 驱动程序的软状态。该软状态包括 lyr_targ 设备的分层驱动程序句柄 (lh) 和 lyr 设备的分层标识符 (li)。有关软状态的更多信息,请参见Retrieving Driver Soft State Information。
在 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 */
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));
}
使用 –D_KERNEL 选项指示这是一个内核模块。
如果要针对 SPARC 体系结构进行编译,请使用 –xarch=v9 选项:
% cc -c -D_KERNEL -xarch=v9 lyr.c
如果要针对 32 位 x86 体系结构进行编译,请使用以下命令:
% cc -c -D_KERNEL lyr.c
% ld -r -o lyr lyr.o
以 root 用户身份,将配置文件复制到计算机的内核驱动程序区域:
# cp lyr.conf /usr/kernel/drv
以 root 用户身份,将驱动程序二进制文件复制到 SPARC 体系结构的 sparcv9 驱动程序区域:
# cp lyr /usr/kernel/drv/sparcv9
以 root 用户身份,将驱动程序二进制文件复制到 32 位 x86 体系结构的 drv 驱动程序区域:
# cp lyr /usr/kernel/drv
以 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() and 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 # # 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 设备上。