Skip to content

Commit b05ff10

Browse files
qzedJiri Kosina
authored andcommitted
HID: Add support for Surface Aggregator Module HID transport
Add a HID transport driver to support integrated HID devices on newer Microsoft Surface models (specifically 7th-generation, i.e. Surface Laptop 3, Surface Book 3, and later). On those models, the internal keyboard and touchpad (as well as some other HID devices with currently unknown function) are connected via the generic HID subsystem (TC=0x15) of the Surface System Aggregator Module (SSAM). This subsystem provides a generic HID transport layer, support for which is implemented by this driver. Co-developed-by: Blaž Hrastnik <[email protected]> Signed-off-by: Blaž Hrastnik <[email protected]> Signed-off-by: Maximilian Luz <[email protected]> Signed-off-by: Jiri Kosina <[email protected]>
1 parent a6ad93e commit b05ff10

File tree

8 files changed

+647
-0
lines changed

8 files changed

+647
-0
lines changed

MAINTAINERS

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11876,6 +11876,13 @@ S: Maintained
1187611876
T: git git://git.kernel.org/pub/scm/linux/kernel/git/pdx86/platform-drivers-x86.git
1187711877
F: drivers/platform/surface/
1187811878

11879+
MICROSOFT SURFACE HID TRANSPORT DRIVER
11880+
M: Maximilian Luz <[email protected]>
11881+
11882+
11883+
S: Maintained
11884+
F: drivers/hid/surface-hid/
11885+
1187911886
MICROSOFT SURFACE HOT-PLUG DRIVER
1188011887
M: Maximilian Luz <[email protected]>
1188111888

drivers/hid/Kconfig

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1206,4 +1206,6 @@ source "drivers/hid/intel-ish-hid/Kconfig"
12061206

12071207
source "drivers/hid/amd-sfh-hid/Kconfig"
12081208

1209+
source "drivers/hid/surface-hid/Kconfig"
1210+
12091211
endmenu

drivers/hid/Makefile

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,3 +145,5 @@ obj-$(CONFIG_INTEL_ISH_HID) += intel-ish-hid/
145145
obj-$(INTEL_ISH_FIRMWARE_DOWNLOADER) += intel-ish-hid/
146146

147147
obj-$(CONFIG_AMD_SFH_HID) += amd-sfh-hid/
148+
149+
obj-$(CONFIG_SURFACE_HID_CORE) += surface-hid/

drivers/hid/surface-hid/Kconfig

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# SPDX-License-Identifier: GPL-2.0+
2+
menu "Surface System Aggregator Module HID support"
3+
depends on SURFACE_AGGREGATOR
4+
depends on INPUT
5+
6+
config SURFACE_HID
7+
tristate "HID transport driver for Surface System Aggregator Module"
8+
depends on SURFACE_AGGREGATOR_REGISTRY
9+
select SURFACE_HID_CORE
10+
help
11+
Driver to support integrated HID devices on newer Microsoft Surface
12+
models.
13+
14+
This driver provides support for the HID transport protocol provided
15+
by the Surface Aggregator Module (i.e. the embedded controller) on
16+
7th-generation Microsoft Surface devices, i.e. Surface Book 3 and
17+
Surface Laptop 3. On those models, it is mainly used to connect the
18+
integrated touchpad and keyboard.
19+
20+
Say M or Y here, if you want support for integrated HID devices, i.e.
21+
integrated touchpad and keyboard, on 7th generation Microsoft Surface
22+
models.
23+
24+
endmenu
25+
26+
config SURFACE_HID_CORE
27+
tristate
28+
select HID

drivers/hid/surface-hid/Makefile

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# SPDX-License-Identifier: GPL-2.0+
2+
#
3+
# Makefile - Surface System Aggregator Module (SSAM) HID transport driver.
4+
#
5+
obj-$(CONFIG_SURFACE_HID_CORE) += surface_hid_core.o
6+
obj-$(CONFIG_SURFACE_HID) += surface_hid.o

drivers/hid/surface-hid/surface_hid.c

