surface_hid_core.c (6893B)
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 24static 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 60static 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 81static 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 88static 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 96static int surface_hid_open(struct hid_device *hid) 97{ 98 return 0; 99} 100 101static void surface_hid_close(struct hid_device *hid) 102{ 103} 104 105static 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 124static 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 141static 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 153int 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} 190EXPORT_SYMBOL_GPL(surface_hid_device_add); 191 192void surface_hid_device_destroy(struct surface_hid_device *shid) 193{ 194 hid_destroy_device(shid->hid); 195} 196EXPORT_SYMBOL_GPL(surface_hid_device_destroy); 197 198 199/* -- PM ops. --------------------------------------------------------------- */ 200 201#ifdef CONFIG_PM_SLEEP 202 203static int surface_hid_suspend(struct device *dev) 204{ 205 struct surface_hid_device *d = dev_get_drvdata(dev); 206 207 return hid_driver_suspend(d->hid, PMSG_SUSPEND); 208} 209 210static int surface_hid_resume(struct device *dev) 211{ 212 struct surface_hid_device *d = dev_get_drvdata(dev); 213 214 return hid_driver_resume(d->hid); 215} 216 217static int surface_hid_freeze(struct device *dev) 218{ 219 struct surface_hid_device *d = dev_get_drvdata(dev); 220 221 return hid_driver_suspend(d->hid, PMSG_FREEZE); 222} 223 224static int surface_hid_poweroff(struct device *dev) 225{ 226 struct surface_hid_device *d = dev_get_drvdata(dev); 227 228 return hid_driver_suspend(d->hid, PMSG_HIBERNATE); 229} 230 231static int surface_hid_restore(struct device *dev) 232{ 233 struct surface_hid_device *d = dev_get_drvdata(dev); 234 235 return hid_driver_reset_resume(d->hid); 236} 237 238const struct dev_pm_ops surface_hid_pm_ops = { 239 .freeze = surface_hid_freeze, 240 .thaw = surface_hid_resume, 241 .suspend = surface_hid_suspend, 242 .resume = surface_hid_resume, 243 .poweroff = surface_hid_poweroff, 244 .restore = surface_hid_restore, 245}; 246EXPORT_SYMBOL_GPL(surface_hid_pm_ops); 247 248#else /* CONFIG_PM_SLEEP */ 249 250const struct dev_pm_ops surface_hid_pm_ops = { }; 251EXPORT_SYMBOL_GPL(surface_hid_pm_ops); 252 253#endif /* CONFIG_PM_SLEEP */ 254 255MODULE_AUTHOR("Maximilian Luz <luzmaximilian@gmail.com>"); 256MODULE_DESCRIPTION("HID transport driver core for Surface System Aggregator Module"); 257MODULE_LICENSE("GPL");