编写设备驱动程序

设备状态管理

管理 USB 设备具体涉及到热插拔、系统电源管理(检查点和恢复)以及设备电源管理这几方面。所有客户机驱动程序应实现下图中所示的基本状态机。有关更多信息,请参见 /usr/include/sys/usb/usbai.h

图 20–4 USB 设备状态机

图中显示了在七个不同事件中的每个事件之后设备进入的状态。

可以使用特定于驱动程序的状态扩充此状态机及其四种状态。可以定义设备状态 0x800xff,且只有客户机驱动程序可以使用这些状态。

热插拔 USB 设备

USB 设备支持热插拔。可以随时插入或移除 USB 设备。客户机驱动程序必须处理打开的设备的移除和重新插入。使用热插拔回调可处理打开的设备。关闭的设备的插入和移除由 attach(9E)detach(9E) 入口点处理。

热插拔回调

USBA 2.0 框架支持以下事件通知:

客户机驱动程序必须在其 attach(9E) 例程中调用 usb_register_hotplug_cbs(9F),以便注册事件回调。在中断之前,驱动程序必须在其 detach(9E) 例程中调用 usb_unregister_hotplug_cbs(9F)

热插入

USB 设备的热插入的事件顺序如下:

  1. 集线器驱动程序 hubd(7D) 等待端口连接状态发生变化。

  2. hubd 驱动程序检测到端口连接。

  3. hubd 驱动程序枚举设备,创建子设备节点,然后连接客户机驱动程序。有关兼容名称的定义,请参阅绑定客户机驱动程序

  4. 客户机驱动程序管理设备。驱动程序处于 ONLINE 状态。

热移除

USB 设备的热移除的事件顺序如下:

  1. 集线器驱动程序 hubd(7D) 等待端口连接状态发生变化。

  2. hubd 驱动程序检测到端口断开连接。

  3. hubd 驱动程序将断开连接事件发送到子客户机驱动程序。如果子客户机驱动程序是 hubd 驱动程序或 usb_mid(7D) 多接口驱动程序,则子客户机驱动程序将该事件传播到其子级。

  4. 客户机驱动程序在内核线程上下文中接收断开连接事件通知。内核线程上下文使驱动程序的断开连接处理程序进入阻塞状态。

  5. 客户机驱动程序将转为 DISCONNECTED 状态。未完成的 I/O 传输将失败,完成原因为 device not responding。所有新 I/O 传输以及打开设备节点的尝试也将失败。要关闭管道,不需要客户机驱动程序。而要保存设备以及重新连接设备时需要恢复的驱动程序上下文,需要客户机驱动程序。

  6. hubd 驱动程序试图按照从下到上的顺序使 OS 设备节点及其子节点脱机。

如果在 hubd 驱动程序试图使设备节点脱机时,未打开该设备节点,则会发生以下事件:

  1. 将调用客户机驱动程序的 detach(9E) 入口点。

  2. 销毁设备节点。

  3. 新设备可以使用相应端口。

  4. 重新开始热插拔事件序列。hubd 驱动程序等待端口连接状态发生变化。

如果在 hubd 驱动程序试图使设备节点脱机时已打开该设备节点,则会发生以下事件:

  1. hubd 驱动程序将脱机请求放入定期脱机重试队列。

  2. 新设备仍然不可使用相应端口。

如果在 hubd 驱动程序试图使设备节点脱机时,已打开该设备节点,但用户稍后关闭了该设备节点,则 hubd 驱动程序定期使该设备节点脱机将成功,且会发生以下事件:

  1. 将调用客户机驱动程序的 detach(9E) 入口点。

  2. 销毁设备节点。

  3. 新设备可以使用相应端口。

  4. 重新开始热插拔事件序列。hubd 驱动程序等待端口连接状态发生变化。

如果用户关闭使用该设备的所有应用程序,则端口将重新变为可用。如果应用程序未终止或未关闭该设备,则端口仍然不可用。

热重新插入

如果将先前移除的设备重新插入同一端口,同时该设备的设备节点仍处于打开状态,则会发生以下事件:

  1. 集线器驱动程序 hubd(7D) 检测到端口连接。

  2. hubd 驱动程序恢复总线地址和设备配置。

  3. hubd 驱动程序取消脱机重试请求。

  4. hubd 驱动程序将连接事件发送到客户机驱动程序。

  5. 客户机驱动程序收到连接事件。

  6. 客户机驱动程序确定新设备是否与先前连接的设备相同。客户机驱动程序首先通过比较设备描述符来进行此项确定。客户机驱动程序也可以比较序列号和配置描述符群。

