brintos

brintos / linux-shallow public Read only

0
0
Text · 7.7 KiB · 6690c24 Raw
294 lines · c
1// SPDX-License-Identifier: GPL-2.0+2/*3 * Common/core components for the Surface System Aggregator Module (SSAM) HID4 * transport driver. Provides support for integrated HID devices on Microsoft5 * 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 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 be28	 * 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. This116	 * also includes unregistration of HID events, so we need to check this117	 * here. Only if the device has not been marked as hot-removed, we can118	 * 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_SLEEP238 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