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