第 1 部分针对 Oracle Solaris 平台设计设备驱动程序
9. 直接内存访问 (Direct Memory Access, DMA)
14. 分层驱动程序接口 (Layered Driver Interface, LDI)
以下各节提供了有关转换驱动程序以在 64 位环境中运行的信息。驱动程序编写人员可能需要执行以下一项或多项任务:
使用硬件寄存器的固定宽度类型。
使用固定宽度的公共访问函数。
检查并扩展派生类型的用法。
检查 DDI 数据结构中更改的字段。
检查 DDI 函数中更改的参数。
根据需要修改用于处理用户数据的驱动程序入口点。
检查 x86 平台上使用 64 位 long 类型的结构。
下面详细说明这些步骤。
完成每一步骤后,请修复所有编译器警告,然后使用 lint 查找其他问题。对于 SC5.0(或更高)版本的 lint,要想找出 64 位问题,在使用该命令时必须指定 -Xarch=v9 和 -errchk=longptr64 选项。
注 - 请勿忽略 LP64 转换期间出现的编译警告。以前在 ILP32 环境中可安全忽略的警告现在可能表示比较严重的问题。
完成所有步骤后,同时将驱动程序作为 32 位和 64 位模块进行编译和测试。
许多处理硬件设备的设备驱动程序使用 C 数据结构说明硬件的布局。在 LP64 数据模型中,使用 long 或 unsigned long 类型定义硬件寄存器的数据结构几乎肯定不正确,因为 long 类型现在是 64 位。首先包括 <sys/inttypes.h>,然后将此类数据结构更新为使用 int32_t 或 uint32_t,而不是 32 位设备数据的 long。 此方法可保留 32 位数据结构的二进制布局。例如,将以下代码:
struct device_regs { ulong_t addr; uint_t count; }; /* Only works for ILP32 compilation */
更改为:
struct device_regs { uint32_t addr; uint32_t count; }; /* Works for any data model */
Oracle Solaris DDI 允许通过访问函数访问设备寄存器,以便可在多个平台间移植。DDI 公共访问函数以前以字节和字等单位指定数据大小。例如,ddi_getl(9F) 用于访问 32 位。此函数不存在于 64 位 DDI 环境中,且已被替换为可指定要处理的位数的函数版本。
这些例程已添加到 Solaris 2.6 操作环境的 32 位内核中,以允许驱动程序编写人员采用其早期版本。例如,要移植到 32 位和 64 位内核中,驱动程序必须使用 ddi_get32(9F) 而不是 ddi_getl(9F) 来访问 32 位数据。
所有公共访问例程都被替换为其固定宽度的对等例程。有关详细信息,请参见 ddi_get8(9F)、ddi_put8(9F)、ddi_rep_get8(9F) 和 ddi_rep_put8(9F) 手册页。
应尽可能使用系统派生的类型(如 size_t),以使产生的变量在各种函数间传递时都有效。而新的派生类型 uintptr_t 或 intptr_t 是整数类型,应该用于指针。
固定宽度的整数类型用于表示二进制数据结构或硬件寄存器的显式大小,而基础 C 语言数据类型(如 int)仍然可用于循环计数器或文件描述符。
一些系统派生类型在 32 位系统上表示 32 位,但是在 64 位系统上表示 64 位。以此方式更改大小的派生类型包括:clock_t、daddr_t、dev_t、ino_t、intptr_t、off_t、size_t、ssize_t、time_t、uintptr_t 和 timeout_id_t。
设计使用这些派生类型的驱动程序时,请特别注意这些类型的用法,尤其是驱动程序将这些值指定给其他类型(如固定宽度类型)的变量时。
DDI 数据结构中某些字段的数据类型(如 buf(9S))已被更改。使用这些数据结构的驱动程序应确保正确使用这些字段。下面列出了变动很大的数据结构及字段。
以下列出的字段与传输大小(现在可超过 4 GB)有关。
size_t b_bcount; /* was type unsigned int */ size_t b_resid; /* was type unsigned int */ size_t b_bufsize; /* was type long */
ddi_dma_attr(9S) 结构定义 DMA 引擎和设备的属性。因为这些属性指定寄存器大小,所以使用了固定宽度的数据类型而不是基本类型。
uint32_t dmac_address; /* was type unsigned long */ size_t dmac_size; /* was type u_int */
ddi_dma_cookie(9S) 结构包含 32 位 DMA 地址,因此使用了固定宽度的数据类型来定义该地址。其大小已重新定义为 size_t。
uint_t sts_rqpkt_state; /* was type u_long */ uint_t sts_rqpkt_statistics; /* was type u_long */
uint_t pkt_flags; /* was type u_long */ int pkt_time; /* was type long */ ssize_t pkt_resid; /* was type long */ uint_t pkt_state; /* was type u_long */ uint_t pkt_statistics; /* was type u_long */
由于 scsi_pkt(9S) 结构中的 pkt_flags、pkt_state 和 pkt_statistics 字段无需增大,因此这些字段已重新定义为 32 位整数。数据传输大小 pkt_resid 字段需要增大,已重新定义为 ssize_t。
本节介绍已更改的 DDI 函数参数数据类型。
struct buf *getrbuf(int sleepflag);
在以前的发行版中,sleepflag 被定义为 long 类型。
int drv_getparm(unsigned int parm, void *value_p);
在以前的发行版中,value_p 被定义为 unsigned long 类型。在 64 位内核中,drv_getparm(9F) 可提取 32 位和 64 位。此接口未定义这些量的数据类型,可能会发生简单的编程错误。
以下新例程提供更安全的替代方法:
clock_t ddi_get_lbolt(void); time_t ddi_get_time(void); cred_t *ddi_get_cred(void); pid_t ddi_get_pid(void);
强烈要求驱动程序编写人员使用这些例程而不要使用 drv_getparm(9F)。
void delay(clock_t ticks); timeout_id_t timeout(void (*func)(void *), void *arg, clock_t ticks);
delay(9F) 和 timeout(9F) 例程的 ticks 参数已从 long更改为 clock_t。
struct map *rmallocmap(size_t mapsize); struct map *rmallocmap_wait(size_t mapsize);
rmallocmap(9F) 和 rmallocmap_wait(9F) 例程的 mapsize 参数已从 ulong_t 更改为 size_t。
struct buf *scsi_alloc_consistent_buf(struct scsi_address *ap, struct buf *bp, size_t datalen, uint_t bflags, int (*callback )(caddr_t), caddr_t arg);
在以前的发行版中,datalen 被定义为 int 类型,bflags 被定义为 ulong 类型。
int uiomove(caddr_t address, size_t nbytes, enum uio_rw rwflag, uio_t *uio_p);
nbytes 参数被定义为 long 类型,但是由于 nbytes 以字节为单位表示大小,因此 size_t 更适合。
int cv_timedwait(kcondvar_t *cvp, kmutex_t *mp, clock_t timeout); int cv_timedwait_sig(kcondvar_t *cvp, kmutex_t *mp, clock_t timeout);
在以前的发行版中,cv_timedwait(9F) 和 cv_timedwait_sig(9F) 例程的 timeout 参数被定义为 long 类型。由于这些例程表示时间周期,因此 clock_t 更适合。
int ddi_device_copy(ddi_acc_handle_t src_handle, caddr_t src_addr, ssize_t src_advcnt, ddi_acc_handle_t dest_handle, caddr_t dest_addr, ssize_t dest_advcnt, size_t bytecount, uint_t dev_datasz);
src_advcnt、dest_advcnt、dev_datasz 参数的类型已更改。这些参数以前分别被定义为 long、long 和 ulong_t 类型。
int ddi_device_zero(ddi_acc_handle_t handle, caddr_t dev_addr, size_t bytecount, ssize_t dev_advcnt, uint_t dev_datasz):
在以前的发行版中,dev_advcnt 被定义为 long 类型,dev_datasz 被定义为 ulong_t 类型。
int ddi_dma_mem_alloc(ddi_dma_handle_t handle, size_t length, ddi_device_acc_attr_t *accattrp, uint_t flags, int (*waitfp)(caddr_t), caddr_t arg, caddr_t *kaddrp, size_t *real_length, ddi_acc_handle_t *handlep);
在以前的发行版中,length、flags 和 real_length 分别被定义为 uint_t、ulong_t 和 uint_t * 类型。
如果设备驱动程序使用 ioctl(9E)、devmap(9E) 或 mmap(9E) 与 32 位应用程序共享包含 long 或指针类型的数据结构,且驱动程序已针对 64 位内核进行了重新编译,则数据结构的二进制布局将不兼容。如果当前已按 long 类型定义了字段,并且未使用 64 位数据项,请更改数据结构,以使用仍为 32 位的数据类型(int 和 unsigned int)。否则,驱动程序需要识别 ILP32 和 LP64 的不同结构形式,并确定应用程序与内核之间是否出现模型不匹配。
要处理潜在的数据模型差异,需要写入可直接与用户应用程序交互的 ioctl()、devmap() 和 mmap() 驱动程序入口点,以确定参数是否来自与内核使用相同的数据模型的应用程序。
要确定应用程序与驱动程序之间是否存在模型不匹配,驱动程序可使用 FMODELS 掩码确定 ioctl()mode 参数的模型类型。在 mode 中采用以下值之一来标识应用程序的数据模型:
对有 64 位处理能力的设备驱动程序的 I/O 控制支持中的代码示例说明如何使用 ddi_model_convert_from(9F) 处理此情况。
要使 64 位驱动程序和 32 位应用程序共享内存,64 位驱动程序生成的二进制布局必须与 32 位应用程序使用的布局相同。要导出到应用程序的映射内存可能需要包含与数据模型有关的数据结构。
很少内存映射设备会面临此问题,因为在内核数据模型发生变化时设备寄存器不会改变大小。但是,一些将映射导出到用户地址空间的伪设备可能要将不同数据结构导出到 ILP32 或 LP64 应用程序。要确定是否出现了数据模型不匹配,devmap(9E) 可使用 model 参数说明应用程序期望的数据模型。将 model 参数设置为以下值之一:
可将未经转换的模型参数传递到 ddi_model_convert_from(9F) 例程或 STRUCT_INIT()。请参见32 位和 64 位数据结构宏。
由于 mmap(9E) 没有可用于传递数据模型信息的参数,因此可编写驱动程序的 mmap(9E) 入口点,以使用新的 DDI 函数 ddi_model_convert_from(9F)。此函数返回以下值之一,以指示应用程序的数据类型模型:
与 ioctl() 和 devmap() 一样,可将模型位传递到 ddi_model_convert_from(9F) 以确定是否需要进行数据转换,或可将模型传递到 STRUCT_INIT()。
或者,迁移设备驱动程序以支持 devmap(9E) 入口点。
您应认真检查 x86 平台上使用 64 位 long 类型(如 uint64_t)的结构。32 位模式编译与 64 位模式编译的对齐方式和大小可能不同。请参考以下示例。
#include <studio> #include <sys> struct myTestStructure { uint32_t my1stInteger; uint64_t my2ndInteger; }; main() { struct myTestStructure a; printf("sizeof myTestStructure is: %d\n", sizeof(a)); printf("offset to my2ndInteger is: %d\n", (uintptr_t)&a.bar - (uintptr_t)&a); }
在 32 位系统中,该示例显示以下结果:
sizeof myTestStructure is: 12 offset to my2ndInteger is: 4
而在 64 位系统中,该示例显示以下结果:
sizeof myTestStructure is: 16 offset to my2ndInteger is: 8
因此,32 位应用程序与 64 位应用程序对结构的理解不同。这样,尝试在 32 位和 64 位两种环境中使用同一结构可能会导致问题。这种情况经常发生,尤其是在通过 ioctl() 调用将结构传入或传出内核的情况下。