如果客户机驱动程序确定当前设备与先前连接的设备不同,则可能会发生以下事件:

  1. 客户机驱动程序可能向控制台发出警告消息。

  2. 用户可能再次移除该设备。如果用户再次移除该设备,则将重新开始热移除事件序列。hubd 驱动程序检测到端口断开连接。如果用户没有再次移除该设备,则会发生以下事件:

    1. 客户机驱动程序仍然保持 DISCONNECTED 状态,所有请求和打开操作将失败。

    2. 端口仍然不可用。用户必须关闭设备并断开其连接以释放端口。

    3. 释放端口时,将重新开始热插拔事件序列。hubd 驱动程序等待端口连接状态发生变化。

如果客户机驱动程序确定当前设备与先前连接的设备相同,则可能会发生以下事件:

  1. 客户机驱动程序可能恢复其状态,并继续正常操作。此策略由客户机驱动程序负责。音频扬声器就是客户机驱动程序可继续操作的典型示例。

  2. 如果使用重新连接的设备继续操作是安全的,则将重新开始热插拔事件序列。hubd 驱动程序等待端口连接状态发生变化。设备再次可用。

电源管理

本节讨论设备电源管理和系统电源管理。

设备电源管理根据各个 USB 设备的 I/O 是处于活动状态还是空闲状态来管理这些设备。

系统电源管理使用检查点和恢复机制在文件中设置系统状态的检查点,然后完全关闭系统。(检查点有时称为“系统暂停”。)再次打开系统电源时,系统将恢复为其暂停前的状态。

设备电源管理

下面简要列出了要对 USB 设备进行电源管理时驱动程序需要执行的操作。后面对电源管理进行了较详细的说明。

  1. 在执行 attach(9E) 期间创建电源管理组件。请参见 usb_create_pm_components(9F) 手册页。

  2. 实现 power(9E) 入口点。

  3. 在访问设备之前调用 pm_busy_component(9F)pm_raise_power(9F)

  4. 完成设备访问后调用 pm_idle_component(9F)

USBA 2.0 框架支持 USB 接口电源管理规范指定的四种电源级别。有关 USB 电源级别与操作系统电源级别对应关系的信息,请参见 /usr/include/sys/usb/usbai.h

当设备进入 USB_DEV_OS_PWR_OFF 状态时,hubd 驱动程序将暂停端口。当设备进入 USB_DEV_OS_PWR_1 及以上状态时,hubd 驱动程序将恢复端口。请注意,端口暂停不同于系统暂停。端口暂停时,将仅关闭 USB 端口。系统电源管理中定义了系统暂停。

客户机驱动程序可以选择在设备上启用远程唤醒。请参见 usb_handle_remote_wakeup(9F) 手册页。当 hubd 驱动程序在端口上发现远程唤醒时,hubd 驱动程序将完成唤醒操作,并调用 pm_raise_power(9F) 以通知子级。

下图显示了电源管理的不同部分之间的关系。

图 20–5 USB 电源管理

图中显示了使用两种不同电源管理方案的时机。

驱动程序可以实现图 20–5 底部说明的两种电源管理方案之一。被动方案比主动方案简单,这是因为被动方案在设备传输期间不进行电源管理。

主动电源管理

本节介绍实现主动电源管理方案需使用的函数。

在驱动程序的 attach(9E) 入口点执行以下工作:

  1. 调用 usb_create_pm_components(9F)

  2. 可选择调用 usb_handle_remote_wakeup(9F)(使用 USB_REMOTE_WAKEUP_ENABLE 作为第二个参数),以在设备上启用远程唤醒。

  3. 调用 pm_busy_component(9F)

  4. 调用 pm_raise_power(9F) 以使功耗达到 USB_DEV_OS_FULL_PWR 级别。

  5. 与设备通信以初始化该设备。

  6. 调用 pm_idle_component(9F)

在驱动程序的 detach(9E) 入口点执行以下工作:

  1. 调用 pm_busy_component(9F)

  2. 调用 pm_raise_power(9F) 以使功耗达到 USB_DEV_OS_FULL_PWR 级别。

  3. 如果在 attach(9E) 入口点中调用了 usb_handle_remote_wakeup (9F) 函数,请在此处调用 usb_handle_remote_wakeup(9F)(使用 USB_REMOTE_WAKEUP_DISABLE 作为第二个参数)。

  4. 与设备通信以干净地关闭该设备。

  5. 调用 pm_lower_power(9F) 以使功耗达到 USB_DEV_OS_PWR_OFF 级别。

    这是唯一一次客户机驱动程序调用 pm_lower_power(9F)。

  6. 调用 pm_idle_component(9F)

