1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | /* |
3 | * Common/core components for the Surface System Aggregator Module (SSAM) HID |
4 | * transport driver. Provides support for integrated HID devices on Microsoft |
5 | * Surface models. |
6 | * |
7 | * Copyright (C) 2019-2021 Maximilian Luz <luzmaximilian@gmail.com> |
8 | */ |
9 | |
10 | #include <asm/unaligned.h> |
11 | #include <linux/hid.h> |
12 | #include <linux/kernel.h> |
13 | #include <linux/module.h> |
14 | #include <linux/types.h> |
15 | #include <linux/usb/ch9.h> |
16 | |
17 | #include <linux/surface_aggregator/controller.h> |
18 | |
19 | #include "surface_hid_core.h" |
20 | |
21 | |
22 | /* -- Utility functions. ---------------------------------------------------- */ |
23 | |
24 | static bool surface_hid_is_hot_removed(struct surface_hid_device *shid) |
25 | { |
26 | /* |
27 | * Non-ssam client devices, i.e. platform client devices, cannot be |
28 | * hot-removed. |
29 | */ |
30 | if (!is_ssam_device(d: shid->dev)) |
31 | return false; |
32 | |
33 | return ssam_device_is_hot_removed(to_ssam_device(shid->dev)); |
34 | } |
35 | |
36 | |
37 | /* -- Device descriptor access. --------------------------------------------- */ |
38 | |
39 | static int surface_hid_load_hid_descriptor(struct surface_hid_device *shid) |
40 | { |
41 | int status; |
42 | |
43 | if (surface_hid_is_hot_removed(shid)) |
44 | return -ENODEV; |
45 | |
46 | status = shid->ops.get_descriptor(shid, SURFACE_HID_DESC_HID, |
47 | (u8 *)&shid->hid_desc, sizeof(shid->hid_desc)); |
48 | if (status) |
49 | return status; |
50 | |
51 | if (shid->hid_desc.desc_len != sizeof(shid->hid_desc)) { |
52 | dev_err(shid->dev, "unexpected HID descriptor length: got %u, expected %zu\n" , |
53 | shid->hid_desc.desc_len, sizeof(shid->hid_desc)); |
54 | return -EPROTO; |
55 | } |
56 | |
57 | if (shid->hid_desc.desc_type != HID_DT_HID) { |
58 | dev_err(shid->dev, "unexpected HID descriptor type: got %#04x, expected %#04x\n" , |
59 | shid->hid_desc.desc_type, HID_DT_HID); |
60 | return -EPROTO; |
61 | } |
62 | |
63 | if (shid->hid_desc.num_descriptors != 1) { |
64 | dev_err(shid->dev, "unexpected number of descriptors: got %u, expected 1\n" , |
65 | shid->hid_desc.num_descriptors); |
66 | return -EPROTO; |
67 | } |
68 | |
69 | if (shid->hid_desc.report_desc_type != HID_DT_REPORT) { |
70 | dev_err(shid->dev, "unexpected report descriptor type: got %#04x, expected %#04x\n" , |
71 | shid->hid_desc.report_desc_type, HID_DT_REPORT); |
72 | return -EPROTO; |
73 | } |
74 | |
75 | return 0; |
76 | } |
77 | |
78 | static int surface_hid_load_device_attributes(struct surface_hid_device *shid) |
79 | { |
80 | int status; |
81 | |
82 | if (surface_hid_is_hot_removed(shid)) |
83 | return -ENODEV; |
84 | |
85 | status = shid->ops.get_descriptor(shid, SURFACE_HID_DESC_ATTRS, |
86 | (u8 *)&shid->attrs, sizeof(shid->attrs)); |
87 | if (status) |
88 | return status; |
89 | |
90 | if (get_unaligned_le32(p: &shid->attrs.length) != sizeof(shid->attrs)) { |
91 | dev_err(shid->dev, "unexpected attribute length: got %u, expected %zu\n" , |
92 | get_unaligned_le32(&shid->attrs.length), sizeof(shid->attrs)); |
93 | return -EPROTO; |
94 | } |
95 | |
96 | return 0; |
97 | } |
98 | |
99 | |
100 | /* -- Transport driver (common). -------------------------------------------- */ |
101 | |
102 | static int surface_hid_start(struct hid_device *hid) |
103 | { |
104 | struct surface_hid_device *shid = hid->driver_data; |
105 | |
106 | return ssam_notifier_register(ctrl: shid->ctrl, n: &shid->notif); |
107 | } |
108 | |
109 | static void surface_hid_stop(struct hid_device *hid) |
110 | { |
111 | struct surface_hid_device *shid = hid->driver_data; |
112 | bool hot_removed; |
113 | |
114 | /* |
115 | * Communication may fail for devices that have been hot-removed. This |
116 | * also includes unregistration of HID events, so we need to check this |
117 | * here. Only if the device has not been marked as hot-removed, we can |
118 | * safely disable events. |
119 | */ |
120 | hot_removed = surface_hid_is_hot_removed(shid); |
121 | |
122 | /* Note: This call will log errors for us, so ignore them here. */ |
123 | __ssam_notifier_unregister(ctrl: shid->ctrl, n: &shid->notif, disable: !hot_removed); |
124 | } |
125 | |
126 | static int surface_hid_open(struct hid_device *hid) |
127 | { |
128 | return 0; |
129 | } |
130 | |
131 | static void surface_hid_close(struct hid_device *hid) |
132 | { |
133 | } |
134 | |
135 | static int surface_hid_parse(struct hid_device *hid) |
136 | { |
137 | struct surface_hid_device *shid = hid->driver_data; |
138 | size_t len = get_unaligned_le16(p: &shid->hid_desc.report_desc_len); |
139 | u8 *buf; |
140 | int status; |
141 | |
142 | if (surface_hid_is_hot_removed(shid)) |
143 | return -ENODEV; |
144 | |
145 | buf = kzalloc(size: len, GFP_KERNEL); |
146 | if (!buf) |
147 | return -ENOMEM; |
148 | |
149 | status = shid->ops.get_descriptor(shid, SURFACE_HID_DESC_REPORT, buf, len); |
150 | if (!status) |
151 | status = hid_parse_report(hid, start: buf, size: len); |
152 | |
153 | kfree(objp: buf); |
154 | return status; |
155 | } |
156 | |
157 | static int surface_hid_raw_request(struct hid_device *hid, unsigned char reportnum, u8 *buf, |
158 | size_t len, unsigned char rtype, int reqtype) |
159 | { |
160 | struct surface_hid_device *shid = hid->driver_data; |
161 | |
162 | if (surface_hid_is_hot_removed(shid)) |
163 | return -ENODEV; |
164 | |
165 | if (rtype == HID_OUTPUT_REPORT && reqtype == HID_REQ_SET_REPORT) |
166 | return shid->ops.output_report(shid, reportnum, buf, len); |
167 | |
168 | else if (rtype == HID_FEATURE_REPORT && reqtype == HID_REQ_GET_REPORT) |
169 | return shid->ops.get_feature_report(shid, reportnum, buf, len); |
170 | |
171 | else if (rtype == HID_FEATURE_REPORT && reqtype == HID_REQ_SET_REPORT) |
172 | return shid->ops.set_feature_report(shid, reportnum, buf, len); |
173 | |
174 | return -EIO; |
175 | } |
176 | |
177 | static const struct hid_ll_driver surface_hid_ll_driver = { |
178 | .start = surface_hid_start, |
179 | .stop = surface_hid_stop, |
180 | .open = surface_hid_open, |
181 | .close = surface_hid_close, |
182 | .parse = surface_hid_parse, |
183 | .raw_request = surface_hid_raw_request, |
184 | }; |
185 | |
186 | |
187 | /* -- Common device setup. -------------------------------------------------- */ |
188 | |
189 | int surface_hid_device_add(struct surface_hid_device *shid) |
190 | { |
191 | int status; |
192 | |
193 | status = surface_hid_load_hid_descriptor(shid); |
194 | if (status) |
195 | return status; |
196 | |
197 | status = surface_hid_load_device_attributes(shid); |
198 | if (status) |
199 | return status; |
200 | |
201 | shid->hid = hid_allocate_device(); |
202 | if (IS_ERR(ptr: shid->hid)) |
203 | return PTR_ERR(ptr: shid->hid); |
204 | |
205 | shid->hid->dev.parent = shid->dev; |
206 | shid->hid->bus = BUS_HOST; |
207 | shid->hid->vendor = get_unaligned_le16(p: &shid->attrs.vendor); |
208 | shid->hid->product = get_unaligned_le16(p: &shid->attrs.product); |
209 | shid->hid->version = get_unaligned_le16(p: &shid->hid_desc.hid_version); |
210 | shid->hid->country = shid->hid_desc.country_code; |
211 | |
212 | snprintf(buf: shid->hid->name, size: sizeof(shid->hid->name), fmt: "Microsoft Surface %04X:%04X" , |
213 | shid->hid->vendor, shid->hid->product); |
214 | |
215 | strscpy(shid->hid->phys, dev_name(shid->dev), sizeof(shid->hid->phys)); |
216 | |
217 | shid->hid->driver_data = shid; |
218 | shid->hid->ll_driver = &surface_hid_ll_driver; |
219 | |
220 | status = hid_add_device(shid->hid); |
221 | if (status) |
222 | hid_destroy_device(shid->hid); |
223 | |
224 | return status; |
225 | } |
226 | EXPORT_SYMBOL_GPL(surface_hid_device_add); |
227 | |
228 | void surface_hid_device_destroy(struct surface_hid_device *shid) |
229 | { |
230 | hid_destroy_device(shid->hid); |
231 | } |
232 | EXPORT_SYMBOL_GPL(surface_hid_device_destroy); |
233 | |
234 | |
235 | /* -- PM ops. --------------------------------------------------------------- */ |
236 | |
237 | #ifdef CONFIG_PM_SLEEP |
238 | |
239 | static int surface_hid_suspend(struct device *dev) |
240 | { |
241 | struct surface_hid_device *d = dev_get_drvdata(dev); |
242 | |
243 | return hid_driver_suspend(hdev: d->hid, PMSG_SUSPEND); |
244 | } |
245 | |
246 | static int surface_hid_resume(struct device *dev) |
247 | { |
248 | struct surface_hid_device *d = dev_get_drvdata(dev); |
249 | |
250 | return hid_driver_resume(hdev: d->hid); |
251 | } |
252 | |
253 | static int surface_hid_freeze(struct device *dev) |
254 | { |
255 | struct surface_hid_device *d = dev_get_drvdata(dev); |
256 | |
257 | return hid_driver_suspend(hdev: d->hid, PMSG_FREEZE); |
258 | } |
259 | |
260 | static int surface_hid_poweroff(struct device *dev) |
261 | { |
262 | struct surface_hid_device *d = dev_get_drvdata(dev); |
263 | |
264 | return hid_driver_suspend(hdev: d->hid, PMSG_HIBERNATE); |
265 | } |
266 | |
267 | static int surface_hid_restore(struct device *dev) |
268 | { |
269 | struct surface_hid_device *d = dev_get_drvdata(dev); |
270 | |
271 | return hid_driver_reset_resume(hdev: d->hid); |
272 | } |
273 | |
274 | const struct dev_pm_ops surface_hid_pm_ops = { |
275 | .freeze = surface_hid_freeze, |
276 | .thaw = surface_hid_resume, |
277 | .suspend = surface_hid_suspend, |
278 | .resume = surface_hid_resume, |
279 | .poweroff = surface_hid_poweroff, |
280 | .restore = surface_hid_restore, |
281 | }; |
282 | EXPORT_SYMBOL_GPL(surface_hid_pm_ops); |
283 | |
284 | #else /* CONFIG_PM_SLEEP */ |
285 | |
286 | const struct dev_pm_ops surface_hid_pm_ops = { }; |
287 | EXPORT_SYMBOL_GPL(surface_hid_pm_ops); |
288 | |
289 | #endif /* CONFIG_PM_SLEEP */ |
290 | |
291 | MODULE_AUTHOR("Maximilian Luz <luzmaximilian@gmail.com>" ); |
292 | MODULE_DESCRIPTION("HID transport driver core for Surface System Aggregator Module" ); |
293 | MODULE_LICENSE("GPL" ); |
294 | |