mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-08-24 01:41:39 +00:00
![Maximilian Luz](/assets/img/avatar_default.png)
We currently have a struct ssam_request_sync and a function ssam_request_sync(). While this is valid C, there are some downsides to it. One of these is that current Sphinx versions (>= 3.0) cannot disambiguate between the two (see disucssion and pull request linked below). It instead emits a "WARNING: Duplicate C declaration" and links for the struct and function in the resulting documentation link to the same entry (i.e. both to either function or struct documentation) instead of their respective own entries. While we could just ignore that and wait for a fix, there's also a point to be made that the current naming can be somewhat confusing when searching (e.g. via grep) or trying to understand the levels of abstraction at play: We currently have struct ssam_request_sync and associated functions ssam_request_sync_[alloc|free|init|wait|...]() operating on this struct. However, function ssam_request_sync() is one abstraction level above this. Similarly, ssam_request_sync_with_buffer() is not a function operating on struct ssam_request_sync, but rather a sibling to ssam_request_sync(), both using the struct under the hood. Therefore, rename the top level request functions: ssam_request_sync() -> ssam_request_do_sync() ssam_request_sync_with_buffer() -> ssam_request_do_sync_with_buffer() ssam_request_sync_onstack() -> ssam_request_do_sync_onstack() Link: https://lore.kernel.org/all/085e0ada65c11da9303d07e70c510dc45f21315b.1656756450.git.mchehab@kernel.org/ Link: https://github.com/sphinx-doc/sphinx/pull/8313 Signed-off-by: Maximilian Luz <luzmaximilian@gmail.com> Link: https://lore.kernel.org/r/20221220175608.1436273-2-luzmaximilian@gmail.com Reviewed-by: Hans de Goede <hdegoede@redhat.com> Signed-off-by: Hans de Goede <hdegoede@redhat.com>
253 lines
6.8 KiB
C
253 lines
6.8 KiB
C
// SPDX-License-Identifier: GPL-2.0+
|
|
/*
|
|
* Surface System Aggregator Module (SSAM) HID transport driver for the
|
|
* generic HID interface (HID/TC=0x15 subsystem). Provides support for
|
|
* integrated HID devices on Surface Laptop 3, Book 3, and later.
|
|
*
|
|
* Copyright (C) 2019-2021 Blaž Hrastnik <blaz@mxxn.io>,
|
|
* Maximilian Luz <luzmaximilian@gmail.com>
|
|
*/
|
|
|
|
#include <asm/unaligned.h>
|
|
#include <linux/hid.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/module.h>
|
|
#include <linux/types.h>
|
|
|
|
#include <linux/surface_aggregator/controller.h>
|
|
#include <linux/surface_aggregator/device.h>
|
|
|
|
#include "surface_hid_core.h"
|
|
|
|
|
|
/* -- SAM interface. -------------------------------------------------------- */
|
|
|
|
struct surface_hid_buffer_slice {
|
|
__u8 entry;
|
|
__le32 offset;
|
|
__le32 length;
|
|
__u8 end;
|
|
__u8 data[];
|
|
} __packed;
|
|
|
|
static_assert(sizeof(struct surface_hid_buffer_slice) == 10);
|
|
|
|
enum surface_hid_cid {
|
|
SURFACE_HID_CID_OUTPUT_REPORT = 0x01,
|
|
SURFACE_HID_CID_GET_FEATURE_REPORT = 0x02,
|
|
SURFACE_HID_CID_SET_FEATURE_REPORT = 0x03,
|
|
SURFACE_HID_CID_GET_DESCRIPTOR = 0x04,
|
|
};
|
|
|
|
static int ssam_hid_get_descriptor(struct surface_hid_device *shid, u8 entry, u8 *buf, size_t len)
|
|
{
|
|
u8 buffer[sizeof(struct surface_hid_buffer_slice) + 0x76];
|
|
struct surface_hid_buffer_slice *slice;
|
|
struct ssam_request rqst;
|
|
struct ssam_response rsp;
|
|
u32 buffer_len, offset, length;
|
|
int status;
|
|
|
|
/*
|
|
* Note: The 0x76 above has been chosen because that's what's used by
|
|
* the Windows driver. Together with the header, this leads to a 128
|
|
* byte payload in total.
|
|
*/
|
|
|
|
buffer_len = ARRAY_SIZE(buffer) - sizeof(struct surface_hid_buffer_slice);
|
|
|
|
rqst.target_category = shid->uid.category;
|
|
rqst.target_id = shid->uid.target;
|
|
rqst.command_id = SURFACE_HID_CID_GET_DESCRIPTOR;
|
|
rqst.instance_id = shid->uid.instance;
|
|
rqst.flags = SSAM_REQUEST_HAS_RESPONSE;
|
|
rqst.length = sizeof(struct surface_hid_buffer_slice);
|
|
rqst.payload = buffer;
|
|
|
|
rsp.capacity = ARRAY_SIZE(buffer);
|
|
rsp.pointer = buffer;
|
|
|
|
slice = (struct surface_hid_buffer_slice *)buffer;
|
|
slice->entry = entry;
|
|
slice->end = 0;
|
|
|
|
offset = 0;
|
|
length = buffer_len;
|
|
|
|
while (!slice->end && offset < len) {
|
|
put_unaligned_le32(offset, &slice->offset);
|
|
put_unaligned_le32(length, &slice->length);
|
|
|
|
rsp.length = 0;
|
|
|
|
status = ssam_retry(ssam_request_do_sync_onstack, shid->ctrl, &rqst, &rsp,
|
|
sizeof(*slice));
|
|
if (status)
|
|
return status;
|
|
|
|
offset = get_unaligned_le32(&slice->offset);
|
|
length = get_unaligned_le32(&slice->length);
|
|
|
|
/* Don't mess stuff up in case we receive garbage. */
|
|
if (length > buffer_len || offset > len)
|
|
return -EPROTO;
|
|
|
|
if (offset + length > len)
|
|
length = len - offset;
|
|
|
|
memcpy(buf + offset, &slice->data[0], length);
|
|
|
|
offset += length;
|
|
length = buffer_len;
|
|
}
|
|
|
|
if (offset != len) {
|
|
dev_err(shid->dev, "unexpected descriptor length: got %u, expected %zu\n",
|
|
offset, len);
|
|
return -EPROTO;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int ssam_hid_set_raw_report(struct surface_hid_device *shid, u8 rprt_id, bool feature,
|
|
u8 *buf, size_t len)
|
|
{
|
|
struct ssam_request rqst;
|
|
u8 cid;
|
|
|
|
if (feature)
|
|
cid = SURFACE_HID_CID_SET_FEATURE_REPORT;
|
|
else
|
|
cid = SURFACE_HID_CID_OUTPUT_REPORT;
|
|
|
|
rqst.target_category = shid->uid.category;
|
|
rqst.target_id = shid->uid.target;
|
|
rqst.instance_id = shid->uid.instance;
|
|
rqst.command_id = cid;
|
|
rqst.flags = 0;
|
|
rqst.length = len;
|
|
rqst.payload = buf;
|
|
|
|
buf[0] = rprt_id;
|
|
|
|
return ssam_retry(ssam_request_do_sync, shid->ctrl, &rqst, NULL);
|
|
}
|
|
|
|
static int ssam_hid_get_raw_report(struct surface_hid_device *shid, u8 rprt_id, u8 *buf, size_t len)
|
|
{
|
|
struct ssam_request rqst;
|
|
struct ssam_response rsp;
|
|
|
|
rqst.target_category = shid->uid.category;
|
|
rqst.target_id = shid->uid.target;
|
|
rqst.instance_id = shid->uid.instance;
|
|
rqst.command_id = SURFACE_HID_CID_GET_FEATURE_REPORT;
|
|
rqst.flags = SSAM_REQUEST_HAS_RESPONSE;
|
|
rqst.length = sizeof(rprt_id);
|
|
rqst.payload = &rprt_id;
|
|
|
|
rsp.capacity = len;
|
|
rsp.length = 0;
|
|
rsp.pointer = buf;
|
|
|
|
return ssam_retry(ssam_request_do_sync_onstack, shid->ctrl, &rqst, &rsp, sizeof(rprt_id));
|
|
}
|
|
|
|
static u32 ssam_hid_event_fn(struct ssam_event_notifier *nf, const struct ssam_event *event)
|
|
{
|
|
struct surface_hid_device *shid = container_of(nf, struct surface_hid_device, notif);
|
|
|
|
if (event->command_id != 0x00)
|
|
return 0;
|
|
|
|
hid_input_report(shid->hid, HID_INPUT_REPORT, (u8 *)&event->data[0], event->length, 0);
|
|
return SSAM_NOTIF_HANDLED;
|
|
}
|
|
|
|
|
|
/* -- Transport driver. ----------------------------------------------------- */
|
|
|
|
static int shid_output_report(struct surface_hid_device *shid, u8 rprt_id, u8 *buf, size_t len)
|
|
{
|
|
int status;
|
|
|
|
status = ssam_hid_set_raw_report(shid, rprt_id, false, buf, len);
|
|
return status >= 0 ? len : status;
|
|
}
|
|
|
|
static int shid_get_feature_report(struct surface_hid_device *shid, u8 rprt_id, u8 *buf, size_t len)
|
|
{
|
|
int status;
|
|
|
|
status = ssam_hid_get_raw_report(shid, rprt_id, buf, len);
|
|
return status >= 0 ? len : status;
|
|
}
|
|
|
|
static int shid_set_feature_report(struct surface_hid_device *shid, u8 rprt_id, u8 *buf, size_t len)
|
|
{
|
|
int status;
|
|
|
|
status = ssam_hid_set_raw_report(shid, rprt_id, true, buf, len);
|
|
return status >= 0 ? len : status;
|
|
}
|
|
|
|
|
|
/* -- Driver setup. --------------------------------------------------------- */
|
|
|
|
static int surface_hid_probe(struct ssam_device *sdev)
|
|
{
|
|
struct surface_hid_device *shid;
|
|
|
|
shid = devm_kzalloc(&sdev->dev, sizeof(*shid), GFP_KERNEL);
|
|
if (!shid)
|
|
return -ENOMEM;
|
|
|
|
shid->dev = &sdev->dev;
|
|
shid->ctrl = sdev->ctrl;
|
|
shid->uid = sdev->uid;
|
|
|
|
shid->notif.base.priority = 1;
|
|
shid->notif.base.fn = ssam_hid_event_fn;
|
|
shid->notif.event.reg = SSAM_EVENT_REGISTRY_REG(sdev->uid.target);
|
|
shid->notif.event.id.target_category = sdev->uid.category;
|
|
shid->notif.event.id.instance = sdev->uid.instance;
|
|
shid->notif.event.mask = SSAM_EVENT_MASK_STRICT;
|
|
shid->notif.event.flags = 0;
|
|
|
|
shid->ops.get_descriptor = ssam_hid_get_descriptor;
|
|
shid->ops.output_report = shid_output_report;
|
|
shid->ops.get_feature_report = shid_get_feature_report;
|
|
shid->ops.set_feature_report = shid_set_feature_report;
|
|
|
|
ssam_device_set_drvdata(sdev, shid);
|
|
return surface_hid_device_add(shid);
|
|
}
|
|
|
|
static void surface_hid_remove(struct ssam_device *sdev)
|
|
{
|
|
surface_hid_device_destroy(ssam_device_get_drvdata(sdev));
|
|
}
|
|
|
|
static const struct ssam_device_id surface_hid_match[] = {
|
|
{ SSAM_SDEV(HID, ANY, SSAM_SSH_IID_ANY, 0x00) },
|
|
{ },
|
|
};
|
|
MODULE_DEVICE_TABLE(ssam, surface_hid_match);
|
|
|
|
static struct ssam_device_driver surface_hid_driver = {
|
|
.probe = surface_hid_probe,
|
|
.remove = surface_hid_remove,
|
|
.match_table = surface_hid_match,
|
|
.driver = {
|
|
.name = "surface_hid",
|
|
.pm = &surface_hid_pm_ops,
|
|
.probe_type = PROBE_PREFER_ASYNCHRONOUS,
|
|
},
|
|
};
|
|
module_ssam_device_driver(surface_hid_driver);
|
|
|
|
MODULE_AUTHOR("Blaž Hrastnik <blaz@mxxn.io>");
|
|
MODULE_AUTHOR("Maximilian Luz <luzmaximilian@gmail.com>");
|
|
MODULE_DESCRIPTION("HID transport driver for Surface System Aggregator Module");
|
|
MODULE_LICENSE("GPL");
|