cachepc-linux

Fork of AMDESE/linux with modifications for CachePC side-channel attack
git clone https://git.sinitax.com/sinitax/cachepc-linux
Log | Files | Refs | README | LICENSE | sfeed.txt

surface_hid.c (6963B)


      1// SPDX-License-Identifier: GPL-2.0+
      2/*
      3 * Surface System Aggregator Module (SSAM) HID transport driver for the
      4 * generic HID interface (HID/TC=0x15 subsystem). Provides support for
      5 * integrated HID devices on Surface Laptop 3, Book 3, and later.
      6 *
      7 * Copyright (C) 2019-2021 Blaž Hrastnik <blaz@mxxn.io>,
      8 *                         Maximilian Luz <luzmaximilian@gmail.com>
      9 */
     10
     11#include <asm/unaligned.h>
     12#include <linux/hid.h>
     13#include <linux/kernel.h>
     14#include <linux/module.h>
     15#include <linux/types.h>
     16
     17#include <linux/surface_aggregator/controller.h>
     18#include <linux/surface_aggregator/device.h>
     19
     20#include "surface_hid_core.h"
     21
     22
     23/* -- SAM interface. -------------------------------------------------------- */
     24
     25struct surface_hid_buffer_slice {
     26	__u8 entry;
     27	__le32 offset;
     28	__le32 length;
     29	__u8 end;
     30	__u8 data[];
     31} __packed;
     32
     33static_assert(sizeof(struct surface_hid_buffer_slice) == 10);
     34
     35enum surface_hid_cid {
     36	SURFACE_HID_CID_OUTPUT_REPORT      = 0x01,
     37	SURFACE_HID_CID_GET_FEATURE_REPORT = 0x02,
     38	SURFACE_HID_CID_SET_FEATURE_REPORT = 0x03,
     39	SURFACE_HID_CID_GET_DESCRIPTOR     = 0x04,
     40};
     41
     42static int ssam_hid_get_descriptor(struct surface_hid_device *shid, u8 entry, u8 *buf, size_t len)
     43{
     44	u8 buffer[sizeof(struct surface_hid_buffer_slice) + 0x76];
     45	struct surface_hid_buffer_slice *slice;
     46	struct ssam_request rqst;
     47	struct ssam_response rsp;
     48	u32 buffer_len, offset, length;
     49	int status;
     50
     51	/*
     52	 * Note: The 0x76 above has been chosen because that's what's used by
     53	 * the Windows driver. Together with the header, this leads to a 128
     54	 * byte payload in total.
     55	 */
     56
     57	buffer_len = ARRAY_SIZE(buffer) - sizeof(struct surface_hid_buffer_slice);
     58
     59	rqst.target_category = shid->uid.category;
     60	rqst.target_id = shid->uid.target;
     61	rqst.command_id = SURFACE_HID_CID_GET_DESCRIPTOR;
     62	rqst.instance_id = shid->uid.instance;
     63	rqst.flags = SSAM_REQUEST_HAS_RESPONSE;
     64	rqst.length = sizeof(struct surface_hid_buffer_slice);
     65	rqst.payload = buffer;
     66
     67	rsp.capacity = ARRAY_SIZE(buffer);
     68	rsp.pointer = buffer;
     69
     70	slice = (struct surface_hid_buffer_slice *)buffer;
     71	slice->entry = entry;
     72	slice->end = 0;
     73
     74	offset = 0;
     75	length = buffer_len;
     76
     77	while (!slice->end && offset < len) {
     78		put_unaligned_le32(offset, &slice->offset);
     79		put_unaligned_le32(length, &slice->length);
     80
     81		rsp.length = 0;
     82
     83		status = ssam_retry(ssam_request_sync_onstack, shid->ctrl, &rqst, &rsp,
     84				    sizeof(*slice));
     85		if (status)
     86			return status;
     87
     88		offset = get_unaligned_le32(&slice->offset);
     89		length = get_unaligned_le32(&slice->length);
     90
     91		/* Don't mess stuff up in case we receive garbage. */
     92		if (length > buffer_len || offset > len)
     93			return -EPROTO;
     94
     95		if (offset + length > len)
     96			length = len - offset;
     97
     98		memcpy(buf + offset, &slice->data[0], length);
     99
    100		offset += length;
    101		length = buffer_len;
    102	}
    103
    104	if (offset != len) {
    105		dev_err(shid->dev, "unexpected descriptor length: got %u, expected %zu\n",
    106			offset, len);
    107		return -EPROTO;
    108	}
    109
    110	return 0;
    111}
    112
    113static int ssam_hid_set_raw_report(struct surface_hid_device *shid, u8 rprt_id, bool feature,
    114				   u8 *buf, size_t len)
    115{
    116	struct ssam_request rqst;
    117	u8 cid;
    118
    119	if (feature)
    120		cid = SURFACE_HID_CID_SET_FEATURE_REPORT;
    121	else
    122		cid = SURFACE_HID_CID_OUTPUT_REPORT;
    123
    124	rqst.target_category = shid->uid.category;
    125	rqst.target_id = shid->uid.target;
    126	rqst.instance_id = shid->uid.instance;
    127	rqst.command_id = cid;
    128	rqst.flags = 0;
    129	rqst.length = len;
    130	rqst.payload = buf;
    131
    132	buf[0] = rprt_id;
    133
    134	return ssam_retry(ssam_request_sync, shid->ctrl, &rqst, NULL);
    135}
    136
    137static int ssam_hid_get_raw_report(struct surface_hid_device *shid, u8 rprt_id, u8 *buf, size_t len)
    138{
    139	struct ssam_request rqst;
    140	struct ssam_response rsp;
    141
    142	rqst.target_category = shid->uid.category;
    143	rqst.target_id = shid->uid.target;
    144	rqst.instance_id = shid->uid.instance;
    145	rqst.command_id = SURFACE_HID_CID_GET_FEATURE_REPORT;
    146	rqst.flags = SSAM_REQUEST_HAS_RESPONSE;
    147	rqst.length = sizeof(rprt_id);
    148	rqst.payload = &rprt_id;
    149
    150	rsp.capacity = len;
    151	rsp.length = 0;
    152	rsp.pointer = buf;
    153
    154	return ssam_retry(ssam_request_sync_onstack, shid->ctrl, &rqst, &rsp, sizeof(rprt_id));
    155}
    156
    157static u32 ssam_hid_event_fn(struct ssam_event_notifier *nf, const struct ssam_event *event)
    158{
    159	struct surface_hid_device *shid = container_of(nf, struct surface_hid_device, notif);
    160
    161	if (event->command_id != 0x00)
    162		return 0;
    163
    164	hid_input_report(shid->hid, HID_INPUT_REPORT, (u8 *)&event->data[0], event->length, 0);
    165	return SSAM_NOTIF_HANDLED;
    166}
    167
    168
    169/* -- Transport driver. ----------------------------------------------------- */
    170
    171static int shid_output_report(struct surface_hid_device *shid, u8 rprt_id, u8 *buf, size_t len)
    172{
    173	int status;
    174
    175	status = ssam_hid_set_raw_report(shid, rprt_id, false, buf, len);
    176	return status >= 0 ? len : status;
    177}
    178
    179static int shid_get_feature_report(struct surface_hid_device *shid, u8 rprt_id, u8 *buf, size_t len)
    180{
    181	int status;
    182
    183	status = ssam_hid_get_raw_report(shid, rprt_id, buf, len);
    184	return status >= 0 ? len : status;
    185}
    186
    187static int shid_set_feature_report(struct surface_hid_device *shid, u8 rprt_id, u8 *buf, size_t len)
    188{
    189	int status;
    190
    191	status = ssam_hid_set_raw_report(shid, rprt_id, true, buf, len);
    192	return status >= 0 ? len : status;
    193}
    194
    195
    196/* -- Driver setup. --------------------------------------------------------- */
    197
    198static int surface_hid_probe(struct ssam_device *sdev)
    199{
    200	struct surface_hid_device *shid;
    201
    202	shid = devm_kzalloc(&sdev->dev, sizeof(*shid), GFP_KERNEL);
    203	if (!shid)
    204		return -ENOMEM;
    205
    206	shid->dev = &sdev->dev;
    207	shid->ctrl = sdev->ctrl;
    208	shid->uid = sdev->uid;
    209
    210	shid->notif.base.priority = 1;
    211	shid->notif.base.fn = ssam_hid_event_fn;
    212	shid->notif.event.reg = SSAM_EVENT_REGISTRY_REG(sdev->uid.target);
    213	shid->notif.event.id.target_category = sdev->uid.category;
    214	shid->notif.event.id.instance = sdev->uid.instance;
    215	shid->notif.event.mask = SSAM_EVENT_MASK_STRICT;
    216	shid->notif.event.flags = 0;
    217
    218	shid->ops.get_descriptor = ssam_hid_get_descriptor;
    219	shid->ops.output_report = shid_output_report;
    220	shid->ops.get_feature_report = shid_get_feature_report;
    221	shid->ops.set_feature_report = shid_set_feature_report;
    222
    223	ssam_device_set_drvdata(sdev, shid);
    224	return surface_hid_device_add(shid);
    225}
    226
    227static void surface_hid_remove(struct ssam_device *sdev)
    228{
    229	surface_hid_device_destroy(ssam_device_get_drvdata(sdev));
    230}
    231
    232static const struct ssam_device_id surface_hid_match[] = {
    233	{ SSAM_SDEV(HID, SSAM_ANY_TID, SSAM_ANY_IID, 0x00) },
    234	{ },
    235};
    236MODULE_DEVICE_TABLE(ssam, surface_hid_match);
    237
    238static struct ssam_device_driver surface_hid_driver = {
    239	.probe = surface_hid_probe,
    240	.remove = surface_hid_remove,
    241	.match_table = surface_hid_match,
    242	.driver = {
    243		.name = "surface_hid",
    244		.pm = &surface_hid_pm_ops,
    245		.probe_type = PROBE_PREFER_ASYNCHRONOUS,
    246	},
    247};
    248module_ssam_device_driver(surface_hid_driver);
    249
    250MODULE_AUTHOR("Blaž Hrastnik <blaz@mxxn.io>");
    251MODULE_AUTHOR("Maximilian Luz <luzmaximilian@gmail.com>");
    252MODULE_DESCRIPTION("HID transport driver for Surface System Aggregator Module");
    253MODULE_LICENSE("GPL");