用户空间 DTX (剪贴板分离系统) 接口¶
surface_dtx
驱动程序负责正确的剪贴板分离和重新连接处理。为此,它提供了 /dev/surface/dtx
设备文件,可以通过它与用户空间守护程序交互。然后,此守护程序最终负责确定并采取必要的措施,例如卸载连接到基座的设备、卸载/重新加载图形驱动程序、用户通知等。
此驱动程序中使用了两个基本的通信原则:命令(在文档的其他部分也称为请求)和事件。命令被发送到 EC,并且在不同的上下文中可能具有不同的含义。事件由 EC 在某些内部状态更改时发送。命令始终由驱动程序启动,而事件始终由 EC 启动。
命名法¶
剪贴板: Surface Book 的可分离上部,包含屏幕和 CPU。
基座: Surface Book 的下部,剪贴板可以从中分离,可以选择(取决于型号)包含独立 GPU (dGPU)。
锁闩: 在正常操作中将剪贴板连接到基座的机制,并允许在请求时将其分离。
静默忽略的命令: 该命令被 EC 接受为有效命令并确认(遵循标准通信协议),但 EC 不对其采取任何操作,即忽略它。
分离过程¶
警告:本文档的这一部分基于逆向工程和测试,因此可能包含错误或不完整。
锁闩状态¶
锁闩机制有两种主要状态:打开和关闭。在关闭状态(默认)下,剪贴板固定到基座,而在打开状态下,用户可以移除剪贴板。
锁闩还可以被锁定和相应地解锁,这可能会影响分离过程。具体而言,此锁定机制旨在防止位于设备基座中的 dGPU 在使用中时被热插拔。有关更多详细信息,请参见下面的分离程序文档。默认情况下,锁闩是解锁的。
分离程序¶
请注意,分离过程完全由 EC 控制。surface_dtx
驱动程序仅将事件从 EC 中继到用户空间,并将命令从用户空间中继到 EC,即它不影响此过程。
分离过程从用户按下设备基座上的分离按钮或执行 SDTX_IOCTL_LATCH_REQUEST
IOCTL 开始。此后
EC 打开分离按钮上的指示灯,发送一个分离请求事件 (
SDTX_EVENT_REQUEST
),并等待进一步的指令/命令。如果锁闩已解锁,指示灯将闪烁绿色。如果锁闩已锁定,指示灯将呈红色常亮。该事件通过
surface_dtx
驱动程序中继到用户空间,用户空间中的相应守护程序可以处理该事件并通过此驱动程序提供的 IOCTL 将指令发回 EC。EC 等待用户空间的指令并根据指令执行操作。如果 EC 在给定的时间内未收到任何指令,它将超时并按以下方式继续:
如果锁闩已解锁,EC 将打开锁闩,并且剪贴板可以从基座分离。这与没有此驱动程序或任何用户空间守护程序的情况下的行为完全相同。有关 EC 后续行为的更多详细信息,请参见下面的
SDTX_IOCTL_LATCH_CONFIRM
说明。如果锁闩已锁定,EC 将不打开锁闩,这意味着剪贴板无法从基座分离。此外,EC 发送一个取消事件 (
SDTX_EVENT_CANCEL
),其中详细说明了取消原因SDTX_DETACH_TIMEDOUT
(有关详细信息,请参见 事件)。
用户空间守护程序对分离请求事件的有效响应是:
执行
SDTX_IOCTL_LATCH_REQUEST
。这将立即中止分离过程。此外,EC 将发送一个分离请求事件,类似于用户按下分离按钮取消该过程(参见下文)。执行
SDTX_IOCTL_LATCH_CONFIRM
。这将导致 EC 打开锁闩,之后用户可以分离剪贴板和基座。由于这会更改锁闩状态,因此一旦成功打开锁闩,将发送一个锁闩状态事件 (
SDTX_EVENT_LATCH_STATUS
)。如果 EC 无法打开锁闩,例如由于硬件错误或电池电量不足,将发送一个锁闩取消事件 (SDTX_EVENT_CANCEL
),取消原因表明具体故障。如果锁闩当前已锁定,则锁闩将在打开之前自动解锁。
执行
SDTX_IOCTL_LATCH_HEARTBEAT
。这将重置内部超时。不会执行其他操作,即分离过程既不会完成也不会取消,并且 EC 仍将等待进一步的响应。执行
SDTX_IOCTL_LATCH_CANCEL
。这将中止分离过程,类似于上面描述的SDTX_IOCTL_LATCH_REQUEST
或下面描述的按钮按下。作为对此的响应,将发送一个通用请求事件 (SDTX_EVENT_REQUEST
)。但是,与这些相反,如果没有正在进行的分离过程,此命令不会触发新的分离过程。不执行任何操作。分离过程最终会如第 3 点所述超时。
有关这些响应的更多详细信息,请参见 IOCTL。
重要的是要注意,如果用户在分离操作正在进行中的任何时候按下分离按钮(即在 EC 发送初始分离请求事件 (SDTX_EVENT_REQUEST
) 之后,并在收到结束该过程的相应响应之前),分离过程将在 EC 级别取消,并且将发送一个相同的事件。因此,分离请求事件本身并不表示分离过程的开始。
由于硬件故障或剪贴板电池电量不足,EC 可能会进一步取消分离过程。这是通过一个取消事件 (SDTX_EVENT_CANCEL
) 完成的,其中包含相应的取消原因。
用户空间接口文档¶
错误代码和状态值¶
错误和状态代码分为不同的类别,可用于确定状态代码是否为错误,如果是,则确定错误的严重性和类型。当前的类别是:
名称 |
值 |
简短描述 |
---|---|---|
|
|
非错误状态代码。 |
|
|
非关键运行时错误。 |
|
|
严重硬件故障。 |
|
|
未知错误代码。 |
其他类别保留供将来使用。SDTX_CATEGORY()
宏可用于确定任何状态值的类别。SDTX_SUCCESS()
宏可用于检查状态值是否为成功值 (SDTX_CATEGORY_STATUS
) 或是否指示失败。
EC 发送的未知状态或错误代码由驱动程序分配给 UNKNOWN
类别,并且将来可能会通过自己的代码实现。
当前使用的错误代码为:
名称 |
类别 |
值 |
简短描述 |
---|---|---|---|
|
|
|
由于剪贴板电池电量不足,无法分离。 |
|
|
|
锁闩锁定时分离过程超时。 |
|
|
|
无法打开锁闩。 |
|
|
|
无法保持锁闩打开。 |
|
|
|
无法关闭锁闩。 |
其他错误代码保留供将来使用。非错误状态代码可能会重叠,并且通常仅在其用例中是唯一的
名称 |
类别 |
值 |
简短描述 |
---|---|---|---|
|
|
|
锁闩已关闭/已关闭。 |
|
|
|
锁闩已打开/已打开。 |
名称 |
类别 |
值 |
简短描述 |
---|---|---|---|
|
|
|
基座已分离/不存在。 |
|
|
|
基座已连接/存在。 |
同样,其他代码保留供将来使用。
事件¶
可以通过从设备文件读取来接收事件。默认情况下,它们处于禁用状态,必须先执行 SDTX_IOCTL_EVENTS_ENABLE
才能启用。所有事件都遵循 struct sdtx_event
规定的布局。特定事件类型可以通过其事件代码来识别,该代码在 enum sdtx_event_code
中描述。请注意,其他事件代码保留供将来使用,因此事件解析器必须能够通过依赖事件头中给出的有效负载长度来优雅地处理任何未知/不支持的事件类型。
当前提供的事件类型为:
名称 |
代码 |
有效负载 |
简短描述 |
---|---|---|---|
|
|
|
分离过程已启动/中止。 |
|
|
|
EC 取消了分离过程。 |
|
|
|
基座连接状态已更改。 |
|
|
|
锁闩状态已更改。 |
|
|
|
设备模式已更改。 |
更详细的各个事件
SDTX_EVENT_REQUEST
¶
当用户启动或中止分离过程时发送,可以通过按下分离按钮或从用户空间发送分离请求 (SDTX_IOCTL_LATCH_REQUEST
) 来实现。
没有任何有效负载。
SDTX_EVENT_CANCEL
¶
当 EC 由于未满足的先决条件(例如,剪贴板电池电量太低而无法分离)或硬件故障而取消分离过程时发送。取消的原因在下面详细说明的事件有效负载中给出,可以是以下之一:
SDTX_DETACH_TIMEDOUT
:锁闩锁定时分离超时。锁闩既没有打开也没有解锁。SDTX_DETACH_NOT_FEASIBLE
:由于剪贴板电池电量不足,无法分离。SDTX_ERR_FAILED_TO_OPEN
:无法打开锁闩(硬件故障)。SDTX_ERR_FAILED_TO_REMAIN_OPEN
:无法保持锁闩打开(硬件故障)。SDTX_ERR_FAILED_TO_CLOSE
:无法关闭锁闩(硬件故障)。
此上下文中的其他错误代码保留供将来使用。
这些代码可以通过 SDTX_CATEGORY()
宏进行分类,以区分关键硬件错误 (SDTX_CATEGORY_HARDWARE_ERROR
) 或运行时错误 (SDTX_CATEGORY_RUNTIME_ERROR
),如果未给出某些分离的先决条件,则后者可能在正常操作期间发生。
字段 |
类型 |
描述 |
---|---|---|
|
|
取消原因。 |
SDTX_EVENT_BASE_CONNECTION
¶
当基座连接状态已更改时发送,即当基座已连接、分离或由于剪贴板电池电量不足而导致分离变得不可行时。新状态,如果基座已连接,则基座的 ID 作为 struct sdtx_base_info
类型的有效负载提供,其布局如下所示:
字段 |
类型 |
描述 |
---|---|---|
|
|
基座连接状态。 |
|
|
连接的基座类型(如果没有则为零)。 |
state
的可能值为:
SDTX_BASE_DETACHED
,SDTX_BASE_ATTACHED
和SDTX_DETACH_NOT_FEASIBLE
.
其他值保留供将来使用。
SDTX_EVENT_LATCH_STATUS
¶
当锁闩状态已更改时发送,即当锁闩已打开、关闭或发生错误时。当前状态作为有效负载提供
字段 |
类型 |
描述 |
---|---|---|
|
|
锁闩状态。 |
status
的可能值为:
SDTX_LATCH_CLOSED
,SDTX_LATCH_OPENED
,SDTX_ERR_FAILED_TO_OPEN
,SDTX_ERR_FAILED_TO_REMAIN_OPEN
和SDTX_ERR_FAILED_TO_CLOSE
.
其他值保留供将来使用。
SDTX_EVENT_DEVICE_MODE
¶
当设备模式已更改时发送。新设备模式作为有效负载提供
字段 |
类型 |
描述 |
---|---|---|
|
|
设备操作模式。 |
mode
的可能值为:
SDTX_DEVICE_MODE_TABLET
,SDTX_DEVICE_MODE_LAPTOP
和SDTX_DEVICE_MODE_STUDIO
.
其他值保留供将来使用。
IOCTL¶
提供了以下 IOCTL:
类型 |
编号 |
方向 |
名称 |
描述 |
---|---|---|---|---|
|
|
|
|
为当前文件描述符启用事件。 |
|
|
|
|
为当前文件描述符禁用事件。 |
|
|
|
|
锁定锁闩。 |
|
|
|
|
解锁锁闩。 |
|
|
|
|
请求剪贴板分离。 |
|
|
|
|
确认剪贴板分离请求。 |
|
|
|
|
向 EC 发送心跳信号。 |
|
|
|
|
取消分离过程。 |
|
|
|
|
获取当前基座/连接信息。 |
|
|
|
|
获取当前设备操作模式。 |
|
|
|
|
获取当前设备锁闩状态。 |
SDTX_IOCTL_EVENTS_ENABLE
¶
定义为 _IO(0xA5, 0x22)
。
为当前文件描述符启用事件。如果启用了事件,可以通过从设备读取来获取事件。默认情况下,事件处于禁用状态。
SDTX_IOCTL_EVENTS_DISABLE
¶
定义为 _IO(0xA5, 0x22)
。
为当前文件描述符禁用事件。如果启用了事件,可以通过从设备读取来获取事件。默认情况下,事件处于禁用状态。
SDTX_IOCTL_LATCH_LOCK
¶
定义为 _IO(0xA5, 0x23)
。
锁定锁闩,导致分离过程中止,而不会在超时时打开锁闩。默认情况下,锁闩是解锁的。如果锁闩已锁定,此命令将被静默忽略。
SDTX_IOCTL_LATCH_UNLOCK
¶
定义为 _IO(0xA5, 0x24)
。
解锁锁闩,导致分离过程在超时时打开锁闩。默认情况下,锁闩是解锁的。如果在正在进行的分离过程中发送此命令,它不会打开锁闩。如果锁闩已解锁,此命令将被静默忽略。
SDTX_IOCTL_LATCH_REQUEST
¶
定义为 _IO(0xA5, 0x25)
。
通用锁闩请求。行为取决于上下文:如果没有活动的分离过程,则请求分离。否则,当前活动的分离过程将被中止。
如果分离过程被此操作取消,将发送一个通用分离请求事件 (SDTX_EVENT_REQUEST
)。
这本质上与按下分离按钮的行为相同。
SDTX_IOCTL_LATCH_CONFIRM
¶
定义为 _IO(0xA5, 0x26)
。
确认并确认锁闩请求。如果在正在进行的分离过程中发送此命令,这将导致锁闩立即打开。如果锁闩已锁定,锁闩也将打开。在这种情况下,锁闩锁定将重置为解锁状态。
如果当前没有正在进行的分离程序,此命令将被静默忽略。
SDTX_IOCTL_LATCH_HEARTBEAT
¶
定义为 _IO(0xA5, 0x27)
。
发送心跳信号,本质上是重置分离超时。当成功分离所需的工作仍在进行中时,可以使用此命令来保持分离过程的活动状态。
如果当前没有正在进行的分离程序,此命令将被静默忽略。
SDTX_IOCTL_LATCH_CANCEL
¶
定义为 _IO(0xA5, 0x28)
。
取消正在进行的分离(如果有)。如果分离过程被此操作取消,将发送一个通用分离请求事件 (SDTX_EVENT_REQUEST
)。
如果当前没有正在进行的分离程序,此命令将被静默忽略。
SDTX_IOCTL_GET_BASE_INFO
¶
定义为 _IOR(0xA5, 0x29, struct sdtx_base_info)
。
获取当前基座连接状态(即已连接/已分离)以及连接到剪贴板的基座类型。此命令本质上提供了一种查询基座连接更改事件 (SDTX_EVENT_BASE_CONNECTION
) 提供的信息的方法。
struct sdtx_base_info.state
的可能值为:
SDTX_BASE_DETACHED
,SDTX_BASE_ATTACHED
和SDTX_DETACH_NOT_FEASIBLE
.
其他值保留供将来使用。
SDTX_IOCTL_GET_DEVICE_MODE
¶
定义为 _IOR(0xA5, 0x2A, __u16)
。
返回设备操作模式,指示基座是否以及如何连接到剪贴板。此命令本质上提供了一种查询设备模式更改事件 (SDTX_EVENT_DEVICE_MODE
) 提供的信息的方法。
返回的值为:
SDTX_DEVICE_MODE_LAPTOP
SDTX_DEVICE_MODE_TABLET
SDTX_DEVICE_MODE_STUDIO
有关详细信息,请参见 struct sdtx_device_mode
。其他值保留供将来使用。
SDTX_IOCTL_GET_LATCH_STATUS
¶
定义为 _IOR(0xA5, 0x2B, __u16)
。
获取当前锁闩状态或(大概)尝试打开/关闭锁闩时遇到的最后一个错误。此命令本质上提供了一种查询锁闩状态更改事件 (SDTX_EVENT_LATCH_STATUS
) 提供的信息的方法。
返回的值为:
SDTX_LATCH_CLOSED
,SDTX_LATCH_OPENED
,SDTX_ERR_FAILED_TO_OPEN
,SDTX_ERR_FAILED_TO_REMAIN_OPEN
和SDTX_ERR_FAILED_TO_CLOSE
.
其他值保留供将来使用。
关于基座 ID 的说明¶
通过 SDTX_EVENT_BASE_CONNECTION
或 SDTX_IOCTL_GET_BASE_INFO
提供的基座类型/ID 直接从 EC 转发到组合 __u16
值的低字节中,驱动程序将此 ID 来自的 EC 类型存储在高字节中(如果没有这个,不同类型的 EC 上的基座 ID 可能会重叠)。
SDTX_DEVICE_TYPE()
宏可用于确定 EC 设备类型。这可以是以下之一:
SDTX_DEVICE_TYPE_HID
,用于通过 HID 的 Surface 聚合器模块,以及SDTX_DEVICE_TYPE_SSH
,用于通过 Surface 串行集线器的 Surface 聚合器模块。
请注意,目前仅支持 SSH
类型的 EC,但是 HID
类型保留供将来使用。
结构体和枚举¶
-
enum sdtx_device_mode¶
描述剪贴板如何(以及是否)连接到设备基座的模式。
常量
SDTX_DEVICE_MODE_TABLET
剪贴板已从基座分离,并且设备作为平板电脑运行。
SDTX_DEVICE_MODE_LAPTOP
剪贴板正常连接到基座,并且设备作为笔记本电脑运行。
SDTX_DEVICE_MODE_STUDIO
剪贴板反向连接到基座。设备作为平板电脑运行,键盘和触摸板已停用,但是基座电池以及(如果在特定设备型号中存在)dGPU 可用于系统。
-
struct sdtx_event¶
通过从 DTX 设备文件读取提供的事件。
定义:
struct sdtx_event {
__u16 length;
__u16 code;
__u8 data[];
};
成员
length
事件有效负载的长度(以字节为单位)。
code
事件代码,详细说明这是什么类型的事件。
data
事件的有效负载,包含 length 字节。
描述
有关当前有效的事件代码,请参见 enum sdtx_event_code
。
-
enum sdtx_event_code¶
描述事件类型的代码。
常量
SDTX_EVENT_REQUEST
分离请求事件类型。
SDTX_EVENT_CANCEL
取消分离过程事件类型。
SDTX_EVENT_BASE_CONNECTION
基座/剪贴板连接更改事件类型。
SDTX_EVENT_LATCH_STATUS
锁闩状态更改事件类型。
SDTX_EVENT_DEVICE_MODE
设备模式更改事件类型。
描述
在 struct sdtx_event
中使用,以描述事件的类型。进一步的事件代码保留供将来使用。任何事件解析器都应该能够优雅地处理未知事件,即通过简单地跳过它们。
有关各个事件类型的详细信息,请查阅 DTX 用户空间接口文档。
-
struct sdtx_base_info¶
描述底座是否已连接以及连接的类型。
定义:
struct sdtx_base_info {
__u16 state;
__u16 base_id;
};
成员
state
连接状态。有效值为
SDTX_BASE_DETACHED
、SDTX_BASE_ATTACHED
和SDTX_DETACH_NOT_FEASIBLE
(如果底座已连接但剪贴板电池电量低,无法分离)。其他值目前保留。base_id
连接的底座类型。如果没有底座连接,则为零。
API 用户¶
使用此 API 的用户空间守护程序可以在 https://github.com/linux-surface/surface-dtx-daemon 中找到。