JavaScript is required to for searching.
跳过导航链接
退出打印视图
编写设备驱动程序     Oracle Solaris 10 1/13 Information Library (简体中文)
search filter icon
search icon

文档信息

前言

第 1 部分针对 Oracle Solaris 平台设计设备驱动程序

1.  Oracle Solaris 设备驱动程序概述

2.  Oracle Solaris 内核和设备树

3.  多线程

4.  属性

5.  管理事件和排队任务

6.  驱动程序自动配置

7.  设备访问:程控 I/O

8.  中断处理程序

9.  直接内存访问 (Direct Memory Access, DMA)

10.  映射设备和内核内存

11.  设备上下文管理

12.  电源管理

13.  强化 Oracle Solaris 驱动程序

14.  分层驱动程序接口 (Layered Driver Interface, LDI)

第 2 部分设计特定种类的设备驱动程序

15.  字符设备驱动程序

16.  块设备驱动程序

17.  SCSI 目标驱动程序

18.  SCSI 主机总线适配器驱动程序

19.  网络设备驱动程序

20.  USB 驱动程序

21.  SR-IOV 驱动程序

第 3 部分生成设备驱动程序

22.  编译、装入、打包和测试驱动程序

23.  调试、测试和调优设备驱动程序

24.  推荐的编码方法

第 4 部分附录

A.  硬件概述

B.  Solaris DDI/DKI 服务汇总

C.  使设备驱动程序支持 64 位

64 位驱动程序设计简介

常规转换步骤

使用硬件寄存器的固定宽度类型

使用固定宽度的公共访问函数

检查并扩展派生类型的用法

检查 DDI 数据结构中更改的字段

buf 结构更改

ddi_dma_attr

ddi_dma_cookie 结构更改

csi_arq_status 结构更改

scsi_pkt 结构更改

检查 DDI 函数中更改的参数

getrbuf() 参数更改

drv_getparm() 参数更改

delay()timeout() 参数更改

rmallocmap()rmallocmap_wait() 参数更改

scsi_alloc_consistent_buf() 参数更改

uiomove() 参数更改

cv_timedwait()cv_timedwait_sig() 参数更改

ddi_device_copy() 参数更改

ddi_device_zero() 参数更改

ddi_dma_mem_alloc() 参数更改

修改处理数据共享的例程

ioctl() 中的数据共享

devmap() 中的数据共享

mmap() 中的数据共享

检查 x86 平台上 64 位 Long 数据类型的结构

已知的 ioctl 接口

设备大小

D.  控制台帧缓存器驱动程序

E.  pci.conf 文件

索引

常规转换步骤

以下各节提供了有关转换驱动程序以在 64 位环境中运行的信息。驱动程序编写人员可能需要执行以下一项或多项任务:

  1. 使用硬件寄存器的固定宽度类型。

  2. 使用固定宽度的公共访问函数。

  3. 检查并扩展派生类型的用法。

  4. 检查 DDI 数据结构中更改的字段。

  5. 检查 DDI 函数中更改的参数。

  6. 根据需要修改用于处理用户数据的驱动程序入口点。

  7. 检查 x86 平台上使用 64 位 long 类型的结构。

下面详细说明这些步骤。

完成每一步骤后,请修复所有编译器警告,然后使用 lint 查找其他问题。对于 SC5.0(或更高)版本的 lint,要想找出 64 位问题,必须在使用该命令时指定 -Xarch=v9-errchk=longptr64 选项。请参见《Solaris(64 位)开发者指南》中有关使用和解释 lint 输出的说明。


注 - 请勿忽略 LP64 转换期间出现的编译警告。以前在 ILP32 环境中可安全忽略的警告现在可能表示比较严重的问题。


完成所有步骤后,同时将驱动程序作为 32 位和 64 位模块进行编译和测试。

使用硬件寄存器的固定宽度类型

许多处理硬件设备的设备驱动程序使用 C 数据结构说明硬件的布局。在 LP64 数据模型中,使用 long 或 unsigned long 类型定义硬件寄存器的数据结构几乎肯定不正确,因为 long 类型现在是 64 位。首先包括 <sys/inttypes.h>,然后将此类数据结构更新为使用 int32_tuint32_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 */

使用固定宽度的公共访问函数

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_tintptr_t 是整数类型,应该用于指针。

固定宽度的整数类型用于表示二进制数据结构或硬件寄存器的显式大小,而基础 C 语言数据类型(如 int)仍然可用于循环计数器或文件描述符。

一些系统派生类型在 32 位系统上表示 32 位,但是在 64 位系统上表示 64 位。以此方式更改大小的派生类型包括: clock_tdaddr_tdev_tino_tintptr_toff_t size_tssize_ttime_tuintptr_ttimeout_id_t

设计使用这些派生类型的驱动程序时,请特别注意这些类型的用法,尤其是驱动程序将这些值指定给其他类型(如固定宽度类型)的变量时。

检查 DDI 数据结构中更改的字段

DDI 数据结构中某些字段的数据类型(如 buf(9S))已被更改。使用这些数据结构的驱动程序应确保正确使用这些字段。下面列出了变动很大的数据结构及字段。

buf 结构更改

