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 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 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 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 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 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 126 static int surface_hid_open(struct hid_device *hid) 127 { 128 return 0; 129 } 130 131 static void surface_hid_close(struct hid_device *hid) 132 { 133 } 134 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 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 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 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 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 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 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 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 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