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
24static 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
39static 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
78static 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
102static 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
109static 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
126static int surface_hid_open(struct hid_device *hid)
127{
128	return 0;
129}
130
131static void surface_hid_close(struct hid_device *hid)
132{
133}
134
135static 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
157static 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
177static 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
189int 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}
226EXPORT_SYMBOL_GPL(surface_hid_device_add);
227
228void surface_hid_device_destroy(struct surface_hid_device *shid)
229{
230	hid_destroy_device(shid->hid);
231}
232EXPORT_SYMBOL_GPL(surface_hid_device_destroy);
233
234
235/* -- PM ops. --------------------------------------------------------------- */
236
237#ifdef CONFIG_PM_SLEEP
238
239static 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
246static 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
253static 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
260static 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
267static 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
274const 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};
282EXPORT_SYMBOL_GPL(surface_hid_pm_ops);
283
284#else /* CONFIG_PM_SLEEP */
285
286const struct dev_pm_ops surface_hid_pm_ops = { };
287EXPORT_SYMBOL_GPL(surface_hid_pm_ops);
288
289#endif /* CONFIG_PM_SLEEP */
290
291MODULE_AUTHOR("Maximilian Luz <luzmaximilian@gmail.com>");
292MODULE_DESCRIPTION("HID transport driver core for Surface System Aggregator Module");
293MODULE_LICENSE("GPL");
294