编写模板驱动程序
本节讲述本驱动程序所包含的入口点和数据结构,并介绍了其定义。所有这些数据结构和几乎所有这些入口点对于任何字符设备驱动程序都是必需的。
本节讲述下列入口点和数据结构:
-
可加载模块配置入口点
-
自动配置入口点
-
用户上下文入口点
-
字符和块操作结构
-
设备操作结构
-
模块连接结构
首先,创建一个目录,用于开发驱动程序。本驱动程序名为 dummy,因为本驱动程序不做任何实际工作。接下来打开一个新文本文件,名为
dummy.c。
编写可加载模块配置入口点
不管什么类型,每个内核模块必须至少定义以下 3 个可加载模块配置入口点:
mod_install(9F)、mod_info(9F) 和 mod_remove(9F) 函数在每个驱动程序里的用法都完全相同,不管驱动程序的功能是什么。您无需关心这些函数的参数的值应该是什么。您可以从本例复制这些函数调用并将它们粘贴到您所编写的任何驱动程序中。
在本节中,下列代码被添加到 dummy.c 源文件中:
/* Loadable module configuration entry points */
int
_init(void)
{
cmn_err(CE_NOTE, "Inside _init");
return(mod_install(&ml));
}
int
_info(struct modinfo *modinfop)
{
cmn_err(CE_NOTE, "Inside _info");
return(mod_info(&ml, modinfop));
}
int
_fini(void)
{
cmn_err(CE_NOTE, "Inside _fini");
return(mod_remove(&ml));
}
声明可加载模块配置入口点
_init(9E)、_info(9E) 和 _fini(9E) 例程名称对于任何特定的内核模块都不惟一。在模块中对它们进行定义时,可以定制这些例程的行为,但这些例程的名称都不惟一。这些
3 个例程是在 modctl.h 头文件中声明的。在 dummy.c 文件中需要包含
modctl.h 头文件。请勿在 dummy.c 声明这 3 个例程。
定义模块初始化入口点
_init(9E)
例程返回类型 int 且不带参数。_init(9E) 例程必须调用 mod_install(9F) 函数并返回
mod_install(9F) 所返回的成功或失败值。
mod_install(9F) 函数带有一个参数,该参数是一种 modlinkage(9S) 结构。请参阅定义模块连接结构,了解信息 modlinkage(9S) 结构的有关信息。
每当输入一个入口点时,本驱动程序都应该写一条消息。使用 cmn_err(9F)
函数可以将一条消息写入系统日志。cmn_err(9F) 函数通常 用于报告错误状态。cmn_err(9F) 函数对于调试也很有用,且这种调试方式与在用户程序中使用打印语句的方式相同。
cmn_err(9F) 函数需要包含 cmn_err.h 头文件、ddi.h
头文件和 sunddi.h 头文件。cmn_err(9F) 函数带有两个参数。第一个参数是一个常量,用于指示错误消息的严重性。本驱动程序所写的消息不是错误消息,而只是测试消息。将
CE_NOTE 用于该严重性常量的值。cmn_err(9F) 函数的第二个参数是一个字符串消息。
下列代码是应当输入 dummy.c 文件的 _init(9E) 例程。ml 结构就是定义模块连接结构中所讨论的 modlinkage(9S) 结构。
int
_init(void)
{
cmn_err(CE_NOTE, "Inside _init");
return(mod_install(&ml));
} 定义模块信息入口点
_info(9E)
例程返回类型 int,它带有一个参数,该参数是一个指向不透明的 modinfo 结构的指针。_info(9E)
例程必须返回 mod_info(9F)
函数所返回的值。
mod_info(9F) 函数带有两个参数。mod_info(9F) 的第一个参数是一个 modlinkage(9S)
结构。请参阅定义模块连接结构,了解modlinkage(9S) 结构的有关信息。mod_info(9F)
的第二个参数是同一个 modinfo 结构指针,它也是 _info(9E) 例程的参数。如果出现错误,mod_info(9F)
函数则返回模块信息或返回零。
使用 cmn_err(9F)
函数可以将消息写入系统日志,并且其方式与您过去在 _init(9E) 入口点中使用 cmn_err(9F) 函数的方式相同。
下列代码是应当输入 dummy.c 文件的 _info(9E) 例程。定义模块连接结构中讨论了 ml 结构。modinfop
参数是指向某一不透明结构的一个指针,系统用该结构来传递模块信息。
int
_info(struct modinfo *modinfop)
{
cmn_err(CE_NOTE, "Inside _info");
return(mod_info(&ml, modinfop));
} 定义模块卸载入口点
_fini(9E)
例程返回类型 int 且不带参数。_fini(9E) 例程必须调用 mod_remove(9F)
函数并返回 mod_remove(9F) 所返回的成功或失败值。
当 mod_remove(9F) 返回成功值时,_fini(9E) 例程必须撤消 _init(9E) 例程所做的每一件事。_fini(9E)
例程必须调用 mod_remove(9F) 因为 _init(9E) 例程调用了 mod_install(9F)。_fini(9E)
例程必须取消分配已经分配的一切,关闭已经打开的一切,并删除 in _init(9E) 例程中已经创建的一切。
加载模块时可以随时调用 _fini(9E) 例程。在正常的操作中,_fini(9E) 例程经常失败。这种情况是很正常的,因为内核允许模块决定是否可以卸载模块。如果
mod_remove(9F) 返回成功值,则模块判定该设备是分离的,该模块可以卸载。如果 mod_remove(9F)
返回失败值, 模块则判定该设备不是分离的,且该模块不能卸载。
调用 mod_remove(9F) 时会有下列操作发生:
-
内核检查本驱动程序是否在执行。下列条件中若有一个为真,则本驱动程序在执行:
-
如果本驱动程序在执行,则 mod_remove(9F) 和 _fini(9E) 都返回失败值。
-
如果本驱动程序未执行,则该内核调用该驱动程序的
detach(9E)
入口点。
mod_remove(9F) 函数带有一个参数,该参数是一个 modlinkage(9S) 结构。请参阅定义模块连接结构,了解 modlinkage(9S) 结构的有关信息。
使用 cmn_err(9F)
函数可以将消息写入系统日志,并且其方式与您过去在 _init(9E) 入口点中使用 cmn_err(9F) 函数的方式相同。
下列代码是应当输入 dummy.c 文件的 _fini(9E) 例程。定义模块连接结构中讨论了 ml 结构。
int
_fini(void)
{
cmn_err(CE_NOTE, "Inside _fini");
return(mod_remove(&ml));
} 包含可加载的模块配置头文件
_init(9E)、_info(9E)、_fini(9E)
和 mod_install(9F)
函数都要求包含 modctl.h 头文件。cmn_err(9F)
函数要求包含 cmn_err.h 头文件、ddi.h 头文件和 sunddi.h头文件。
下列头文件是本节中编写的三个可加载模块配置例程所必需的。请在 dummy.c 文件的顶部附近包含以下代码。
#include <sys/modctl.h> /* used by _init, _info, _fini */
#include <sys/cmn_err.h> /* used by all entry points for this driver */
#include <sys/ddi.h> /* used by all entry points for this driver */
#include <sys/sunddi.h> /* used by all entry points for this driver */ 编写自动配置入口点
每个字符驱动程序都必须至少定义下列自动配置入口点。加载该设备驱动程序时内核就会调用这些例程。
本节中添加了下列代码:
/* Device autoconfiguration entry points */
static int
dummy_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
{
cmn_err(CE_NOTE, "Inside dummy_attach");
switch(cmd) {
case DDI_ATTACH:
dummy_dip = dip;
if (ddi_create_minor_node(dip, "0", S_IFCHR,
ddi_get_instance(dip), DDI_PSEUDO,0)
!= DDI_SUCCESS) {
cmn_err(CE_NOTE,
"%s%d: attach: could not add character node.",
"dummy", 0);
return(DDI_FAILURE);
} else
return DDI_SUCCESS;
default:
return DDI_FAILURE;
}
}
static int
dummy_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
{
cmn_err(CE_NOTE, "Inside dummy_detach");
switch(cmd) {
case DDI_DETACH:
dummy_dip = 0;
ddi_remove_minor_node(dip, NULL);
return DDI_SUCCESS;
default:
return DDI_FAILURE;
}
}
static int
dummy_getinfo(dev_info_t *dip, ddi_info_cmd_t cmd, void *arg,
void **resultp)
{
cmn_err(CE_NOTE, "Inside dummy_getinfo");
switch(cmd) {
case DDI_INFO_DEVT2DEVINFO:
*resultp = dummy_dip;
return DDI_SUCCESS;
case DDI_INFO_DEVT2INSTANCE:
*resultp = 0;
return DDI_SUCCESS;
default:
return DDI_FAILURE;
}
}
static int
dummy_prop_op(dev_t dev, dev_info_t *dip, ddi_prop_op_t prop_op,
int flags, char *name, caddr_t valuep, int *lengthp)
{
cmn_err(CE_NOTE, "Inside dummy_prop_op");
return(ddi_prop_op(dev,dip,prop_op,flags,name,valuep,lengthp));
}
声明自动配置入口点
对本驱动程序,attach(9E)、detach(9E)、getinfo(9E) 和prop_op(9E) 入口点例程需要具有惟一的名称。选择一个前缀用于每个入口点例程。
注 - 根据惯例,用于只有本驱动程序才有的函数和数据名的前缀,或者是本驱动程序名,或者是本驱动程序名的缩写。在本驱动程序中都使用相同的前缀。这样调试就轻松了许多。
在本章所示的这个例子中,dummy_ 被用作专用于本例的每个函数和数据名的前缀。
下列声明是 dummy.c 文件中应有含有的自动配置入口点声明。注意这些函数都被声明为 static。
static int dummy_attach(dev_info_t *dip, ddi_attach_cmd_t cmd);
static int dummy_detach(dev_info_t *dip, ddi_detach_cmd_t cmd);
static int dummy_getinfo(dev_info_t *dip, ddi_info_cmd_t cmd, void *arg,
void **resultp);
static int dummy_prop_op(dev_t dev, dev_info_t *dip, ddi_prop_op_t prop_op,
int flags, char *name, caddr_t valuep, int *lengthp); 定义设备连接入口点
attach(9E)
例程返回类型 int。attach(9E) 例程必须返回 DDI_SUCCESS 或 DDI_FAILURE。这两个常量是在
sunddi.h 中定义的。除了 prop_op(9E) 以外,所有自动配置入口点例程都返回 DDI_SUCCESS
或 DDI_FAILURE。
attach(9E) 例程带有两个参数。第一个参数是一个指向本驱动程序的 dev_info 结构的指针。所有自动配置入口点例程都带有一个
dev_info 参数。第二个参数是一个常量,用于指定连接类型。通过该第二个参数传递的值或者是 DDI_ATTACH
或者是 DDI_RESUME。 每个 attach(9E) 例程必须至少为 DDI_ATTACH
定义行为。
DDI_ATTACH 代码必须初始化一个设备实例。在实际的驱动程序中,可以通过使用状态结构和 ddi_soft_state(9F)
函数来定义和管理驱动程序的多个实例。驱动程序每个实例都有其自己的状态结构副本,该状态结构保存着特定于该实例的数据。特定于每一实例的一个数据块是设备实例指针。该设备驱动程序的每个实例都由
/devices 中的一个单独的设备文件来表示。每个设备实例文件都由一个单独的设备实例指针来指向。请参阅管理设备状态,了解状态结构和 ddi_soft_state(9F) 函数的有关信息。参阅 Devices as Files,了解设备文件和实例的有关信息。
该 dummy 驱动程序仅支持一个实例。由于本驱动程序仅支持一个实例,本驱动程序不使用状态结构。本驱动程序还须声明一个设备实例指针,并在
attach(9E) 例程中初始化该指针值。请在 dummy.c 的开头附近输入下列代码来为本驱动程序声明一个设备实例指针:
dev_info_t *dummy_dip; /* keep track of one instance*/ 下列代码是应当输入 dummy.c 文件的 dummy_attach()
例程。可以从在声明自动配置入口点中输入的声明代码中直接复制该函数定义的名称部分。
static int dummy_attach(dev_info_t *dip, ddi_attach_cmd_t cmd) { cmn_err(CE_NOTE, "Inside dummy_attach"); switch(cmd) { case DDI_ATTACH: dummy_dip = dip; if (ddi_create_minor_node(dip, "0", S_IFCHR, ddi_get_instance(dip), DDI_PSEUDO,0) != DDI_SUCCESS) { cmn_err(CE_NOTE, "%s%d: attach: could not add character node.", "dummy", 0); return(DDI_FAILURE); } else return DDI_SUCCESS; default: return DDI_FAILURE; } }
首先,使用 cmn_err(9F) 将一条消息写入系统日志,与在 _init(9E) 入口点中的做法一样。然后提供
DDI_ATTACH 行为。在 DDI_ATTACH 代码中,首先从 dummy_attach()
参数将设备实例指针分配给在上面声明的 dummy_dip 变量。您需要在该全局变量中保存该指针值,以便能够使用该指针从
dummy_getinfo() 获取该实例的信息,并在
dummy_detach() 中分离该实例。在该 dummy_attach()
例程中,该设备实例指针被 ddi_get_instance(9F)
函数用于返回该实例的编号。设备实例指针和实例编号都被 ddi_create_minor_node(9F)
用于创建新的设备节点。
实际的驱动程序可能会使用 ddi_soft_state(9F) 函数来创建和管理设备节点。该 dummy
驱动程序使用 ddi_create_minor_node(9F) 函数来创建设备节点。ddi_create_minor_node(9F)
函数带有 6 个参数。ddi_create_minor_node(9F) 函数的第一个参数是指向该设备的 dev_info
结构的设备实例指针。第二个参数是该次要设备节点的名称。如果该设备是一个字符次要设备,则第三个参数是 S_IFCHR;如果该设备是一个块次要设备,则是
S_IFBLK。该 dummy 驱动程序是一个字符驱动程序。
ddi_create_minor_node(9F) 函数的第四个参数是该次要设备的较小编号。该编号也叫做实例号。ddi_get_instance(9F)
函数返回该实例号。ddi_create_minor_node(9F) 函数的第五个参数是节点类型。ddi_create_minor_node(9F)
手册页列出了可能的节点类型。DDI_PSEUDO 节点类型用于伪设备。ddi_create_minor_node(9F)
函数的第六个参数指定设备是不是一个克隆设备。由于该设备不是克隆设备,因此该参数值设为 0。
如果 ddi_create_minor_node(9F) 调用不成功,则向系统日志写入一条消息,并返回 DDI_FAILURE。如果
ddi_create_minor_node(9F) 调用成功,则返回 DDI_SUCCESS。如果。如果该
dummy_attach() 例程接收了任何不同于 DDI_ATTACH
的 cmd,则返回 DDI_FAILURE。
定义设备分离入口点
detach(9E)
例程带有两个参数。第一个参数是一个指向本驱动程序的 dev_info 结构的指针。第二个参数是一个常量,用于指定分离类型。通过该第二个参数传递的值或者是
DDI_DETACH,或者是 DDI_SUSPEND。每个 detach(9E) 例程都必须至少定义
DDI_DETACH 的行为。
DDI_DETACH 代码必须撤消 DDI_ATTACH 代码做过的每一件事。在
attach(9E) 例程的 DDI_ATTACH 代码中曾保存了一个新 dev_info
结构的地址,并调用了 ddi_create_minor_node(9F) 函数来创建新节点。在该 detach(9E)
例程的 DDI_DETACH 代码中,需要为该节点重新设置曾指向 dev_info
结构的变量,还需要调用 ddi_remove_minor_node(9F)
函数删除该节点。detach(9E) 例程必须取消分配已经分配的一切,关闭已经打开的一切,并删除 attach(9E)
例程中已经创建的一切。
下列代码是应当输入 dummy.c 文件的 dummy_detach()
例程。可以从在声明自动配置入口点中输入的声明代码中直接复制该函数定义的名称部分。
static int
dummy_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
{
cmn_err(CE_NOTE, "Inside dummy_detach");
switch(cmd) {
case DDI_DETACH:dummy_dip = 0;
ddi_remove_minor_node(dip, NULL);
return DDI_SUCCESS;
default:return DDI_FAILURE;
}
} 首先,使用 cmn_err(9F) 将一条消息写入系统日志,与在
_init(9E) 入口点中的做法一样。然后提供 DDI_DETACH 行为。 在DDI_DETACH
代码中,先重新设置在上面的 dummy_attach()
中设置的 dummy_dip 变量。该设备实例指针无法重新设置,除非删除该设备的所有实例。该 dummy
驱动程序仅支持一个实例。
接下来调用 ddi_remove_minor_node(9F) 函数来该设备节点。ddi_remove_minor_node(9F)
函数带有两个参数。第一个参数是指向该设备 dev_info 结构设备实例指针。第二个参数是要删除的次要设备节点的名称。
如果该次要设备节点参数的值是 NULL,则 ddi_remove_minor_node(9F) 删除该设备的所有实例。由于该驱动程序的
DDI_DETACH 代码问题删除所有实例,因此该 dummy 驱动程序仅支持一个实例。
如果该 dummy_detach() 例程的 cmd 参数的值是
DDI_DETACH,则删除该设备的所有实例,并返回 DDI_SUCCESS。如果该 dummy_detach()
例程收到了不同于 DDI_DETACH 的任何 cmd,则都返回 DDI_FAILURE。
定义获取驱动程序信息入口点
getinfo(9E)
例程带有一个指向设备号的指针,并返回一个指向设备信息结构的指针,或返回一个设备实例号。getinfo(9E) 例程的返回值是
DDI_SUCCESS 或 DDI_FAILURE。从 getinfo(9E) 例程请求的指针或实例号通过指针参数。
getinfo(9E) 例程带有 4 个参数。第一个参数是指向本驱动程序的 dev_info 结构的一个指针。该
dev_info 结构参数已经废弃,getinfo(9E) 例程不再使用它。
getinfo(9E) 例程的第二个参数是一个常量,用于指定 getinfo(9E) 例程必须返回的信息。第二个参数的值或者是
DDI_INFO_DEVT2DEVINFO,或者是 DDI_INFO_DEVT2INSTANCE。getinfo(9E)
例程的第三个参数是指向设备号的一个指针。第四个参数是指向 getinfo(9E) 例程必须用于存储请求信息的位置的一个指针。存储在该位置的信息取决于
getinfo(9E) 例程第二个参数所传递的值。
下表说明了 getinfo(9E) 例程的第二和第四个参数之间的关系。
Table 2-1 获取驱动程序信息入口点参数
| cmd |
arg |
resultp
|
| DDI_INFO_DEVT2DEVINFO
|
设备号 |
设备信息结构指针 |
| DDI_INFO_DEVT2INSTANCE |
设备号 |
设备实例号 |
下列代码是应当输入 dummy.c 文件的 dummy_getinfo()
例程。可以从在声明自动配置入口点中输入的声明代码中直接复制该函数定义的名称部分。
static int
dummy_getinfo(dev_info_t *dip, ddi_info_cmd_t cmd, void *arg,
void **resultp)
{
cmn_err(CE_NOTE, "Inside dummy_getinfo");
switch(cmd) {
case DDI_INFO_DEVT2DEVINFO:*resultp = dummy_dip;
return DDI_SUCCESS;
case DDI_INFO_DEVT2INSTANCE:*resultp = 0;
return DDI_SUCCESS;
default:return DDI_FAILURE;
}
} 首先,使用 cmn_err(9F) 将一条消息写入系统日志,与在
_init(9E) 入口点中的做法一样。然后提供 DDI_INFO_DEVT2DEVINFO 行为。
实际的驱动程序会使用 arg 来获取该设备节点的实例号。实际的驱动程序然后会调用 ddi_get_soft_state(9F)
函数并返回该状态结构的设备信息结构指针。该 dummy 驱动程序仅支持一个实例且不使用状态结构。在这个
dummy_getinfo() 例程的 DDI_INFO_DEVT2DEVINFO
代码中,只返回 dummy_attach() 例程所保存的一个设备信息结构指针。
接下来提供 DDI_INFO_DEVT2INSTANCE 行为。在 DDI_INFO_DEVT2INSTANCE代码中,仅返回
0。该 dummy 驱动程序仅支持一个实例。该实例的实例编号是 0。
定义报告驱动程序属性信息入口点
prop_op(9E)
入口点对于每个驱动程序都是必需的。若驱动程序不需要定制 prop_op(9E) 入口点的行为,则驱动程序可以的将 ddi_prop_op(9F)
函数用于 prop_op(9E) 入口点。创建和管理其自身属性的驱动程序需要一个自定义的 prop_op(9E) 例程。在调用
ddi_prop_op(9F) 函数之前,该 dummy 驱动程序使用 prop_op(9E) 例程来调用
cmn_err(9F)
。
prop_op(9E) 入口点和 ddi_prop_op(9F) 函数都要求包含 types.h 头文件。prop_op(9E)
入口点和 ddi_prop_op(9F) 函数都带有相同的 7 个参数。在此不讨论这些参数,因为该 dummy
驱动程序不创建和管理其自身属性。请参阅 prop_op(9E) 手册页,了解 prop_op(9E) 参数。
下列代码是应当输入 dummy.c 文件的 dummy_prop_op()
例程 。可以从在声明自动配置入口点中输入的声明代码中直接复制该函数定义的名称部分。
static int
dummy_prop_op(dev_t dev, dev_info_t *dip, ddi_prop_op_t prop_op,
int flags, char *name, caddr_t valuep, int *lengthp)
{
cmn_err(CE_NOTE, "Inside dummy_prop_op");
return(ddi_prop_op(dev,dip,prop_op,flags,name,valuep,lengthp));
} 首先,使用 cmn_err(9F) 将一条消息写入系统日志,与在
_init(9E) 入口点中的做法一样。然后调用 ddi_prop_op(9F) 函数,它与 dummy_prop_op()
函数具有完全相同的参数。
包含自动配置头文件
所有的自动配置入口点例程和所有的用户上下文入口点例程都要求包含 ddi.h 和 sunddi.h
头文件。您已经为 cmn_err(9F) 函数包含了这两个头文件。
ddi_create_minor_node(9F)
函数需要 stat.h 头文件。dummy_attach()
例程调用 ddi_create_minor_node(9F) 函数。prop_op(9E)
和 ddi_prop_op(9F)
函数需要 types.h 头文件。
下列代码是用于本节编写的 4 个自动配置例程和上节编写的 3 个可加载模块配置例程的头文件列表,它们现在都应包含在
dummy.c 文件中。
#include <sys/modctl.h> /* used by _init, _info, _fini */ #include <sys/types.h> /* used by prop_op, ddi_prop_op */ #include <sys/stat.h> /* defines S_IFCHR used by ddi_create_minor_node */ #include <sys/cmn_err.h> /* used by all entry points for this driver */ #include <sys/ddi.h> /* used by all entry points for this driver */ /* also used by ddi_get_instance, ddi_prop_op */ #include <sys/sunddi.h> /* used by all entry points for this driver */ /* also used by ddi_create_minor_node, */ /* ddi_get_instance, and ddi_prop_op */
编写用户上下文入口点
用户上下文入口点与系统调用紧密对应。如果某一系统调用打开了某一设备文件,则该设备驱动程序中的
open(9E) 例程就会被调用。
所有的字符和块驱动程序都必须定义 open(9E) 用户上下文入口点。不过,open(9E) 例程可以是 nulldev(9F)。close(9E)、read(9E)
和 write(9E) 用户上下文例程是可选的。
本节中添加了下列代码:
/* Use context entry points */
static int
dummy_open(dev_t *devp, int flag, int otyp, cred_t *cred)
{
cmn_err(CE_NOTE, "Inside dummy_open");
return DDI_SUCCESS;
}
static int
dummy_close(dev_t dev, int flag, int otyp, cred_t *cred)
{
cmn_err(CE_NOTE, "Inside dummy_close");
return DDI_SUCCESS;
}
static int
dummy_read(dev_t dev, struct uio *uiop, cred_t *credp)
{
cmn_err(CE_NOTE, "Inside dummy_read");
return DDI_SUCCESS;
}
static int
dummy_write(dev_t dev, struct uio *uiop, cred_t *credp)
{
cmn_err(CE_NOTE, "Inside dummy_write");
return DDI_SUCCESS;
}
声明用户上下文入口点
对本驱动程序,用户上下文入口点例程需要具有惟一的名称。对于用于每一个自动配置入口点例程的每一个用户上下文入口点,都使用相同的前缀。下列声明件是应当包含在
dummy.c 文件中的入口点声明:
static int dummy_attach(dev_info_t *dip, ddi_attach_cmd_t cmd);
static int dummy_detach(dev_info_t *dip, ddi_detach_cmd_t cmd);
static int dummy_getinfo(dev_info_t *dip, ddi_info_cmd_t cmd, void *arg,
void **resultp);
static int dummy_prop_op(dev_t dev, dev_info_t *dip, ddi_prop_op_t prop_op,
int flags, char *name, caddr_t valuep, int *lengthp);
static int dummy_open(dev_t *devp, int flag, int otyp, cred_t *cred);
static int dummy_close(dev_t dev, int flag, int otyp, cred_t *cred);
static int dummy_read(dev_t dev, struct uio *uiop, cred_t *credp);
static int dummy_write(dev_t dev, struct uio *uiop, cred_t *credp); 定义打开设备入口点
open(9E)
例程返回类型 int。open(9E) 例程应当返回 DDI_SUCCESS 或相应的错误号。
open(9E) 例程带有 4 个参数。由于该 dummy 驱动程序特别简单,因此该 dummy_open()
例程不使用任何 open(9E) 参数。第 3 章,读写内核内存中的数据中的例子更详细地说明了 open(9E) 例程。
下列代码是应当输入 dummy.c 文件的 dummy_open()
例程。可以从在声明用户上下文入口点中输入的声明代码中直接复制该函数定义的名称部分。将一条消息写入系统日志并返回成功。
static int
dummy_open(dev_t *devp, int flag, int otyp, cred_t *cred)
{
cmn_err(CE_NOTE, "Inside dummy_open");
return DDI_SUCCESS;
} 定义关闭入口点
close(9E)
例程返回类型 int。close(9E) 例程应当返回 DDI_SUCCESS 或相应的错误号。
close(9E) 例程带有 4 个参数。由于该 dummy 驱动程序特别简单,因此该 dummy_close()
例程不使用任何 close(9E) 参数。第 3 章,读写内核内存中的数据中的例子更详细地说明了 close(9E) 例程。
close(9E) 例程必须撤消 open(9E) 例程所做的每一件事。close(9E) 例程必须取消分配已经分配的一切,关闭已经打开的一切,并删除
open(9E) 例程中已经创建的一切。在该 dummy 驱动程序中,由于 open(9E) 例程特别简单,因此在
close(9E) 例程中无需要进行任何撤回或取消。
下列代码是应当输入 dummy.c 文件的 dummy_close()
例程。可以从在声明用户上下文入口点中输入的声明代码中直接复制该函数定义的名称部分。将一条消息写入系统日志并返回成功。
static int
dummy_close(dev_t dev, int flag, int otyp, cred_t *cred)
{
cmn_err(CE_NOTE, "Inside dummy_close");
return DDI_SUCCESS;
} 定义读设备入口点
read(9E)
例程返回类型 int。read(9E) 例程应当返回 DDI_SUCCESS 或相应的错误号。
read(9E) 例程带有 3 个参数。由于该 dummy 驱动程序特别简单,因此该 dummy_read()
例程不使用任何 read(9E) 参数。第 3 章,读写内核内存中的数据中的例子更详细地说明了 read(9E) 例程。
下列代码是应当输入 dummy.c 文件的 dummy_read()
例程。可以从在声明用户上下文入口点中输入的声明代码中直接复制该函数定义的名称部分。将一条消息写入系统日志并返回成功。
static int
dummy_read(dev_t dev, struct uio *uiop, cred_t *credp)
{
cmn_err(CE_NOTE, "Inside dummy_read");
return DDI_SUCCESS;
} 定义写设备入口点
write(9E)
例程返回类型 int。write(9E) 例程应当返回 DDI_SUCCESS 或相应的错误号。
write(9E) 例程带有 3 个参数。该 dummy 驱动程序特别简单,因此该 dummy_write()
例程不使用任何 write(9E) 参数。第 3 章,读写内核内存中的数据中的例子更详细地说明了 write(9E) 例程。
下列代码是应当输入 dummy.c 文件的 dummy_write()
例程。可以从在声明用户上下文入口点中输入的声明代码中直接复制该函数定义的名称部分。将一条消息写入系统日志并返回成功。
static int
dummy_write(dev_t dev, struct uio *uiop, cred_t *credp)
{
cmn_err(CE_NOTE, "Inside dummy_write");
return DDI_SUCCESS;
} 包含用户上下文头文件
有 4 个用户上下文入口点例程要求模块包含几个头文件。已经包含的有 types.h 头文件、ddi.h
头文件和 sunddi.h 头文件。还需要包含 file.h、errno.h、open.h、cred.h
和 uio.h 头文件。
对于本节和前两节编写的所有入口点,下列代码是现在应该已经包含在 dummy.c 文件中的头文件列表:
#include <sys/modctl.h> /* used by modlinkage, modldrv, _init, _info, */
/* and _fini */
#include <sys/types.h> /* used by open, close, read, write, prop_op, */
/* and ddi_prop_op */
#include <sys/file.h> /* used by open, close */
#include <sys/errno.h> /* used by open, close, read, write */
#include <sys/open.h> /* used by open, close, read, write */
#include <sys/cred.h> /* used by open, close, read */
#include <sys/uio.h> /* used by read */
#include <sys/stat.h> /* defines S_IFCHR used by ddi_create_minor_node */
#include <sys/cmn_err.h> /* used by all entry points for this driver */
#include <sys/ddi.h> /* used by all entry points for this driver */
/* also used by ddi_get_instance and */
/* ddi_prop_op */
#include <sys/sunddi.h> /* used by all entry points for this driver */
/* also used by ddi_create_minor_node, */
/* ddi_get_instance, and ddi_prop_op */
编写驱动程序数据结构
本节所述的所有数据结构对于每个设备驱动程序都是必需的。所有驱动程序都必须定义一个
dev_ops(9S)
设备操作结构。由于dev_ops(9S) 结构包含了一个指向 cb_ops(9S)
字符和块操作结构的指针,因此必须先定义 cb_ops(9S) 结构。可加载的驱动程序的 modldrv(9S)
连接结构包含一个指向 dev_ops(9S) 结构的指针。modlinkage(9S)
模块连接结构包含一个指向 modldrv(9S) 结构的指针。
除可加载模块配置入口点外,所有必需的设备入口点都是在字符和块操作结构或设备操作结构中初始化的。某些可选的入口点和其他相关数据也在这些数据结构中初始化。在这些数据结构中初始化入口点使驱动程序可以被动态加载。
可加载模块配置入口点不在驱动程序数据结构中初始化。_init(9E)、_info(9E) 和 _fini(9E) 入口点对于所有内核模块都是必需的,且都不是特定于设备驱动程序模块的。
本节中添加了下列代码:
/* cb_ops structure */
static struct cb_ops dummy_cb_ops = {
dummy_open,
dummy_close,
nodev, /* no strategy - nodev returns ENXIO */
nodev, /* no print */
nodev, /* no dump */
dummy_read,
dummy_write,
nodev, /* no ioctl */
nodev, /* no devmap */
nodev, /* no mmap */
nodev, /* no segmap */
nochpoll, /* returns ENXIO for non-pollable devices */
dummy_prop_op,
NULL, /* streamtab struct; if not NULL, all above */
/* fields are ignored */
D_NEW | D_MP, /* compatibility flags: see conf.h */
CB_REV, /* cb_ops revision number */
nodev, /* no aread */
nodev /* no awrite */
};
/* dev_ops structure */
static struct dev_ops dummy_dev_ops = {
DEVO_REV,
0, /* reference count */
dummy_getinfo,
nulldev, /* no identify - nulldev returns 0 */
nulldev, /* no probe */
dummy_attach,
dummy_detach,
nodev, /* no reset - nodev returns ENXIO */
&dummy_cb_ops,
(struct bus_ops *)NULL,
nodev /* no power */
};
/* modldrv structure */
static struct modldrv md = {
&mod_driverops, /* Type of module. This is a driver. */
"dummy driver", /* Name of the module. */
&dummy_dev_ops
};
/* modlinkage structure */
static struct modlinkage ml = {
MODREV_1,
&md,
NULL
};
/* dev_info structure */
dev_info_t *dummy_dip; /* keep track of one instance */
定义字符和块操作结构
cb_ops(9S)
结构可对标准的字符和块接口进行初始化。参阅 cb_ops(9S) 手册页,可了解各个元素的定义及其值。该 dummy
驱动程序没有使用 cb_ops(9S) 结构中的所有元素。请参阅代码示例后面的描述。
在命名该结构时,使用的 dummy_ 前缀要与用于自动配置例程名称和用户上下文例程名称的前缀相同。预先声明
static 类型修饰符。
下列代码是应当输入 dummy.c 文件的 cb_ops(9S) 结构:
static struct cb_ops dummy_cb_ops = { dummy_open, dummy_close, nodev, /* no strategy - nodev returns ENXIO */ nodev, /* no print */ nodev, /* no dump */ dummy_read, dummy_write, nodev, /* no ioctl */ nodev, /* no devmap */ nodev, /* no mmap */ nodev, /* no segmap */ nochpoll, /* returns ENXIO for non-pollable devices */ dummy_prop_op, NULL, /* streamtab struct; if not NULL, all above */ /* fields are ignored */ D_NEW | D_MP, /* compatibility flags: see conf.h */ CB_REV, /* cb_ops revision number */ nodev, /* no aread */ nodev /* no awrite */ };
输入本驱动程序的 open(9E) 和close(9E) 入口点名称,作为该结构前两个元素的值。输入该驱动程序的 read(9E)
和 write(9E) 入口点名称,作为该结构第六和第七个元素的值。输入该驱动程序的 prop_op(9E) 入口点名称,作为该结构第十三个元素的值。
strategy(9E)、print(9E) 和 dump(9E)
例程仅适用于块驱动程序。该 dummy 驱动程序没有定义这 3 个例程,因为本驱动程序是一个字符驱动程序。本驱动程序未定义
ioctl(9E) 入口点,因为本驱动程序不使用 I/O 控制命令。本驱动程序没有定义 devmap(9E)、mmap(9E)
或 segmap(9E) 入口点,因为本驱动程序不支持内存映射。本驱动程序未定义一个 read(9E) 或 awrite(9E)
入口点,因为本驱动程序不进行任何异步读写。将这些未使用的函数元素都初始化为 nodev(9F)。nodev(9F)
函数返回 ENXIO 错误代码。
为 cb_ops(9S) 结构的 chpoll(9E) 元素指定 nochpoll(9F)
函数,因为本驱动程序不是针对可查询设备的。将 streamtab(9S) STREAMS 实体声明结构指定为 NULL,因为本驱动程序不是一个
STREAMS 驱动程序。
兼容性标记是在 conf.h 头文件中定义的。D_NEW 标记意指本驱动程序是一个新型驱动程序。D_MP
标记意思是本驱动程序可安全地支持多线程执行。所有驱动程序都必须具有多线程安全性,且都必须指定该 D_MP
标记。D_64BIT 标记意思是说本驱动程序支持 64 位 偏移和块编号。请参阅 conf.h
头文件 ,了解更多的兼容性标记。
cb_ops(9S) 结构的 CB_REV 元素是 cb_ops(9S) 修订号。CB_REV
是在 devops.h 头文件中定义的。
定义设备操作结构
dev_ops(9S)
结构可对用于诸如连接和断开本驱动程序等操作的接口进行初始化。请参阅 dev_ops(9S) 手册页,了解各个元素的定义及其值。该
dummy 驱动程序没有使用 dev_ops(9S) 结构中的任何元素。请参阅代码示例后面的描述。
在命名该结构时,所使用的 dummy_ 前缀要与用于自动配置例程名称和用户上下文例程名称的前缀相同。预先声明
static 类型修饰符。
下列代码是应当输入 dummy.c 文件的 dev_ops(9S) 结构:
static struct dev_ops dummy_dev_ops = { DEVO_REV, 0, /* reference count */ dummy_getinfo, nulldev, /* no identify - nulldev returns 0 */ nulldev, /* no probe */ dummy_attach, dummy_detach, nodev, /* no reset - nodev returns ENXIO */ &dummy_cb_ops, (struct bus_ops *)NULL, nodev /* no power */
};
dev_ops(9S) 结构的DEVO_REV 元素是本驱动程序构建版本。DEVO_REV
在 devops.h 头文件中定义。该结构中的第二个元素是驱动程序的引用计数。将该值初始化为 0。驱动程序引用计数是指当前处于打开状态的驱动程序实例的数量。如果本驱动程序的任何一个实例仍处于打开状态,则不能卸载本驱动程序。
dev_ops(9S) 结构的后 6 个元素分别是这一特定驱动程序的
getinfo(9E)、identify(9E)、probe(9E)、attach(9E)、detach(9E) 和
reset() 函数的名称。identify(9E) 函数已经废弃。将该结构元素初始化为 nulldev(9F)。probe(9E)
函数用于确定对应的设备是否存在和有效。该 dummy 驱动程序没有定义 probe(9E) 函数。将该结构元素初始化为
nulldev。nulldev(9F) 函数返回成功。reset()
函数已经废弃。将 reset() 函数初始化为
nodev(9F)。
dev_ops(9S) 结构的下一个元素是指向本驱动程序的 cb_ops(9S) 结构的一个指针。在定义字符和块操作结构中已经对本驱动程序的 cb_ops(9S) 结构进行了初始化。输入
&dummy_cb_ops 作为指向该 cb_ops(9S) 结构的这个指针的值。
dev_ops(9S) 结构的下一个元素是指向总线操作结构的一个指针。只有节点驱动程序才有总线操作结构。该 dummy
驱动程序不是一个节点驱动程序。将该值设定为 NULL,因为本驱动程序是一个叶子驱动程序。
dev_ops(9S) 结构的最后一个元素是本驱动程序的 power(9E) 例程的名称。power(9E) 例程在硬件设备上运行。本驱动程序不驱动硬件设备。将该结构元素的值设为
nodev。
定义模块连接结构
其他两个模块加载结构对于每个驱动程序都是必需的。_init(9E)、_info(9E)
和 _fini(9E) 例程使用 modlinkage(9S)
模块连接结构 从模块安装、删除和检索信息。可加载的驱动程序的 modldrv(9S)
连接结构将特定于驱动程序的信息导出到内核。请参阅各结构的手册页,了解各元素的定义及其值。
下列代码定义了本章中所示驱动程序的 modldrv(9S) 和 modlinkage(9S) 结构:
static struct modldrv md = {
&mod_driverops, /* Type of module. This is a driver. */ "dummy driver", /* Name of the module. */ &dummy_dev_ops
};
static struct modlinkage ml = {
MODREV_1,
&md,
NULL
};
modldrv(9S) 结构中的第一个元素是指向这样一个结构的指针,即该结构可以告诉内核模块是何种类型。将该值设定为
mod_driverops 结构的地址。mod_driverops 结构告诉内核
dummy.c 模块是一个可加载的驱动程序模块。mod_driverops 结构在
modctl.h 头文件中声明。dummy.c 文件中已经包含了 modctl.h
头文件,因此不要在 dummy.c 中声明 mod_driverops 结构。mod_driverops
结构在 modctl.c 源文件定义。
modldrv(9S) 结构中的第二个元素是一个描述该模块的字符串。该字符串通常包含了该模块名和该模块的版本号。modldrv(9S)
结构的最后一个元素是指向本驱动程序的 dev_ops(9S) 结构的一个指针。在定义设备操作结构中已经初始化了本驱动程序的 dev_ops(9S) 结构。
modlinkage(9S) 结构的第一个元素是可加载模块系统的修订号。将该值设定为 MODREV_1。modlinkage(9S)
结构的下一个元素是指向连接结构的空结束指针数组的地址。驱动程序模块只有一个连接结构。输入 md 结构的地址,作为
modlinkage(9S) 结构的这一元素的值。输入值 NULL 可以终止该连接结构列表。
包含数据结构头文件
cb_ops(9S)
和 dev_ops(9S)
结构都要求包含 conf.h 和devops.h 头文件。modlinkage(9S)
和 modldrv(9S)
结构要求包含 modctl.h 头文件。您已经包含了可加载模块配置入口点的 modctl.h
头文件。
下列代码是应该包含在 dummy.c 文件中的完整的头文件列表:
#include <sys/devops.h> /* used by dev_ops */
#include <sys/conf.h> /* used by dev_ops 和cb_ops */
#include <sys/modctl.h> /* used by modlinkage, modldrv, _init, _info, */
/* and _fini */ #include <sys/types.h> /* used by open, close, read, write, prop_op, */ /* and ddi_prop_op */ #include <sys/file.h> /* used by open, close */ #include <sys/errno.h> /* used by open, close, read, write */ #include <sys/open.h> /* used by open, close, read, write */ #include <sys/cred.h> /* used by open, close, read */ #include <sys/uio.h> /* used by read */ #include <sys/stat.h> /* defines S_IFCHR used by ddi_create_minor_node */ #include <sys/cmn_err.h> /* used by all entry points for this driver */ #include <sys/ddi.h> /* used by all entry points for this driver */ /* also used by cb_ops, ddi_get_instance, and */ /* ddi_prop_op */ #include <sys/sunddi.h> /* used by all entry points for this driver */ /* also used by cb_ops, ddi_create_minor_node, */ /* ddi_get_instance, and ddi_prop_op */
|