第 1 部分针对 Oracle Solaris 平台设计设备驱动程序
9. 直接内存访问 (Direct Memory Access, DMA)
14. 分层驱动程序接口 (Layered Driver Interface, LDI)
GLDv3 框架是 MAC 插件和 MAC 驱动程序服务例程与结构的基于函数调用的接口。GLDv3 框架代表符合 GLDv3 的驱动程序实现必要的 STREAMS 入口点,并处理 DLPI 兼容性。
本节讨论以下主题:
GLDv3 为使用 MAC_PLUGIN_IDENT_ETHER 插件类型注册的驱动程序定义驱动程序 API。
GLDv3 设备驱动程序必须执行以下步骤来向 MAC 层注册:
包含以下三个 MAC 头文件:sys/mac.h、sys/mac_ether.h 和 sys/mac_provider.h。切勿在驱动程序中包含其他任何与 MAC 相关的头文件。
填充 mac_callbacks 结构。
在其 _init() 入口点中调用 mac_fini_ops() 函数。
在其 attach() 入口点中调用 mac_alloc() 函数,以分配 mac_register 结构。
填充 mac_register 结构,并在其 attach() 入口点中调用 mac_register() 函数。
在其 detach() 入口点中调用 mac_unregister() 函数。
在其 _fini() 入口点中调用 mac_fini_ops() 函数。
链接 misc/mac 依赖性:
# ld -N"misc/mac" xx.o -o xx
GLDv3 接口包括在使用 MAC 层注册过程中通告的驱动程序入口点和驱动程序调用的 MAC 入口点。
void mac_init_ops(struct dev_ops *ops, const char *name);
GLDv3 设备驱动程序必须在调用 mod_install(9F) 之前在其 _init(9E) 入口点中调用 mac_init_ops(9F) 函数。
void mac_fini_ops(struct dev_ops *ops);
GLDv3 设备驱动程序必须在调用 mod_remove(9F) 之后在其 _fini(9E) 入口点中调用 mac_fini_ops(9F) 函数。
示例 19-1 mac_init_ops() 和 mac_fini_ops() 函数
int _init(void) { int rv; mac_init_ops(&xx_devops, "xx"); if ((rv = mod_install(&xx_modlinkage)) != DDI_SUCCESS) { mac_fini_ops(&xx_devops); } return (rv); } int _fini(void) { int rv; if ((rv = mod_remove(&xx_modlinkage)) == DDI_SUCCESS) { mac_fini_ops(&xx_devops); } return (rv); }
mac_register_t *mac_alloc(uint_t version);
mac_alloc(9F) 函数分配一个新的 mac_register 结构,并返回此结构的指针。在将新结构传递给 mac_register() 之前初始化结构成员。在 mac_alloc() 返回之前,MAC 层会初始化 MAC 专用元素。version 的值必须是 MAC_VERSION_V1。
void mac_free(mac_register_t *mregp);
mac_free(9F) 函数释放此前由 mac_alloc() 分配的 mac_register 结构。
int mac_register(mac_register_t *mregp, mac_handle_t *mhp);
为了使用 MAC 层注册新实例,GLDv3 驱动程序必须在 attach(9E) 入口点中调用 mac_register(9F) 函数。mregp 参数是指向 mac_register 注册信息结构的指针。在成功时,mhp 参数是指向新 MAC 实例的 MAC 句柄指针。mac_tx_update()、mac_link_update () 和 mac_rx() 等其他例程需要此句柄。
示例 19-2 mac_alloc()、mac_register () 和 mac_free() 函数及 mac_register 结构
int xx_attach(dev_info_t *dip, ddi_attach_cmd_t cmd) { mac_register_t *macp; /* ... */ if ((macp = mac_alloc(MAC_VERSION)) == NULL) { xx_error(dip, "mac_alloc failed"); goto failed; } macp->m_type_ident = MAC_PLUGIN_IDENT_ETHER; macp->m_driver = xxp; macp->m_dip = dip; macp->m_src_addr = xxp->xx_curraddr; macp->m_callbacks = &xx_m_callbacks; macp->m_min_sdu = 0; macp->m_max_sdu = ETHERMTU; macp->m_margin = VLAN_TAGSZ; if (mac_register(macp, &xxp->xx_mh) == DDI_SUCCESS) { mac_free(macp); return (DDI_SUCCESS); } /* failed to register with MAC */ mac_free(macp); failed: /* ... */ }
int mac_unregister(mac_handle_t mh);
mac_unregister(9F) 函数取消注册此前通过 mac_register() 注册的 MAC 实例。mh 参数是 mac_register() 分配的 MAC 句柄。从 detach(9E) 入口点调用 mac_unregister()。
示例 19-3 mac_unregister() 函数
int xx_detach(dev_info_t *dip, ddi_detach_cmd_t cmd) { xx_t *xxp; /* driver soft state */ /* ... */ switch (cmd) { case DDI_DETACH: if (mac_unregister(xxp->xx_mh) != 0) { return (DDI_FAILURE); } /* ... */ }
本节介绍的结构是在 sys/mac_provider.h 头文件中定义的。在 GLDv3 驱动程序中包含以下三个 MAC 头文件:sys/mac.h、sys/mac_ether.h 和 sys/mac_provider.h。切勿包含其他任何于 MAC 相关的头文件。
mac_register(9S) 数据结构是 MAC 注册信息结构,由 mac_alloc() 分配,传递至 mac_register()。在将新结构传递给 mac_register() 之前初始化结构成员。在 mac_alloc() 返回之前,MAC 层会初始化 MAC 专用元素。m_version 结构成员是 MAC 版本。切勿修改 MAC 版本。m_type_ident 结构成员是 MAC 类型标识符。将 MAC 类型标识符设置为 MAC_PLUGIN_IDENT_ETHER。mac_register 结构的 m_callbacks 成员是指向 mac_callbacks 结构的一个实例的指针。
mac_callbacks(9S) 数据结构是设备驱动程序用于向 MAC 层公开其入口点的结构。MAC 层使用这些入口点来控制驱动程序。这些入口点用于完成启动和停止适配器、管理多播地址、设置混杂模式、查询适配器功能、获取和设置属性等任务。有关必需和可选 GLDv3 入口点的完整列表,请参见表 19-1。在 mac_register 结构的 m_callbacks 字段中提供指向 mac_callbacks 结构的指针。
mac_callbacks 结构的 mc_callbacks 成员是一个位掩码,结合了指定驱动程序将实现哪些可选入口点的以下标志。mac_callbacks 结构的其他成员是驱动程序各入口点的指针。
显示 mc_ioctl() 入口点。
显示 mc_getcapab() 入口点。
显示 mc_setprop() 入口点。
显示 mc_getprop() 入口点。
显示 mc_propinfo() 入口点。
显示所有属性入口点。设置 MC_PROPERTIES 等同于设置全部三个标志:MC_SETPROP、 MC_GETPROP 和 MC_PROPINFO。
示例 19-4 mac_callbacks 结构
#define XX_M_CALLBACK_FLAGS \ (MC_IOCTL | MC_GETCAPAB | MC_PROPERTIES) static mac_callbacks_t xx_m_callbacks = { XX_M_CALLBACK_FLAGS, xx_m_getstat, /* mc_getstat() */ xx_m_start, /* mc_start() */ xx_m_stop, /* mc_stop() */ xx_m_promisc, /* mc_setpromisc() */ xx_m_multicst, /* mc_multicst() */ xx_m_unicst, /* mc_unicst() */ xx_m_tx, /* mc_tx() */ NULL, /* Reserved, do not use */ xx_m_ioctl, /* mc_ioctl() */ xx_m_getcapab, /* mc_getcapab() */ NULL, /* Reserved, do not use */ NULL, /* Reserved, do not use */ xx_m_setprop, /* mc_setprop() */ xx_m_getprop, /* mc_getprop() */ xx_m_propinfo /* mc_propinfo() */ };
GLDv3 实现一种功能机制,允许框架查询和启用 GLDv3 驱动程序支持的功能。请使用 mc_getcapab(9E) 入口点来报告功能。如果驱动程序支持某种功能,请通过 mc_getcapab() 传递关于该功能的信息,例如特定于功能的入口点或标志。传递一个指向 mac_callback 结构中 mc_getcapab() 入口点的指针。有关 GLDv3 MAC 注册数据结构 结构的更多信息,请参阅 GLDv3 MAC 注册数据结构。
boolean_t mc_getcapab(void *driver_handle, mac_capab_t cap, void *cap_data);
cap 参数指定所查询的功能类型。cap 的值可以是 MAC_CAPAB_HCKSUM(硬件校验和负载转移)或 MAC_CAPAB_LSO(大段负载转移)。使用 cap_data 参数将功能数据返回框架。
如果驱动程序支持 cap 功能,则 mc_getcapab() 入口点必须返回 B_TRUE。如果驱动程序不支持 cap 功能,则 mc_getcapab() 必须返回 B_FALSE。
示例 19-5 mc_getcapab() 入口点
static boolean_t xx_m_getcapab(void *arg, mac_capab_t cap, void *cap_data) { switch (cap) { case MAC_CAPAB_HCKSUM: { uint32_t *txflags = cap_data; *txflags = HCKSUM_INET_FULL_V4 | HCKSUM_IPHDRCKSUM; break; } case MAC_CAPAB_LSO: { /* ... */ break; } default: return (B_FALSE); } return (B_TRUE); }
下面各节将介绍所支持的功能和需要返回的相应功能数据。
为了获得关于硬件校验和负载转移支持的数据,框架将在 cap 参数中发送 MAC_CAPA _HCKSUM。请参见硬件校验和负载转移功能信息。
要在启用硬件校验和的情况下查询校验和负载转移元数据以及检索每个包的硬件校验和元数据,请使用 mac_hcksum_get(9F)。请参见mac_hcksum_get()() 函数标志。
要设置校验和负载转移元数据,请使用 mac_hcksum_set(9F)。请参见mac_hcksum_set()() 函数标志。
有关更多信息,请参见硬件校验和:硬件和硬件校验和:MAC 层。
要将关于 MAC_CAPAB_HCKSUM 功能的信息传递给框架,驱动程序必须在指向 uint32_t 的 cap_data 中设置以下标志的组合。这些标志指明驱动程序能够为外发包执行的硬件校验和负载转移的级别。
1 的补码的部分校验和功能
针对 IPv4 包的 1 的补码的完全校验和能力
针对 IPv6 包的 1 的补码的完全校验和能力
IPv4 头校验和负载转移功能
mac_hcksum_get() 的 flags 参数是以下值的组合:
计算此包的完整校验和。
完整校验和已在硬件中通过验证,证实是正确的。
根据传递给 mac_hcksum_get() 的其他参数计算 1 的补码的部分校验和。HCK_PARTIALCKSUM 与 HCK_FULLCKSUM 互斥。
计算 IP 报头校验和。
IP 头校验和已在硬件中通过验证,证实是正确的。
mac_hcksum_set 的 flags() 参数是以下值的组合:
通过 value 参数计算和传递完整校验和。
完整校验和已在硬件中通过验证,证实是正确的。
通过 value 参数计算和传递部分校验和。HCK_PARTIALCKSUM 与 HCK_FULLCKSUM 互斥。
通过 value 参数计算和传递 IP 头校验和。
IP 头校验和已在硬件中通过验证,证实是正确的。
为了查询大段(或大量传送)负载转移支持,框架将在 cap 参数中发送 MAC_CAPA _LSO,并等待接收在指向 mac_capab_lso(9S) 结构的 cap_data 中返回的信息。框架将分配 mac_capab_lso 结构,并在 cap_data 中传递指向此结构的指针。mac_capab_lso 结构包含一个 lso_basic_tcp_ipv4(9S) 结构和一个 lso_flags 成员。如果驱动程序实例为 IPv4 上的 TCP 支持 LSO,请设置 lso_flags 中的 LSO_TX_BASIC_TCP_IPV4 标志,并将 lso_basic_tcp_ipv4 结构的 lso_max 成员设置为驱动程序实例支持的最大有效载荷大小。
使用 mac_lso_get(9F) 获得每个包的 LSO 元数据。如果为此包启用了 LSO,则将在 mac_lso_get () flags 参数中设置 HW_LSO 标志。在大段的分段过程中使用的最大段大小 (maximum segment size, MSS) 将通过 mss 参数指向的位置返回。有关更多信息,请参见大段负载转移。
数据路径入口点包括以下组件:
由驱动程序导出、由 GLDv3 框架调用以发送包的回调。
驱动程序为传输流量控制和接收包而调用的 GLDv3 框架入口点。
GLDv3 框架使用传输入口点 mc_tx(9E) 将消息块链传递至驱动程序。在您的 mac_callbacks 结构中提供指向 mc_tx() 入口点的指针。有关 GLDv3 MAC 注册数据结构 结构的更多信息,请参阅 GLDv3 MAC 注册数据结构。
示例 19-6 mc_tx() 入口点
mblk_t * xx_m_tx(void *arg, mblk_t *mp) { xx_t *xxp = arg; mblk_t *nmp; mutex_enter(&xxp->xx_xmtlock); if (xxp->xx_flags & XX_SUSPENDED) { while ((nmp = mp) != NULL) { xxp->xx_carrier_errors++; mp = mp->b_next; freemsg(nmp); } mutex_exit(&xxp->xx_xmtlock); return (NULL); } while (mp != NULL) { nmp = mp->b_next; mp->b_next = NULL; if (!xx_send(xxp, mp)) { mp->b_next = nmp; break; } mp = nmp; } mutex_exit(&xxp->xx_xmtlock); return (mp); }
以下各节将讨论与将数据传输至硬件相关的主题。
如果驱动程序因硬件资源不足而无法发送包,则驱动程序将返回无法发送的包的子链。此后,在更多描述符可用时,驱动程序必须调用 mac_tx_update(9F) 通知框架。
如果驱动程序指定了硬件校验和支持(请参见硬件校验和负载转移),则驱动程序必须执行以下任务:
使用 mac_hcksum_get(9F) 检查每个包的硬件校验和元数据。
对硬件进行编程,以执行所需的校验和计算。
如果驱动程序指定了 LSO 功能(请参见大段(或大量传送)负载转移),则驱动程序必须使用 mac_lso_get(9F) 来查询是否必须在包上执行 LSO。
管理员配置 VLAN 时,MAC 层将通过 mc_tx() 入口点,在外发包传递至驱动程序之前将所需的 VLAN 头添加到外发包中。
在驱动程序的中断处理程序中调用 mac_rx(9F) 函数,将一个或多个包构成的链沿堆栈向上传递至 MAC 层。避免在调用 mac_rx() 的过程中保留互斥锁或其他锁。具体来说,不要保留可能会在 mac_rx() 调用过程中被传输获取的锁。有关必须向上发送至 MAC 层的包的信息,请参见mc_unicst(9E)。
以下各节将讨论与将数据发送至 MAC 层相关的主题。
如果驱动程序指定了硬件校验和支持(请参见硬件校验和负载转移),则驱动程序必须使用 mac_hcksum_set(9F) 函数将硬件校验和元数据与包关联。
VLAN 包必须连同标记一起传递至 MAC 层。切勿剥离数据包中的 VLAN 头。
驱动程序可以调用以下函数来通知网络栈驱动程序的状态已更改。
void mac_tx_update(mac_handle_t mh);
mac_tx_update(9F) 函数通知框架有更多 TX 描述符可用。如果 mc_tx() 返回非空包链,则驱动程序必须在资源可用时立即调用 mac_tx_update(),通知 MAC 层重试之前作为未发送的包返回的包。有关 mc_tx() 入口点的更多信息,请参见传输数据路径。
void mac_link_update(mac_handle_t mh, link_state_t new_state);
mac_link_update(9F) 函数通知 MAC 层介质链接的状态已更改。new_state 参数必须为以下值之一:
介质链路为启动状态。
介质链路为关闭状态。
介质链路为未知状态。
设备驱动程序为其管理的设备实例维护一组统计信息。MAC 层通过驱动程序的 mc_getstat(9E) 入口点查询这些统计信息。
int mc_getstat(void *driver_handle, uint_t stat, uint64_t *stat_value);
GLDv3 框架使用 stat 指定所查询的统计信息。驱动程序使用 stat_value 返回 stat 指定的统计信息的值。如果返回了统计信息的值,则 mc_getstat() 必须返回 0。如果驱动程序不支持 stat 统计信息,mc_getstat() 必须返回 ENOTSUP。
所支持的 GLDv3 统计信息是通用 MAC 统计信息和特定于以太网的统计信息的联合。有关所支持的统计信息的完整列表,请参见 mc_getstat(9E) 手册页。
示例 19-7 mc_getstat() 入口点
int xx_m_getstat(void *arg, uint_t stat, uint64_t *val) { xx_t *xxp = arg; mutex_enter(&xxp->xx_xmtlock); if ((xxp->xx_flags & (XX_RUNNING|XX_SUSPENDED)) == XX_RUNNING) xx_reclaim(xxp); mutex_exit(&xxp->xx_xmtlock); switch (stat) { case MAC_STAT_MULTIRCV: *val = xxp->xx_multircv; break; /* ... */ case ETHER_STAT_MACRCV_ERRORS: *val = xxp->xx_macrcv_errors; break; /* ... */ default: return (ENOTSUP); } return (0); }
使用 mc_propinfo(9E) 入口点返回属性的不可变属性。此信息包括权限、缺省值和允许的值范围。使用 mc_setprop(9E) 为此特定驱动程序实例设置属性值。使用 mc_getprop(9E) 返回属性的当前值。
有关属性及其类型的完整列表,请参见 mc_propinfo(9E) 手册页。
mc_propinfo() 入口点应调用 mac_prop_info_set_perm()、mac_prop_info_set_default() 和 mac_prop_info_set_range() 函数来关联所查询属性的特定属性,例如缺省值、权限或允许的值范围等。
mac_prop_info_set_default_uint8(9F)、mac_prop_info_set_default_str(9F) 和 mac_prop_info_set_default_link_flowctrl(9F) 函数将缺省值与特定属性相关联。mac_prop_info_set_range_uint32(9F) 函数为特定属性关联值的允许范围。
mac_prop_info_set_perm(9F) 函数指定属性的权限。权限可以是以下值之一:
属性为只读
属性为只写
属性可以是读取和写入
如果 mc_propinfo() 入口点未为特定属性调用 mac_prop_info_set_perm(),GLDv3 框架将假设该属性拥有对应于 MAC_PROP_PERM_RW 的读写权限。
除了 mc_propinfo(9E) 手册页中列出的属性之外,驱动程序还可公开驱动程序专用属性。使用 mac_register 结构的 m_priv_props 字段指定驱动程序支持的驱动程序专用属性。框架在 mc_setprop()、 mc_getprop() 或 mc_propinfo() 中传递 MAC_PROP_PRIVATE 属性 ID。有关更多信息,请参见 mc_propinfo (9E) 手册页。
下表列出了属于 GLDv3 网络设备驱动程序框架的入口点、其他 DDI 函数和数据结构。
表 19-1 GLDv3 接口
|