xref: /linux/drivers/hid/surface-hid/surface_hid_core.c (revision 7ec462100ef9142344ddbf86f2c3008b97acddbe)
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 <linux/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 
surface_hid_is_hot_removed(struct surface_hid_device * shid)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(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 
surface_hid_load_hid_descriptor(struct surface_hid_device * shid)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 
surface_hid_load_device_attributes(struct surface_hid_device * shid)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(&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 
surface_hid_start(struct hid_device * hid)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(shid->ctrl, &shid->notif);
107 }
108 
surface_hid_stop(struct hid_device * hid)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(shid->ctrl, &shid->notif, !hot_removed);
124 }
125 
surface_hid_open(struct hid_device * hid)126 static int surface_hid_open(struct hid_device *hid)
127 {
128 	return 0;
129 }
130 
surface_hid_close(struct hid_device * hid)131 static void surface_hid_close(struct hid_device *hid)
132 {
133 }
134 
surface_hid_parse(struct hid_device * hid)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(&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(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, buf, len);
152 
153 	kfree(buf);
154 	return status;
155 }
156 
surface_hid_raw_request(struct hid_device * hid,unsigned char reportnum,u8 * buf,size_t len,unsigned char rtype,int reqtype)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 
surface_hid_device_add(struct surface_hid_device * shid)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(shid->hid))
203 		return PTR_ERR(shid->hid);
204 
205 	shid->hid->dev.parent = shid->dev;
206 	shid->hid->bus = BUS_HOST;
207 	shid->hid->vendor = get_unaligned_le16(&shid->attrs.vendor);
208 	shid->hid->product = get_unaligned_le16(&shid->attrs.product);
209 	shid->hid->version = get_unaligned_le16(&shid->hid_desc.hid_version);
210 	shid->hid->country = shid->hid_desc.country_code;
211 
212 	snprintf(shid->hid->name, sizeof(shid->hid->name), "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 
surface_hid_device_destroy(struct surface_hid_device * shid)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 
surface_hid_suspend(struct device * dev)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(d->hid, PMSG_SUSPEND);
244 }
245 
surface_hid_resume(struct device * dev)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(d->hid);
251 }
252 
surface_hid_freeze(struct device * dev)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(d->hid, PMSG_FREEZE);
258 }
259 
surface_hid_poweroff(struct device * dev)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(d->hid, PMSG_HIBERNATE);
265 }
266 
surface_hid_restore(struct device * dev)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(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