以下列出的字段与传输大小(现在可超过 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

ddi_dma_attr(9S) 结构定义 DMA 引擎和设备的属性。因为这些属性指定寄存器大小,所以使用了固定宽度的数据类型而不是基本类型。

ddi_dma_cookie 结构更改

uint32_t     dmac_address;    /* was type unsigned long */
size_t       dmac_size;       /* was type u_int */

ddi_dma_cookie(9S) 结构包含 32 位 DMA 地址,因此使用了固定宽度的数据类型来定义该地址。其大小已重新定义为 size_t

csi_arq_status 结构更改

uint_t    sts_rqpkt_state;         /* was type u_long */
uint_t    sts_rqpkt_statistics;    /* was type u_long */

此结构中的这些字段无需增大,已重新定义为 32 位。

scsi_pkt 结构更改

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_flagspkt_statepkt_statistics 字段无需增大,因此这些字段已重新定义为 32 位整数。数据传输大小 pkt_resid 字段需要增大,已重新定义为 ssize_t

检查 DDI 函数中更改的参数

本节介绍已更改的 DDI 函数参数数据类型。

getrbuf() 参数更改

struct buf *getrbuf(int sleepflag);

在以前的发行版中,sleepflag 被定义为 long 类型。

drv_getparm() 参数更改

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)

delay()timeout() 参数更改

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

rmallocmap()rmallocmap_wait() 参数更改

struct map *rmallocmap(size_t mapsize);
struct map *rmallocmap_wait(size_t mapsize);

rmallocmap(9F)rmallocmap_wait(9F) 例程的 mapsize 参数已从 ulong_t 更改为 size_t

scsi_alloc_consistent_buf() 参数更改

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 类型。

uiomove() 参数更改

int uiomove(caddr_t address, size_t nbytes,
    enum uio_rw rwflag, uio_t *uio_p);

nbytes 参数被定义为 long 类型,但是由于 nbytes 以字节为单位表示大小,因此 size_t 更适合。

cv_timedwait()cv_timedwait_sig() 参数更改

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 更适合。

ddi_device_copy() 参数更改

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_advcntdev_datasz 参数的类型已更改。这些参数以前分别被定义为 longlongulong_t 类型。

ddi_device_zero() 参数更改

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 类型。

ddi_dma_mem_alloc() 参数更改

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);

在以前的发行版中,lengthflagsreal_length 分别被定义为 uint_tulong_tuint_t * 类型。

修改处理数据共享的例程

如果设备驱动程序使用 ioctl(9E)devmap(9E)mmap(9E) 与 32 位应用程序共享包含 long 或指针类型的数据结构,而驱动程序已针对 64 位内核进行了重新编译,则数据结构的二进制布局将不兼容。 如果当前已按 long 类型定义了字段,并且未使用 64 位数据项,请更改数据结构,以使用仍为 32 位的数据类型(intunsigned int)。否则,驱动程序需要识别 ILP32 和 LP64 的不同结构形式,并确定应用程序与内核之间是否出现模型不匹配。

要处理潜在的数据模型差异,需要编写可直接与用户应用程序交互的 ioctl()devmap()mmap() 驱动程序入口点,以确定参数是否来自使用与内核具有相同数据模型的应用程序。

ioctl() 中的数据共享

要确定应用程序与驱动程序之间是否存在模型不匹配,驱动程序可使用 FMODELS 掩码确定 ioctl () mode 参数的模型类型。在 mode 中采用以下值之一来标识应用程序的数据模型:

对有 64 位处理能力的设备驱动程序的 I/O 控制支持中的代码示例说明如 何使用 ddi_model_convert_from(9F) 处理此情况。

devmap() 中的数据共享

要使 64 位驱动程序和 32 位应用程序共享内存,64 位驱动程序生成的二进制布局必须与 32 位应用程序使用的布局相同。要导出到应用程序的映射内存可能需要包含与数据模型有关的数据结构。

很少内存映射设备会面临此问题,因为在内核数据模型发生变化时设备寄存器不会改变大小。但是,一些将映射导出到用户地址空间的伪设备可能要将不同数据结构导出到 ILP32 或 LP64 应用程序。要确定是否出现了数据模型不匹配,devmap(9E) 可使用 model 参数说明应用程序期望的数据模型。model 参数被设置为以下值之一:

可将未经转换的模型参数传递到 ddi_model_convert_from(9F) 例程或 STRUCT_INIT()。请参见32 位和 64 位数据结构宏

mmap() 中的数据共享

由于 mmap(9E) 没有可用于传递数据模型信息的参数,因此可编写驱动程序的 mmap(9E) 入口点,以使用新的 DDI 函数 ddi_model_convert_from(9F)。此函数返回以下值之一,以指示应用程序的数据类型模型:

ioctl()devmap() 一样,可将模型位传递到 ddi_model_convert_from(9F) 以确定是否需要进行数据转换,或可将模型传递到 STRUCT_INIT()

或者,迁移设备驱动程序以支持 devmap(9E) 入口点。

检查 x86 平台上 64 位 Long 数据类型的结构

您应认真检查 x86 平台上使用 64 位 long 类型(如 uint64_t)的结构。32 位模式编译与 64 位模式编译的对齐方式和大小可能不同。请参考以下示例。

#include &lt;studio>
#include &ltsys>

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() 调用将结构传入或传出内核的情况下。