第5 章• 事件79
示例5–1 调用ddi_log_sysevent() (续)
nvlist_t *nvl;
...
nvlist_alloc(&nvl, nvflag, kmflag);
...
(void) nvlist_add_byte_array(nvl, propname, (uchar_t *)propval, proplen + 1);
...
if (ddi_log_sysevent(dip, vendor_name, my_class,
my_subclass, nvl, NULL, DDI_SLEEP)!= DDI_SUCCESS)
cmn_err(CE_WARN, "error logging system event");
nvlist_free(nvl);
定义事件特性
事件特性定义为名称-值对列表。Solaris DDI 提供了用于在名称-值对中存储信息的例程和结
构。名称-值对保留在nvlist_t 结构中,此结构对于驱动程序是不透明的。名称-值对的值
可以是布尔值、int、字节、字符串、nvlist 或这些数据类型的数组。int 可以定义为16
位、32 位或64 位,可以带符号,也可不带符号。
下面是创建名称-值对列表的步骤。
1. 使用nvlist_alloc(9F) 创建nvlist_t 结构。
nvlist_alloc() 接口会采用以下三个参数:
nvlp-指向nvlist_t 结构指针的指针
nvflag-指示名称-值对的名称唯一性的标志。如果此标志设置为
NV_UNIQUE_NAME_TYPE,则会从列表中删除与新对的名称和类型相匹配的任何现有
对。如果标志设置为NV_UNIQUE_NAME,则会删除任何同名的现有对,而不考虑对的
类型。只要对的类型不同,通过指定NV_UNIQUE_NAME_TYPE,列表即可包含两个或多
个同名的对,但如果指定NV_UNIQUE_NAME,则列表中只能有一个对名称实例。如果
未设置标志,则不会执行任何唯一性检查,将由列表的消费方负责处理同名的对。
kmflag-指示内核内存分配策略的标志。如果此参数设置为KM_SLEEP,则驱动程序会
阻塞,直到请求的内存可进行分配为止。KM_SLEEP 分配可能会休眠,但是保证会成
功。KM_NOSLEEP 分配保证不会休眠,但是可能会在当前无可用内存时返回NULL。
定义事件特性
80 编写设备驱动程序• 2006 年11 月
2. 使用名称-值对填充nvlist。例如,要添加字符串,请使用nvlist_add_string(9F)。要
添加32 位整数数组,请使用nvlist_add_int32_array(9F)。nvlist_add_boolean(9F) 手
册页包含用于添加对的可用接口的完整列表。
要取消分配列表,请使用nvlist_free(9F)。
以下代码样例说明如何创建名称-值对列表。
示例5–2创建和填充名称-值对列表
nvlist_t*
create_nvlist()
{
int err;
char *str = "child";
int32_t ints[] = {0, 1, 2};
nvlist_t *nvl;
err = nvlist_alloc(&nvl, NV_UNIQUE_NAME, 0); /* allocate list */
if (err)
return (NULL);
if ((nvlist_add_string(nvl, "name", str) != 0) ||
(nvlist_add_int32_array(nvl, "prop", ints, 3) != 0)) {
nvlist_free(nvl);
return (NULL);
}
return (nvl);
}
定义事件特性
第5 章• 事件81
驱动程序可通过相应类型的查找函数(如nvlist_lookup_int32_array(9F))来检索nvlist
中的元素,此类查找函数将要搜索的名称-值对的名称作为参数。
注– 仅当在调用nvlist_alloc(9F) 的情况下指定了NV_UNIQUE_NAME 或NV_UNIQUE_NAME_TYPE
时,这些接口才会正常工作。否则,将返回ENOTSUP,因为此列表不能包含多个同名的
对。
可以将名称-值列表中的各对放在连续内存中。此方法有助于将列表传递给已订阅了通知的
实体。第一步是使用nvlist_size(9F) 获取列表所需的内存块的大小。第二步是使用
nvlist_pack(9F) 将列表压缩到缓冲区中。收到缓冲区内容的消费方可使用
nvlist_unpack(9F) 解压缩缓冲区。
用户级开发者和内核级开发者均可使用用于处理名称-值对的函数。可以在《man pages
section 3: Library Interfaces and Headers》和《man pages section 9: DDI and DKI Kernel
Functions》中找到这些函数的相同手册页。有关针对名称-值对执行操作的函数的列表,请
参见下表。
表5–1使用名称-值对的函数
手册页用途/函数
nvlist_add_boolean(9F) 向列表中添加名称-值对。函数包括:
nvlist_add_boolean()、nvlist_add_boolean_value()、nvlist_add_byte()、
nvlist_add_int8()、nvlist_add_uint8()、nvlist_add_int16()、
nvlist_add_uint16()、nvlist_add_int32()、nvlist_add_uint32()、
nvlist_add_int64()、nvlist_add_uint64()、nvlist_add_string()、
nvlist_add_nvlist()、nvlist_add_nvpair()、nvlist_add_boolean_array()、
nvlist_add_int8_array, nvlist_add_uint8_array()、
nvlist_add_nvlist_array()、nvlist_add_byte_array()、
nvlist_add_int16_array()、nvlist_add_uint16_array()、
nvlist_add_int32_array()、nvlist_add_uint32_array()、
nvlist_add_int64_array()、nvlist_add_uint64_array()、
nvlist_add_string_array()
nvlist_alloc(9F) 处理名称-值列表缓冲区。函数包括:
nvlist_alloc()、nvlist_free()、nvlist_size()、nvlist_pack()、
nvlist_unpack()、nvlist_dup()、nvlist_merge()
定义事件特性
82 编写设备驱动程序• 2006 年11 月
表5–1 使用名称-值对的函数(续)
手册页用途/函数
nvlist_lookup_boolean(9F) 搜索名称-值对。函数包括:
nvlist_lookup_boolean()、nvlist_lookup_boolean_value()、
nvlist_lookup_byte()、nvlist_lookup_int8()、nvlist_lookup_int16()、
nvlist_lookup_int32()、nvlist_lookup_int64()、nvlist_lookup_uint8()、
nvlist_lookup_uint16()、nvlist_lookup_uint32()、nvlist_lookup_uint64()、
nvlist_lookup_string()、nvlist_lookup_nvlist()、
nvlist_lookup_boolean_array, nvlist_lookup_byte_array()、
nvlist_lookup_int8_array()、nvlist_lookup_int16_array()、
nvlist_lookup_int32_array()、nvlist_lookup_int64_array()、
nvlist_lookup_uint8_array()、nvlist_lookup_uint16_array()、
nvlist_lookup_uint32_array()、nvlist_lookup_uint64_array()、
nvlist_lookup_string_array()、nvlist_lookup_nvlist_array()、
nvlist_lookup_pairs()
nvlist_next_nvpair(9F) 获取名称-值对数据。函数包括:
nvlist_next_nvpair()、nvpair_name()、nvpair_type()
nvlist_remove(9F) 删除名称-值对。函数包括:
nv_remove()、nv_remove_all()
定义事件特性
第5 章• 事件83
84
驱动程序自动配置
在自动配置过程中,驱动程序会将代码和静态数据装入内存中。随后在系统中注册此信
息。在自动配置过程中还会连接由驱动程序控制的各个设备实例。
本章介绍有关以下主题的信息:
第85 页中的“驱动程序的装入和卸载”
第86 页中的“驱动程序必需的数据结构”
第89 页中的“可装入驱动程序接口”
第95 页中的“设备配置概念”
第114 页中的“使用设备ID”
驱动程序的装入和卸载
系统从用于自动配置的内核模块目录的drv 子目录装入驱动程序二进制模块。请参见第494
页中的“将驱动程序复制到模块目录”。
将模块读入内存且解析了所有符号之后,系统将调用此模块的_init(9E) 入口点。_init()
函数将调用mod_install(9F),实际上就是装入此模块。
注– 调用mod_install() 期间,一旦调用了mod_install(),其他线程便可以调用
attach(9E)。从编程角度来看,在调用mod_install() 之前必须执行所有_init() 初始化。
如果mod_install() 失败(即返回非零值),则必须取消初始化。
一旦_init() 成功完成,便会在系统中正确注册驱动程序。实际上,此时驱动程序并不管理
任何设备。设备管理是在设备配置过程中进行的。
为了节省系统内存或根据用户的明确请求,系统会卸载驱动程序二进制模块。从内存中删
除驱动程序代码和数据之前,将调用该驱动程序的_fini(9E) 入口点。当且仅当_fini() 返
回成功信息时,才会卸载驱动程序。
6第6 章
85
下图概述了设备驱动程序的结构。阴影区域突出显示驱动程序的数据结构和入口点。阴影
区域的上半部分包括支持驱动程序装入和卸载的数据结构和入口点。下半部分与驱动程序
配置相关。
modldrv(9S)
dev_ops(9S)
cb_ops(9S)
_info()
_fini()
_init()
attach(9E)
detach(9E)
getinfo(9E)
probe(9E)
power(9E)
modlinkage(9S)
图6–1模块装入和自动配置入口点
驱动程序必需的数据结构
为了支持自动配置,驱动程序需要静态初始化以下数据结构:
modlinkage(9S)
modldrv(9S)
dev_ops(9S)
cb_ops(9S)
驱动程序依赖于图5-1 中的数据结构。必须提供并正确初始化这些数据结构。没有这些数据
结构,可能无法正确装入驱动程序。结果导致可能无法装入必需的例程。如果驱动程序不
支持某个操作,则nodev(9F) 例程的地址可以用作占位符。在某些情况下,驱动程序支持入
口点,并且仅需要返回成功信息或失败信息。在这种情况下,可以使用例程nulldev(9F) 的
地址。
注– 应该在编译时对这些结构进行初始化。在任何其他时间,驱动程序都不应访问或更改这
些结构。
modlinkage 结构
static struct modlinkage xxmodlinkage = {
MODREV_1, /* ml_rev */
驱动程序必需的数据结构
86 编写设备驱动程序• 2006 年11 月
&xxmodldrv, /* ml_linkage[] */
NULL /* NULL termination */
};
第一个字段是装入子系统的模块的版本号。该字段应为MODREV_1。第二个字段指向接下来
定义的驱动程序的modldrv 结构。该结构的最后一个元素应始终为NULL。
modldrv 结构
static struct modldrv xxmodldrv = {
&mod_driverops, /* drv_modops */
"generic driver v1.1", /* drv_linkinfo */
&xx_dev_ops /* drv_dev_ops */
};
该结构更加详细地描述模块。第一个字段提供有关模块安装的信息。对于驱动程序模块,
该字段应设置为&mod_driverops。第二个字段是将由modinfo(1M) 显示的字符串。第二个字
段应包含足够的信息,以便确定生成驱动程序二进制文件的源代码版本。最后一个字段指
向下节所定义的驱动程序的dev_ops 结构。
dev_ops 结构
static struct dev_ops xx_dev_ops = {
DEVO_REV, /* devo_rev, */
0, /* devo_refcnt */
xxgetinfo, /* getinfo(9E) */
nulldev, /* identify(9E) */
xxprobe, /* probe(9E) */
xxattach, /* attach(9E) */
xxdetach, /* detach(9E) */
nodev, /* devo_reset */
驱动程序必需的数据结构