xref: /linux/drivers/hid/surface-hid/surface_hid_core.c (revision bdd1a21b52557ea8f61d0a5dc2f77151b576eb70)
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 /* -- Device descriptor access. --------------------------------------------- */
23 
24 static int surface_hid_load_hid_descriptor(struct surface_hid_device *shid)
25 {
26 	int status;
27 
28 	status = shid->ops.get_descriptor(shid, SURFACE_HID_DESC_HID,
29 			(u8 *)&shid->hid_desc, sizeof(shid->hid_desc));
30 	if (status)
31 		return status;
32 
33 	if (shid->hid_desc.desc_len != sizeof(shid->hid_desc)) {
34 		dev_err(shid->dev, "unexpected HID descriptor length: got %u, expected %zu\n",
35 			shid->hid_desc.desc_len, sizeof(shid->hid_desc));
36 		return -EPROTO;
37 	}
38 
39 	if (shid->hid_desc.desc_type != HID_DT_HID) {
40 		dev_err(shid->dev, "unexpected HID descriptor type: got %#04x, expected %#04x\n",
41 			shid->hid_desc.desc_type, HID_DT_HID);
42 		return -EPROTO;
43 	}
44 
45 	if (shid->hid_desc.num_descriptors != 1) {
46 		dev_err(shid->dev, "unexpected number of descriptors: got %u, expected 1\n",
47 			shid->hid_desc.num_descriptors);
48 		return -EPROTO;
49 	}
50 
51 	if (shid->hid_desc.report_desc_type != HID_DT_REPORT) {
52 		dev_err(shid->dev, "unexpected report descriptor type: got %#04x, expected %#04x\n",
53 			shid->hid_desc.report_desc_type, HID_DT_REPORT);
54 		return -EPROTO;
55 	}
56 
57 	return 0;
58 }
59 
60 static int surface_hid_load_device_attributes(struct surface_hid_device *shid)
61 {
62 	int status;
63 
64 	status = shid->ops.get_descriptor(shid, SURFACE_HID_DESC_ATTRS,
65 			(u8 *)&shid->attrs, sizeof(shid->attrs));
66 	if (status)
67 		return status;
68 
69 	if (get_unaligned_le32(&shid->attrs.length) != sizeof(shid->attrs)) {
70 		dev_err(shid->dev, "unexpected attribute length: got %u, expected %zu\n",
71 			get_unaligned_le32(&shid->attrs.length), sizeof(shid->attrs));
72 		return -EPROTO;
73 	}
74 
75 	return 0;
76 }
77 
78 
79 /* -- Transport driver (common). -------------------------------------------- */
80 
81 static int surface_hid_start(struct hid_device *hid)
82 {
83 	struct surface_hid_device *shid = hid->driver_data;
84 
85 	return ssam_notifier_register(shid->ctrl, &shid->notif);
86 }
87 
88 static void surface_hid_stop(struct hid_device *hid)
89 {
90 	struct surface_hid_device *shid = hid->driver_data;
91 
92 	/* Note: This call will log errors for us, so ignore them here. */
93 	ssam_notifier_unregister(shid->ctrl, &shid->notif);
94 }
95 
96 static int surface_hid_open(struct hid_device *hid)
97 {
98 	return 0;
99 }
100 
101 static void surface_hid_close(struct hid_device *hid)
102 {
103 }
104 
105 static int surface_hid_parse(struct hid_device *hid)
106 {
107 	struct surface_hid_device *shid = hid->driver_data;
108 	size_t len = get_unaligned_le16(&shid->hid_desc.report_desc_len);
109 	u8 *buf;
110 	int status;
111 
112 	buf = kzalloc(len, GFP_KERNEL);
113 	if (!buf)
114 		return -ENOMEM;
115 
116 	status = shid->ops.get_descriptor(shid, SURFACE_HID_DESC_REPORT, buf, len);
117 	if (!status)
118 		status = hid_parse_report(hid, buf, len);
119 
120 	kfree(buf);
121 	return status;
122 }
123 
124 static int surface_hid_raw_request(struct hid_device *hid, unsigned char reportnum, u8 *buf,
125 				   size_t len, unsigned char rtype, int reqtype)
126 {
127 	struct surface_hid_device *shid = hid->driver_data;
128 
129 	if (rtype == HID_OUTPUT_REPORT && reqtype == HID_REQ_SET_REPORT)
130 		return shid->ops.output_report(shid, reportnum, buf, len);
131 
132 	else if (rtype == HID_FEATURE_REPORT && reqtype == HID_REQ_GET_REPORT)
133 		return shid->ops.get_feature_report(shid, reportnum, buf, len);
134 
135 	else if (rtype == HID_FEATURE_REPORT && reqtype == HID_REQ_SET_REPORT)
136 		return shid->ops.set_feature_report(shid, reportnum, buf, len);
137 
138 	return -EIO;
139 }
140 
141 static struct hid_ll_driver surface_hid_ll_driver = {
142 	.start       = surface_hid_start,
143 	.stop        = surface_hid_stop,
144 	.open        = surface_hid_open,
145 	.close       = surface_hid_close,
146 	.parse       = surface_hid_parse,
147 	.raw_request = surface_hid_raw_request,
148 };
149 
150 
151 /* -- Common device setup. -------------------------------------------------- */
152 
153 int surface_hid_device_add(struct surface_hid_device *shid)
154 {
155 	int status;
156 
157 	status = surface_hid_load_hid_descriptor(shid);
158 	if (status)
159 		return status;
160 
161 	status = surface_hid_load_device_attributes(shid);
162 	if (status)
163 		return status;
164 
165 	shid->hid = hid_allocate_device();
166 	if (IS_ERR(shid->hid))
167 		return PTR_ERR(shid->hid);
168 
169 	shid->hid->dev.parent = shid->dev;
170 	shid->hid->bus = BUS_HOST;
171 	shid->hid->vendor = get_unaligned_le16(&shid->attrs.vendor);
172 	shid->hid->product = get_unaligned_le16(&shid->attrs.product);
173 	shid->hid->version = get_unaligned_le16(&shid->hid_desc.hid_version);
174 	shid->hid->country = shid->hid_desc.country_code;
175 
176 	snprintf(shid->hid->name, sizeof(shid->hid->name), "Microsoft Surface %04X:%04X",
177 		 shid->hid->vendor, shid->hid->product);
178 
179 	strscpy(shid->hid->phys, dev_name(shid->dev), sizeof(shid->hid->phys));
180 
181 	shid->hid->driver_data = shid;
182 	shid->hid->ll_driver = &surface_hid_ll_driver;
183 
184 	status = hid_add_device(shid->hid);
185 	if (status)
186 		hid_destroy_device(shid->hid);
187 
188 	return status;
189 }
190 EXPORT_SYMBOL_GPL(surface_hid_device_add);
191 
192 void surface_hid_device_destroy(struct surface_hid_device *shid)
193 {
194 	hid_destroy_device(shid->hid);
195 }
196 EXPORT_SYMBOL_GPL(surface_hid_device_destroy);
197 
198 
199 /* -- PM ops. --------------------------------------------------------------- */
200 
201 #ifdef CONFIG_PM_SLEEP
202 
203 static int surface_hid_suspend(struct device *dev)
204 {
205 	struct surface_hid_device *d = dev_get_drvdata(dev);
206 
207 	if (d->hid->driver && d->hid->driver->suspend)
208 		return d->hid->driver->suspend(d->hid, PMSG_SUSPEND);
209 
210 	return 0;
211 }
212 
213 static int surface_hid_resume(struct device *dev)
214 {
215 	struct surface_hid_device *d = dev_get_drvdata(dev);
216 
217 	if (d->hid->driver && d->hid->driver->resume)
218 		return d->hid->driver->resume(d->hid);
219 
220 	return 0;
221 }
222 
223 static int surface_hid_freeze(struct device *dev)
224 {
225 	struct surface_hid_device *d = dev_get_drvdata(dev);
226 
227 	if (d->hid->driver && d->hid->driver->suspend)
228 		return d->hid->driver->suspend(d->hid, PMSG_FREEZE);
229 
230 	return 0;
231 }
232 
233 static int surface_hid_poweroff(struct device *dev)
234 {
235 	struct surface_hid_device *d = dev_get_drvdata(dev);
236 
237 	if (d->hid->driver && d->hid->driver->suspend)
238 		return d->hid->driver->suspend(d->hid, PMSG_HIBERNATE);
239 
240 	return 0;
241 }
242 
243 static int surface_hid_restore(struct device *dev)
244 {
245 	struct surface_hid_device *d = dev_get_drvdata(dev);
246 
247 	if (d->hid->driver && d->hid->driver->reset_resume)
248 		return d->hid->driver->reset_resume(d->hid);
249 
250 	return 0;
251 }
252 
253 const struct dev_pm_ops surface_hid_pm_ops = {
254 	.freeze   = surface_hid_freeze,
255 	.thaw     = surface_hid_resume,
256 	.suspend  = surface_hid_suspend,
257 	.resume   = surface_hid_resume,
258 	.poweroff = surface_hid_poweroff,
259 	.restore  = surface_hid_restore,
260 };
261 EXPORT_SYMBOL_GPL(surface_hid_pm_ops);
262 
263 #else /* CONFIG_PM_SLEEP */
264 
265 const struct dev_pm_ops surface_hid_pm_ops = { };
266 EXPORT_SYMBOL_GPL(surface_hid_pm_ops);
267 
268 #endif /* CONFIG_PM_SLEEP */
269 
270 MODULE_AUTHOR("Maximilian Luz <luzmaximilian@gmail.com>");
271 MODULE_DESCRIPTION("HID transport driver core for Surface System Aggregator Module");
272 MODULE_LICENSE("GPL");
273