linux/drivers/hid/surface-hid/surface_hid_core.c
<<
>>
Prefs
   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        if (d->hid->driver && d->hid->driver->suspend)
 208                return d->hid->driver->suspend(d->hid, PMSG_SUSPEND);
 209
 210        return 0;
 211}
 212
 213static 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
 223static 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
 233static 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
 243static 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
 253const 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};
 261EXPORT_SYMBOL_GPL(surface_hid_pm_ops);
 262
 263#else /* CONFIG_PM_SLEEP */
 264
 265const struct dev_pm_ops surface_hid_pm_ops = { };
 266EXPORT_SYMBOL_GPL(surface_hid_pm_ops);
 267
 268#endif /* CONFIG_PM_SLEEP */
 269
 270MODULE_AUTHOR("Maximilian Luz <luzmaximilian@gmail.com>");
 271MODULE_DESCRIPTION("HID transport driver core for Surface System Aggregator Module");
 272MODULE_LICENSE("GPL");
 273