Lines changed: 253 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,253 @@
1+
// SPDX-License-Identifier: GPL-2.0+
2+
/*
3+
* Surface System Aggregator Module (SSAM) HID transport driver for the
4+
* generic HID interface (HID/TC=0x15 subsystem). Provides support for
5+
* integrated HID devices on Surface Laptop 3, Book 3, and later.
6+
*
7+
* Copyright (C) 2019-2021 Blaž Hrastnik <[email protected]>,
8+
* Maximilian Luz <[email protected]>
9+
*/
10+
11+
#include <asm/unaligned.h>
12+
#include <linux/hid.h>
13+
#include <linux/kernel.h>
14+
#include <linux/module.h>
15+
#include <linux/types.h>
16+
17+
#include <linux/surface_aggregator/controller.h>
18+
#include <linux/surface_aggregator/device.h>
19+
20+
#include "surface_hid_core.h"
21+
22+
23+
/* -- SAM interface. -------------------------------------------------------- */
24+
25+
struct surface_hid_buffer_slice {
26+
__u8 entry;
27+
__le32 offset;
28+
__le32 length;
29+
__u8 end;
30+
__u8 data[];
31+
} __packed;
32+
33+
static_assert(sizeof(struct surface_hid_buffer_slice) == 10);
34+
35+
enum surface_hid_cid {
36+
SURFACE_HID_CID_OUTPUT_REPORT = 0x01,
37+
SURFACE_HID_CID_GET_FEATURE_REPORT = 0x02,
38+
SURFACE_HID_CID_SET_FEATURE_REPORT = 0x03,
39+
SURFACE_HID_CID_GET_DESCRIPTOR = 0x04,
40+
};
41+
42+
static int ssam_hid_get_descriptor(struct surface_hid_device *shid, u8 entry, u8 *buf, size_t len)
43+
{
44+
u8 buffer[sizeof(struct surface_hid_buffer_slice) + 0x76];
45+
struct surface_hid_buffer_slice *slice;
46+
struct ssam_request rqst;
47+
struct ssam_response rsp;
48+
u32 buffer_len, offset, length;
49+
int status;
50+
51+
/*
52+
* Note: The 0x76 above has been chosen because that's what's used by
53+
* the Windows driver. Together with the header, this leads to a 128
54+
* byte payload in total.
55+
*/
56+
57+
buffer_len = ARRAY_SIZE(buffer) - sizeof(struct surface_hid_buffer_slice);
58+
59+
rqst.target_category = shid->uid.category;
60+
rqst.target_id = shid->uid.target;
61+
rqst.command_id = SURFACE_HID_CID_GET_DESCRIPTOR;
62+
rqst.instance_id = shid->uid.instance;
63+
rqst.flags = SSAM_REQUEST_HAS_RESPONSE;
64+
rqst.length = sizeof(struct surface_hid_buffer_slice);
65+
rqst.payload = buffer;
66+
67+
rsp.capacity = ARRAY_SIZE(buffer);
68+
rsp.pointer = buffer;
69+
70+
slice = (struct surface_hid_buffer_slice *)buffer;
71+
slice->entry = entry;
72+
slice->end = 0;
73+
74+
offset = 0;
75+
length = buffer_len;
76+
77+
while (!slice->end && offset < len) {
78+
put_unaligned_le32(offset, &slice->offset);
79+
put_unaligned_le32(length, &slice->length);
80+
81+
rsp.length = 0;
82+
83+
status = ssam_retry(ssam_request_sync_onstack, shid->ctrl, &rqst, &rsp,
84+
sizeof(*slice));
85+
if (status)
86+
return status;
87+
88+
offset = get_unaligned_le32(&slice->offset);
89+
length = get_unaligned_le32(&slice->length);
90+
91+
/* Don't mess stuff up in case we receive garbage. */
92+
if (length > buffer_len || offset > len)
93+
return -EPROTO;
94+
95+
if (offset + length > len)
96+
length = len - offset;
97+
98+
memcpy(buf + offset, &slice->data[0], length);
99+
100+
offset += length;
101+
length = buffer_len;
102+
}
103+
104+
if (offset != len) {
105+
dev_err(shid->dev, "unexpected descriptor length: got %u, expected %zu\n",
106+
offset, len);
107+
return -EPROTO;
108+
}
109+
110+
return 0;
111+
}
112+
113+
static int ssam_hid_set_raw_report(struct surface_hid_device *shid, u8 rprt_id, bool feature,
114+
u8 *buf, size_t len)
115+
{
116+
struct ssam_request rqst;
117+
u8 cid;
118+
119+
if (feature)
120+
cid = SURFACE_HID_CID_SET_FEATURE_REPORT;
121+
else
122+
cid = SURFACE_HID_CID_OUTPUT_REPORT;
123+
124+
rqst.target_category = shid->uid.category;
125+
rqst.target_id = shid->uid.target;
126+
rqst.instance_id = shid->uid.instance;
127+
rqst.command_id = cid;
128+
rqst.flags = 0;
129+
rqst.length = len;
130+
rqst.payload = buf;
131+
132+
buf[0] = rprt_id;
133+
134+
return ssam_retry(ssam_request_sync, shid->ctrl, &rqst, NULL);
135+
}
136+
137+
static int ssam_hid_get_raw_report(struct surface_hid_device *shid, u8 rprt_id, u8 *buf, size_t len)
138+
{
139+
struct ssam_request rqst;
140+
struct ssam_response rsp;
141+
142+
rqst.target_category = shid->uid.category;
143+
rqst.target_id = shid->uid.target;
144+
rqst.instance_id = shid->uid.instance;
145+
rqst.command_id = SURFACE_HID_CID_GET_FEATURE_REPORT;
146+
rqst.flags = 0;
147+
rqst.length = sizeof(rprt_id);
148+
rqst.payload = &rprt_id;
149+
150+
rsp.capacity = len;
151+
rsp.length = 0;
152+
rsp.pointer = buf;
153+
154+
return ssam_retry(ssam_request_sync_onstack, shid->ctrl, &rqst, &rsp, sizeof(rprt_id));
155+
}
156+
157+
static u32 ssam_hid_event_fn(struct ssam_event_notifier *nf, const struct ssam_event *event)
158+
{
159+
struct surface_hid_device *shid = container_of(nf, struct surface_hid_device, notif);
160+
161+
if (event->command_id != 0x00)
162+
return 0;
163+
164+
hid_input_report(shid->hid, HID_INPUT_REPORT, (u8 *)&event->data[0], event->length, 0);
165+
return SSAM_NOTIF_HANDLED;
166+
}
167+
168+
169+
/* -- Transport driver. ----------------------------------------------------- */
170+
171+
static int shid_output_report(struct surface_hid_device *shid, u8 rprt_id, u8 *buf, size_t len)
172+
{
173+
int status;
174+
175+
status = ssam_hid_set_raw_report(shid, rprt_id, false, buf, len);
176+
return status >= 0 ? len : status;
177+
}
178+
179+
static int shid_get_feature_report(struct surface_hid_device *shid, u8 rprt_id, u8 *buf, size_t len)
180+
{
181+
int status;
182+
183+
status = ssam_hid_get_raw_report(shid, rprt_id, buf, len);
184+
return status >= 0 ? len : status;
185+
}
186+
187+
static int shid_set_feature_report(struct surface_hid_device *shid, u8 rprt_id, u8 *buf, size_t len)
188+
{
189+
int status;
190+
191+
status = ssam_hid_set_raw_report(shid, rprt_id, true, buf, len);
192+
return status >= 0 ? len : status;
193+
}
194+
195+
196+
/* -- Driver setup. --------------------------------------------------------- */
197+
198+
static int surface_hid_probe(struct ssam_device *sdev)
199+
{
200+
struct surface_hid_device *shid;
201+
202+
shid = devm_kzalloc(&sdev->dev, sizeof(*shid), GFP_KERNEL);
203+
if (!shid)
204+
return -ENOMEM;
205+
206+
shid->dev = &sdev->dev;
207+
shid->ctrl = sdev->ctrl;
208+
shid->uid = sdev->uid;
209+
210+
shid->notif.base.priority = 1;
211+
shid->notif.base.fn = ssam_hid_event_fn;
212+
shid->notif.event.reg = SSAM_EVENT_REGISTRY_REG;
213+
shid->notif.event.id.target_category = sdev->uid.category;
214+
shid->notif.event.id.instance = sdev->uid.instance;
215+
shid->notif.event.mask = SSAM_EVENT_MASK_STRICT;
216+
shid->notif.event.flags = 0;
217+
218+
shid->ops.get_descriptor = ssam_hid_get_descriptor;
219+
shid->ops.output_report = shid_output_report;
220+
shid->ops.get_feature_report = shid_get_feature_report;
221+
shid->ops.set_feature_report = shid_set_feature_report;
222+
223+
ssam_device_set_drvdata(sdev, shid);
224+
return surface_hid_device_add(shid);
225+
}
226+
227+
static void surface_hid_remove(struct ssam_device *sdev)
228+
{
229+
surface_hid_device_destroy(ssam_device_get_drvdata(sdev));
230+
}
231+
232+
static const struct ssam_device_id surface_hid_match[] = {
233+
{ SSAM_SDEV(HID, 0x02, SSAM_ANY_IID, 0x00) },
234+
{ },
235+
};
236+
MODULE_DEVICE_TABLE(ssam, surface_hid_match);
237+
238+
static struct ssam_device_driver surface_hid_driver = {
239+
.probe = surface_hid_probe,
240+
.remove = surface_hid_remove,
241+
.match_table = surface_hid_match,
242+
.driver = {
243+
.name = "surface_hid",
244+
.pm = &surface_hid_pm_ops,
245+
.probe_type = PROBE_PREFER_ASYNCHRONOUS,
246+
},
247+
};
248+
module_ssam_device_driver(surface_hid_driver);
249+
250+
MODULE_AUTHOR("Blaž Hrastnik <[email protected]>");
251+
MODULE_AUTHOR("Maximilian Luz <[email protected]>");
252+
MODULE_DESCRIPTION("HID transport driver for Surface System Aggregator Module");
253+
MODULE_LICENSE("GPL");

0 commit comments

Comments
 (0)