当驱动程序线程要启动在设备上执行 I/O 操作时,该线程将执行以下任务:

  1. 调用 pm_busy_component(9F)

  2. 调用 pm_raise_power(9F) 以使功耗达到 USB_DEV_OS_FULL_PWR 级别。

  3. 开始 I/O 传输。

当驱动程序收到 I/O 传输已完成的通知时,驱动程序将调用 pm_idle_component(9F)

在驱动程序的 power(9E) 入口点中,检查您要转换到的电源级别是否有效。此外,还可能需要考虑同时调用 power(9E) 的不同线程。

如果设备已空闲一段时间或者系统正在关闭,则可以调用 power(9E) 例程以使设备进入 USB_DEV_OS_PWR_OFF 状态。此状态对应于图 20–4 中所示的 PWRED_DWN 状态。如果设备将进入 USB_DEV_OS_PWR_OFF 状态,请在 power(9E) 例程中执行以下工作:

  1. 使所有打开的管道进入空闲状态。例如,停止对中断管道进行的轮询。

  2. 保存任何设备或需要保存的驱动程序上下文。

    在完成 power(9E) 调用后,将暂停设备所连接到的端口。

收到设备启动的远程唤醒或系统启动的唤醒时,可以调用 power(9E) 例程以打开设备电源。由于超出空闲时间或系统暂停而关闭设备电源后,将会发生唤醒通知。如果设备将进入 USB_DEV_OS_PWR_1 或以上状态,请在 power(9E) 例程中执行以下工作:

  1. 恢复任何所需的设备和驱动程序上下文。

  2. 在管道中重新启动适合指定电源级别的活动。例如,对中断管道启动轮询。

如果先前暂停了设备所连接到的端口,则在调用 power(9E) 之前将恢复该端口。

被动电源管理

被动电源管理方案比上面介绍的主动电源管理方案简单。在此被动方案中,在传输期间不执行任何电源管理。要实现此被动方案,请在打开设备时调用 pm_busy_component(9F)pm_raise_power(9F)。然后在关闭设备时调用 pm_idle_component(9F)

系统电源管理

系统电源管理包括:在保存整个系统的状态后关闭系统,以及在重新打开系统后恢复状态。此过程称为 CPR(checkpoint and resume,检查点和恢复)。在 CPR 相关方面,USB 客户机驱动程序的运行方式与其他客户机驱动程序相同。要暂停设备,请在 cmd 参数为 DDI_SUSPEND 的情况下调用驱动程序的 detach(9E) 入口点。要恢复设备,请在 cmd 参数为 DDI_RESUME 的情况下调用驱动程序的 attach(9E) 入口点。处理 detach(9E) 例程中的 DDI_SUSPEND 命令时,请尽可能地清理设备状态和驱动程序状态,以满足后面清理恢复操作的需要。(请注意,这对应于图 20–4 中的 SUSPENDED 状态。)处理 attach(9E) 例程中的 DDI_RESUME 命令时,务必使设备达到全功率状态,以使设备与系统同步。

对于 USB 设备,暂停和恢复的处理与热插拔断开连接和重新连接类似(请参见热插拔 USB 设备)。CPR 与热插拔之间的重要差别是,在 CPR 的情况下,如果设备处于不可暂停的状态,驱动程序的检查点过程可能会失败。例如,如果设备正在进行错误恢复,则无法暂停设备。如果设备正忙,无法安全将其停止,也无法暂停该设备。

序列化

通常,驱动程序在持有互斥锁时不应调用 USBA 函数。因此,客户机驱动程序中的竞态条件可能很难防止。

不允许在处理异步事件(如断开连接或 CPR)的同时运行正常操作代码。这些类型的异步事件通常会清理和中断管道,可能会破坏正常操作代码。

一种管理竞态条件和保护正常操作代码的方法是,编写可以获取和释放独占访问同步对象的序列化工具。您可以按以下方法编写序列化工具:通过调用 USBA 函数安全地持有同步对象。usbskel 驱动程序样例中就采用了这种方法。有关 usbskel 驱动程序的信息,请参见USB 设备驱动程序样例