1303a9d451
Changes:
- Rebase onto v5.12.14
- Add "PCI: PM: Do not read power state in pci_enable_device_flags()"
which has been reverted upstream back.
Links:
- kernel: 81416742c5
10240 lines
331 KiB
Diff
10240 lines
331 KiB
Diff
From e4addc12f76b5625d0a2c48459709b9be269f5df Mon Sep 17 00:00:00 2001
|
|
From: Maximilian Luz <luzmaximilian@gmail.com>
|
|
Date: Fri, 12 Feb 2021 12:54:34 +0100
|
|
Subject: [PATCH] platform/surface: Set up Surface Aggregator device registry
|
|
|
|
The Surface System Aggregator Module (SSAM) subsystem provides various
|
|
functionalities, which are separated by spreading them across multiple
|
|
devices and corresponding drivers. Parts of that functionality / some of
|
|
those devices, however, can (as far as we currently know) not be
|
|
auto-detected by conventional means. While older (specifically 5th- and
|
|
6th-)generation models do advertise most of their functionality via
|
|
standard platform devices in ACPI, newer generations do not.
|
|
|
|
As we are currently also not aware of any feasible way to query said
|
|
functionalities dynamically, this poses a problem. There is, however, a
|
|
device in ACPI that seems to be used by Windows for identifying
|
|
different Surface models: The Windows Surface Integration Device (WSID).
|
|
This device seems to have a HID corresponding to the overall set of
|
|
functionalities SSAM provides for the associated model.
|
|
|
|
This commit introduces a registry providing non-detectable device
|
|
information via software nodes. In addition, a SSAM platform hub driver
|
|
is introduced, which takes care of creating and managing the SSAM
|
|
devices specified in this registry. This approach allows for a
|
|
hierarchical setup akin to ACPI and is easily extendable, e.g. via
|
|
firmware node properties.
|
|
|
|
Note that this commit only provides the basis for the platform hub and
|
|
registry, and does not add any content to it. The registry will be
|
|
expanded in subsequent commits.
|
|
|
|
Signed-off-by: Maximilian Luz <luzmaximilian@gmail.com>
|
|
Link: https://lore.kernel.org/r/20210212115439.1525216-2-luzmaximilian@gmail.com
|
|
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
|
|
Patchset: surface-sam
|
|
---
|
|
MAINTAINERS | 1 +
|
|
drivers/platform/surface/Kconfig | 27 ++
|
|
drivers/platform/surface/Makefile | 1 +
|
|
.../surface/surface_aggregator_registry.c | 284 ++++++++++++++++++
|
|
4 files changed, 313 insertions(+)
|
|
create mode 100644 drivers/platform/surface/surface_aggregator_registry.c
|
|
|
|
diff --git a/MAINTAINERS b/MAINTAINERS
|
|
index 9450e052f1b1..f6c524630575 100644
|
|
--- a/MAINTAINERS
|
|
+++ b/MAINTAINERS
|
|
@@ -11904,6 +11904,7 @@ F: Documentation/driver-api/surface_aggregator/
|
|
F: drivers/platform/surface/aggregator/
|
|
F: drivers/platform/surface/surface_acpi_notify.c
|
|
F: drivers/platform/surface/surface_aggregator_cdev.c
|
|
+F: drivers/platform/surface/surface_aggregator_registry.c
|
|
F: include/linux/surface_acpi_notify.h
|
|
F: include/linux/surface_aggregator/
|
|
F: include/uapi/linux/surface_aggregator/
|
|
diff --git a/drivers/platform/surface/Kconfig b/drivers/platform/surface/Kconfig
|
|
index fd45940ab6ce..c51c55204b5f 100644
|
|
--- a/drivers/platform/surface/Kconfig
|
|
+++ b/drivers/platform/surface/Kconfig
|
|
@@ -77,6 +77,33 @@ config SURFACE_AGGREGATOR_CDEV
|
|
The provided interface is intended for debugging and development only,
|
|
and should not be used otherwise.
|
|
|
|
+config SURFACE_AGGREGATOR_REGISTRY
|
|
+ tristate "Surface System Aggregator Module Device Registry"
|
|
+ depends on SURFACE_AGGREGATOR
|
|
+ depends on SURFACE_AGGREGATOR_BUS
|
|
+ help
|
|
+ Device-registry and device-hubs for Surface System Aggregator Module
|
|
+ (SSAM) devices.
|
|
+
|
|
+ Provides a module and driver which act as a device-registry for SSAM
|
|
+ client devices that cannot be detected automatically, e.g. via ACPI.
|
|
+ Such devices are instead provided via this registry and attached via
|
|
+ device hubs, also provided in this module.
|
|
+
|
|
+ Devices provided via this registry are:
|
|
+ - Platform profile (performance-/cooling-mode) device (5th- and later
|
|
+ generations).
|
|
+ - Battery/AC devices (7th-generation).
|
|
+ - HID input devices (7th-generation).
|
|
+
|
|
+ Select M (recommended) or Y here if you want support for the above
|
|
+ mentioned devices on the corresponding Surface models. Without this
|
|
+ module, the respective devices will not be instantiated and thus any
|
|
+ functionality provided by them will be missing, even when drivers for
|
|
+ these devices are present. In other words, this module only provides
|
|
+ the respective client devices. Drivers for these devices still need to
|
|
+ be selected via the other options.
|
|
+
|
|
config SURFACE_BOOK1_DGPU_SWITCH
|
|
tristate "Surface Book 1 dGPU Switch Driver"
|
|
depends on SYSFS
|
|
diff --git a/drivers/platform/surface/Makefile b/drivers/platform/surface/Makefile
|
|
index 6b69175598ab..ed12676f06e6 100644
|
|
--- a/drivers/platform/surface/Makefile
|
|
+++ b/drivers/platform/surface/Makefile
|
|
@@ -10,6 +10,7 @@ obj-$(CONFIG_SURFACE_3_POWER_OPREGION) += surface3_power.o
|
|
obj-$(CONFIG_SURFACE_ACPI_NOTIFY) += surface_acpi_notify.o
|
|
obj-$(CONFIG_SURFACE_AGGREGATOR) += aggregator/
|
|
obj-$(CONFIG_SURFACE_AGGREGATOR_CDEV) += surface_aggregator_cdev.o
|
|
+obj-$(CONFIG_SURFACE_AGGREGATOR_REGISTRY) += surface_aggregator_registry.o
|
|
obj-$(CONFIG_SURFACE_BOOK1_DGPU_SWITCH) += surfacebook1_dgpu_switch.o
|
|
obj-$(CONFIG_SURFACE_GPE) += surface_gpe.o
|
|
obj-$(CONFIG_SURFACE_HOTPLUG) += surface_hotplug.o
|
|
diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c
|
|
new file mode 100644
|
|
index 000000000000..a051d941ad96
|
|
--- /dev/null
|
|
+++ b/drivers/platform/surface/surface_aggregator_registry.c
|
|
@@ -0,0 +1,284 @@
|
|
+// SPDX-License-Identifier: GPL-2.0+
|
|
+/*
|
|
+ * Surface System Aggregator Module (SSAM) client device registry.
|
|
+ *
|
|
+ * Registry for non-platform/non-ACPI SSAM client devices, i.e. devices that
|
|
+ * cannot be auto-detected. Provides device-hubs and performs instantiation
|
|
+ * for these devices.
|
|
+ *
|
|
+ * Copyright (C) 2020-2021 Maximilian Luz <luzmaximilian@gmail.com>
|
|
+ */
|
|
+
|
|
+#include <linux/acpi.h>
|
|
+#include <linux/kernel.h>
|
|
+#include <linux/module.h>
|
|
+#include <linux/platform_device.h>
|
|
+#include <linux/property.h>
|
|
+
|
|
+#include <linux/surface_aggregator/controller.h>
|
|
+#include <linux/surface_aggregator/device.h>
|
|
+
|
|
+
|
|
+/* -- Device registry. ------------------------------------------------------ */
|
|
+
|
|
+/*
|
|
+ * SSAM device names follow the SSAM module alias, meaning they are prefixed
|
|
+ * with 'ssam:', followed by domain, category, target ID, instance ID, and
|
|
+ * function, each encoded as two-digit hexadecimal, separated by ':'. In other
|
|
+ * words, it follows the scheme
|
|
+ *
|
|
+ * ssam:dd:cc:tt:ii:ff
|
|
+ *
|
|
+ * Where, 'dd', 'cc', 'tt', 'ii', and 'ff' are the two-digit hexadecimal
|
|
+ * values mentioned above, respectively.
|
|
+ */
|
|
+
|
|
+/* Root node. */
|
|
+static const struct software_node ssam_node_root = {
|
|
+ .name = "ssam_platform_hub",
|
|
+};
|
|
+
|
|
+/* Devices for Surface Book 2. */
|
|
+static const struct software_node *ssam_node_group_sb2[] = {
|
|
+ &ssam_node_root,
|
|
+ NULL,
|
|
+};
|
|
+
|
|
+/* Devices for Surface Book 3. */
|
|
+static const struct software_node *ssam_node_group_sb3[] = {
|
|
+ &ssam_node_root,
|
|
+ NULL,
|
|
+};
|
|
+
|
|
+/* Devices for Surface Laptop 1. */
|
|
+static const struct software_node *ssam_node_group_sl1[] = {
|
|
+ &ssam_node_root,
|
|
+ NULL,
|
|
+};
|
|
+
|
|
+/* Devices for Surface Laptop 2. */
|
|
+static const struct software_node *ssam_node_group_sl2[] = {
|
|
+ &ssam_node_root,
|
|
+ NULL,
|
|
+};
|
|
+
|
|
+/* Devices for Surface Laptop 3. */
|
|
+static const struct software_node *ssam_node_group_sl3[] = {
|
|
+ &ssam_node_root,
|
|
+ NULL,
|
|
+};
|
|
+
|
|
+/* Devices for Surface Laptop Go. */
|
|
+static const struct software_node *ssam_node_group_slg1[] = {
|
|
+ &ssam_node_root,
|
|
+ NULL,
|
|
+};
|
|
+
|
|
+/* Devices for Surface Pro 5. */
|
|
+static const struct software_node *ssam_node_group_sp5[] = {
|
|
+ &ssam_node_root,
|
|
+ NULL,
|
|
+};
|
|
+
|
|
+/* Devices for Surface Pro 6. */
|
|
+static const struct software_node *ssam_node_group_sp6[] = {
|
|
+ &ssam_node_root,
|
|
+ NULL,
|
|
+};
|
|
+
|
|
+/* Devices for Surface Pro 7. */
|
|
+static const struct software_node *ssam_node_group_sp7[] = {
|
|
+ &ssam_node_root,
|
|
+ NULL,
|
|
+};
|
|
+
|
|
+
|
|
+/* -- Device registry helper functions. ------------------------------------- */
|
|
+
|
|
+static int ssam_uid_from_string(const char *str, struct ssam_device_uid *uid)
|
|
+{
|
|
+ u8 d, tc, tid, iid, fn;
|
|
+ int n;
|
|
+
|
|
+ n = sscanf(str, "ssam:%hhx:%hhx:%hhx:%hhx:%hhx", &d, &tc, &tid, &iid, &fn);
|
|
+ if (n != 5)
|
|
+ return -EINVAL;
|
|
+
|
|
+ uid->domain = d;
|
|
+ uid->category = tc;
|
|
+ uid->target = tid;
|
|
+ uid->instance = iid;
|
|
+ uid->function = fn;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int ssam_hub_remove_devices_fn(struct device *dev, void *data)
|
|
+{
|
|
+ if (!is_ssam_device(dev))
|
|
+ return 0;
|
|
+
|
|
+ ssam_device_remove(to_ssam_device(dev));
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static void ssam_hub_remove_devices(struct device *parent)
|
|
+{
|
|
+ device_for_each_child_reverse(parent, NULL, ssam_hub_remove_devices_fn);
|
|
+}
|
|
+
|
|
+static int ssam_hub_add_device(struct device *parent, struct ssam_controller *ctrl,
|
|
+ struct fwnode_handle *node)
|
|
+{
|
|
+ struct ssam_device_uid uid;
|
|
+ struct ssam_device *sdev;
|
|
+ int status;
|
|
+
|
|
+ status = ssam_uid_from_string(fwnode_get_name(node), &uid);
|
|
+ if (status)
|
|
+ return status;
|
|
+
|
|
+ sdev = ssam_device_alloc(ctrl, uid);
|
|
+ if (!sdev)
|
|
+ return -ENOMEM;
|
|
+
|
|
+ sdev->dev.parent = parent;
|
|
+ sdev->dev.fwnode = node;
|
|
+
|
|
+ status = ssam_device_add(sdev);
|
|
+ if (status)
|
|
+ ssam_device_put(sdev);
|
|
+
|
|
+ return status;
|
|
+}
|
|
+
|
|
+static int ssam_hub_add_devices(struct device *parent, struct ssam_controller *ctrl,
|
|
+ struct fwnode_handle *node)
|
|
+{
|
|
+ struct fwnode_handle *child;
|
|
+ int status;
|
|
+
|
|
+ fwnode_for_each_child_node(node, child) {
|
|
+ /*
|
|
+ * Try to add the device specified in the firmware node. If
|
|
+ * this fails with -EINVAL, the node does not specify any SSAM
|
|
+ * device, so ignore it and continue with the next one.
|
|
+ */
|
|
+
|
|
+ status = ssam_hub_add_device(parent, ctrl, child);
|
|
+ if (status && status != -EINVAL)
|
|
+ goto err;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+err:
|
|
+ ssam_hub_remove_devices(parent);
|
|
+ return status;
|
|
+}
|
|
+
|
|
+
|
|
+/* -- SSAM platform/meta-hub driver. ---------------------------------------- */
|
|
+
|
|
+static const struct acpi_device_id ssam_platform_hub_match[] = {
|
|
+ /* Surface Pro 4, 5, and 6 (OMBR < 0x10) */
|
|
+ { "MSHW0081", (unsigned long)ssam_node_group_sp5 },
|
|
+
|
|
+ /* Surface Pro 6 (OMBR >= 0x10) */
|
|
+ { "MSHW0111", (unsigned long)ssam_node_group_sp6 },
|
|
+
|
|
+ /* Surface Pro 7 */
|
|
+ { "MSHW0116", (unsigned long)ssam_node_group_sp7 },
|
|
+
|
|
+ /* Surface Book 2 */
|
|
+ { "MSHW0107", (unsigned long)ssam_node_group_sb2 },
|
|
+
|
|
+ /* Surface Book 3 */
|
|
+ { "MSHW0117", (unsigned long)ssam_node_group_sb3 },
|
|
+
|
|
+ /* Surface Laptop 1 */
|
|
+ { "MSHW0086", (unsigned long)ssam_node_group_sl1 },
|
|
+
|
|
+ /* Surface Laptop 2 */
|
|
+ { "MSHW0112", (unsigned long)ssam_node_group_sl2 },
|
|
+
|
|
+ /* Surface Laptop 3 (13", Intel) */
|
|
+ { "MSHW0114", (unsigned long)ssam_node_group_sl3 },
|
|
+
|
|
+ /* Surface Laptop 3 (15", AMD) */
|
|
+ { "MSHW0110", (unsigned long)ssam_node_group_sl3 },
|
|
+
|
|
+ /* Surface Laptop Go 1 */
|
|
+ { "MSHW0118", (unsigned long)ssam_node_group_slg1 },
|
|
+
|
|
+ { },
|
|
+};
|
|
+MODULE_DEVICE_TABLE(acpi, ssam_platform_hub_match);
|
|
+
|
|
+static int ssam_platform_hub_probe(struct platform_device *pdev)
|
|
+{
|
|
+ const struct software_node **nodes;
|
|
+ struct ssam_controller *ctrl;
|
|
+ struct fwnode_handle *root;
|
|
+ int status;
|
|
+
|
|
+ nodes = (const struct software_node **)acpi_device_get_match_data(&pdev->dev);
|
|
+ if (!nodes)
|
|
+ return -ENODEV;
|
|
+
|
|
+ /*
|
|
+ * As we're adding the SSAM client devices as children under this device
|
|
+ * and not the SSAM controller, we need to add a device link to the
|
|
+ * controller to ensure that we remove all of our devices before the
|
|
+ * controller is removed. This also guarantees proper ordering for
|
|
+ * suspend/resume of the devices on this hub.
|
|
+ */
|
|
+ ctrl = ssam_client_bind(&pdev->dev);
|
|
+ if (IS_ERR(ctrl))
|
|
+ return PTR_ERR(ctrl) == -ENODEV ? -EPROBE_DEFER : PTR_ERR(ctrl);
|
|
+
|
|
+ status = software_node_register_node_group(nodes);
|
|
+ if (status)
|
|
+ return status;
|
|
+
|
|
+ root = software_node_fwnode(&ssam_node_root);
|
|
+ if (!root) {
|
|
+ software_node_unregister_node_group(nodes);
|
|
+ return -ENOENT;
|
|
+ }
|
|
+
|
|
+ set_secondary_fwnode(&pdev->dev, root);
|
|
+
|
|
+ status = ssam_hub_add_devices(&pdev->dev, ctrl, root);
|
|
+ if (status) {
|
|
+ set_secondary_fwnode(&pdev->dev, NULL);
|
|
+ software_node_unregister_node_group(nodes);
|
|
+ }
|
|
+
|
|
+ platform_set_drvdata(pdev, nodes);
|
|
+ return status;
|
|
+}
|
|
+
|
|
+static int ssam_platform_hub_remove(struct platform_device *pdev)
|
|
+{
|
|
+ const struct software_node **nodes = platform_get_drvdata(pdev);
|
|
+
|
|
+ ssam_hub_remove_devices(&pdev->dev);
|
|
+ set_secondary_fwnode(&pdev->dev, NULL);
|
|
+ software_node_unregister_node_group(nodes);
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static struct platform_driver ssam_platform_hub_driver = {
|
|
+ .probe = ssam_platform_hub_probe,
|
|
+ .remove = ssam_platform_hub_remove,
|
|
+ .driver = {
|
|
+ .name = "surface_aggregator_platform_hub",
|
|
+ .acpi_match_table = ssam_platform_hub_match,
|
|
+ .probe_type = PROBE_PREFER_ASYNCHRONOUS,
|
|
+ },
|
|
+};
|
|
+module_platform_driver(ssam_platform_hub_driver);
|
|
+
|
|
+MODULE_AUTHOR("Maximilian Luz <luzmaximilian@gmail.com>");
|
|
+MODULE_DESCRIPTION("Device-registry for Surface System Aggregator Module");
|
|
+MODULE_LICENSE("GPL");
|
|
--
|
|
2.32.0
|
|
|
|
From c70ad8f475c65c0000e4d08acdc1861ff557fe01 Mon Sep 17 00:00:00 2001
|
|
From: Maximilian Luz <luzmaximilian@gmail.com>
|
|
Date: Fri, 12 Feb 2021 12:54:35 +0100
|
|
Subject: [PATCH] platform/surface: aggregator_registry: Add base device hub
|
|
|
|
The Surface Book 3 has a detachable base part. While the top part
|
|
(so-called clipboard) contains the CPU, touchscreen, and primary
|
|
battery, the base contains, among other things, a keyboard, touchpad,
|
|
and secondary battery.
|
|
|
|
Those devices do not react well to being accessed when the base part is
|
|
detached and should thus be removed and added in sync with the base. To
|
|
facilitate this, we introduce a virtual base device hub, which
|
|
automatically removes or adds the devices registered under it.
|
|
|
|
Signed-off-by: Maximilian Luz <luzmaximilian@gmail.com>
|
|
Link: https://lore.kernel.org/r/20210212115439.1525216-3-luzmaximilian@gmail.com
|
|
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
|
|
Patchset: surface-sam
|
|
---
|
|
.../surface/surface_aggregator_registry.c | 261 +++++++++++++++++-
|
|
1 file changed, 260 insertions(+), 1 deletion(-)
|
|
|
|
diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c
|
|
index a051d941ad96..6c23d75a044c 100644
|
|
--- a/drivers/platform/surface/surface_aggregator_registry.c
|
|
+++ b/drivers/platform/surface/surface_aggregator_registry.c
|
|
@@ -11,9 +11,12 @@
|
|
|
|
#include <linux/acpi.h>
|
|
#include <linux/kernel.h>
|
|
+#include <linux/limits.h>
|
|
#include <linux/module.h>
|
|
+#include <linux/mutex.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/property.h>
|
|
+#include <linux/types.h>
|
|
|
|
#include <linux/surface_aggregator/controller.h>
|
|
#include <linux/surface_aggregator/device.h>
|
|
@@ -38,6 +41,12 @@ static const struct software_node ssam_node_root = {
|
|
.name = "ssam_platform_hub",
|
|
};
|
|
|
|
+/* Base device hub (devices attached to Surface Book 3 base). */
|
|
+static const struct software_node ssam_node_hub_base = {
|
|
+ .name = "ssam:00:00:02:00:00",
|
|
+ .parent = &ssam_node_root,
|
|
+};
|
|
+
|
|
/* Devices for Surface Book 2. */
|
|
static const struct software_node *ssam_node_group_sb2[] = {
|
|
&ssam_node_root,
|
|
@@ -47,6 +56,7 @@ static const struct software_node *ssam_node_group_sb2[] = {
|
|
/* Devices for Surface Book 3. */
|
|
static const struct software_node *ssam_node_group_sb3[] = {
|
|
&ssam_node_root,
|
|
+ &ssam_node_hub_base,
|
|
NULL,
|
|
};
|
|
|
|
@@ -177,6 +187,230 @@ static int ssam_hub_add_devices(struct device *parent, struct ssam_controller *c
|
|
}
|
|
|
|
|
|
+/* -- SSAM base-hub driver. ------------------------------------------------- */
|
|
+
|
|
+enum ssam_base_hub_state {
|
|
+ SSAM_BASE_HUB_UNINITIALIZED,
|
|
+ SSAM_BASE_HUB_CONNECTED,
|
|
+ SSAM_BASE_HUB_DISCONNECTED,
|
|
+};
|
|
+
|
|
+struct ssam_base_hub {
|
|
+ struct ssam_device *sdev;
|
|
+
|
|
+ struct mutex lock; /* Guards state update checks and transitions. */
|
|
+ enum ssam_base_hub_state state;
|
|
+
|
|
+ struct ssam_event_notifier notif;
|
|
+};
|
|
+
|
|
+static SSAM_DEFINE_SYNC_REQUEST_R(ssam_bas_query_opmode, u8, {
|
|
+ .target_category = SSAM_SSH_TC_BAS,
|
|
+ .target_id = 0x01,
|
|
+ .command_id = 0x0d,
|
|
+ .instance_id = 0x00,
|
|
+});
|
|
+
|
|
+#define SSAM_BAS_OPMODE_TABLET 0x00
|
|
+#define SSAM_EVENT_BAS_CID_CONNECTION 0x0c
|
|
+
|
|
+static int ssam_base_hub_query_state(struct ssam_base_hub *hub, enum ssam_base_hub_state *state)
|
|
+{
|
|
+ u8 opmode;
|
|
+ int status;
|
|
+
|
|
+ status = ssam_retry(ssam_bas_query_opmode, hub->sdev->ctrl, &opmode);
|
|
+ if (status < 0) {
|
|
+ dev_err(&hub->sdev->dev, "failed to query base state: %d\n", status);
|
|
+ return status;
|
|
+ }
|
|
+
|
|
+ if (opmode != SSAM_BAS_OPMODE_TABLET)
|
|
+ *state = SSAM_BASE_HUB_CONNECTED;
|
|
+ else
|
|
+ *state = SSAM_BASE_HUB_DISCONNECTED;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static ssize_t ssam_base_hub_state_show(struct device *dev, struct device_attribute *attr,
|
|
+ char *buf)
|
|
+{
|
|
+ struct ssam_base_hub *hub = dev_get_drvdata(dev);
|
|
+ bool connected;
|
|
+
|
|
+ mutex_lock(&hub->lock);
|
|
+ connected = hub->state == SSAM_BASE_HUB_CONNECTED;
|
|
+ mutex_unlock(&hub->lock);
|
|
+
|
|
+ return sysfs_emit(buf, "%d\n", connected);
|
|
+}
|
|
+
|
|
+static struct device_attribute ssam_base_hub_attr_state =
|
|
+ __ATTR(state, 0444, ssam_base_hub_state_show, NULL);
|
|
+
|
|
+static struct attribute *ssam_base_hub_attrs[] = {
|
|
+ &ssam_base_hub_attr_state.attr,
|
|
+ NULL,
|
|
+};
|
|
+
|
|
+const struct attribute_group ssam_base_hub_group = {
|
|
+ .attrs = ssam_base_hub_attrs,
|
|
+};
|
|
+
|
|
+static int __ssam_base_hub_update(struct ssam_base_hub *hub, enum ssam_base_hub_state new)
|
|
+{
|
|
+ struct fwnode_handle *node = dev_fwnode(&hub->sdev->dev);
|
|
+ int status = 0;
|
|
+
|
|
+ lockdep_assert_held(&hub->lock);
|
|
+
|
|
+ if (hub->state == new)
|
|
+ return 0;
|
|
+ hub->state = new;
|
|
+
|
|
+ if (hub->state == SSAM_BASE_HUB_CONNECTED)
|
|
+ status = ssam_hub_add_devices(&hub->sdev->dev, hub->sdev->ctrl, node);
|
|
+ else
|
|
+ ssam_hub_remove_devices(&hub->sdev->dev);
|
|
+
|
|
+ if (status)
|
|
+ dev_err(&hub->sdev->dev, "failed to update base-hub devices: %d\n", status);
|
|
+
|
|
+ return status;
|
|
+}
|
|
+
|
|
+static int ssam_base_hub_update(struct ssam_base_hub *hub)
|
|
+{
|
|
+ enum ssam_base_hub_state state;
|
|
+ int status;
|
|
+
|
|
+ mutex_lock(&hub->lock);
|
|
+
|
|
+ status = ssam_base_hub_query_state(hub, &state);
|
|
+ if (!status)
|
|
+ status = __ssam_base_hub_update(hub, state);
|
|
+
|
|
+ mutex_unlock(&hub->lock);
|
|
+ return status;
|
|
+}
|
|
+
|
|
+static u32 ssam_base_hub_notif(struct ssam_event_notifier *nf, const struct ssam_event *event)
|
|
+{
|
|
+ struct ssam_base_hub *hub;
|
|
+ struct ssam_device *sdev;
|
|
+ enum ssam_base_hub_state new;
|
|
+
|
|
+ hub = container_of(nf, struct ssam_base_hub, notif);
|
|
+ sdev = hub->sdev;
|
|
+
|
|
+ if (event->command_id != SSAM_EVENT_BAS_CID_CONNECTION)
|
|
+ return 0;
|
|
+
|
|
+ if (event->length < 1) {
|
|
+ dev_err(&sdev->dev, "unexpected payload size: %u\n",
|
|
+ event->length);
|
|
+ return 0;
|
|
+ }
|
|
+
|
|
+ if (event->data[0])
|
|
+ new = SSAM_BASE_HUB_CONNECTED;
|
|
+ else
|
|
+ new = SSAM_BASE_HUB_DISCONNECTED;
|
|
+
|
|
+ mutex_lock(&hub->lock);
|
|
+ __ssam_base_hub_update(hub, new);
|
|
+ mutex_unlock(&hub->lock);
|
|
+
|
|
+ /*
|
|
+ * Do not return SSAM_NOTIF_HANDLED: The event should be picked up and
|
|
+ * consumed by the detachment system driver. We're just a (more or less)
|
|
+ * silent observer.
|
|
+ */
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int __maybe_unused ssam_base_hub_resume(struct device *dev)
|
|
+{
|
|
+ return ssam_base_hub_update(dev_get_drvdata(dev));
|
|
+}
|
|
+static SIMPLE_DEV_PM_OPS(ssam_base_hub_pm_ops, NULL, ssam_base_hub_resume);
|
|
+
|
|
+static int ssam_base_hub_probe(struct ssam_device *sdev)
|
|
+{
|
|
+ struct ssam_base_hub *hub;
|
|
+ int status;
|
|
+
|
|
+ hub = devm_kzalloc(&sdev->dev, sizeof(*hub), GFP_KERNEL);
|
|
+ if (!hub)
|
|
+ return -ENOMEM;
|
|
+
|
|
+ mutex_init(&hub->lock);
|
|
+
|
|
+ hub->sdev = sdev;
|
|
+ hub->state = SSAM_BASE_HUB_UNINITIALIZED;
|
|
+
|
|
+ hub->notif.base.priority = INT_MAX; /* This notifier should run first. */
|
|
+ hub->notif.base.fn = ssam_base_hub_notif;
|
|
+ hub->notif.event.reg = SSAM_EVENT_REGISTRY_SAM;
|
|
+ hub->notif.event.id.target_category = SSAM_SSH_TC_BAS,
|
|
+ hub->notif.event.id.instance = 0,
|
|
+ hub->notif.event.mask = SSAM_EVENT_MASK_NONE;
|
|
+ hub->notif.event.flags = SSAM_EVENT_SEQUENCED;
|
|
+
|
|
+ ssam_device_set_drvdata(sdev, hub);
|
|
+
|
|
+ status = ssam_notifier_register(sdev->ctrl, &hub->notif);
|
|
+ if (status)
|
|
+ goto err_register;
|
|
+
|
|
+ status = ssam_base_hub_update(hub);
|
|
+ if (status)
|
|
+ goto err_update;
|
|
+
|
|
+ status = sysfs_create_group(&sdev->dev.kobj, &ssam_base_hub_group);
|
|
+ if (status)
|
|
+ goto err_update;
|
|
+
|
|
+ return 0;
|
|
+
|
|
+err_update:
|
|
+ ssam_notifier_unregister(sdev->ctrl, &hub->notif);
|
|
+ ssam_hub_remove_devices(&sdev->dev);
|
|
+err_register:
|
|
+ mutex_destroy(&hub->lock);
|
|
+ return status;
|
|
+}
|
|
+
|
|
+static void ssam_base_hub_remove(struct ssam_device *sdev)
|
|
+{
|
|
+ struct ssam_base_hub *hub = ssam_device_get_drvdata(sdev);
|
|
+
|
|
+ sysfs_remove_group(&sdev->dev.kobj, &ssam_base_hub_group);
|
|
+
|
|
+ ssam_notifier_unregister(sdev->ctrl, &hub->notif);
|
|
+ ssam_hub_remove_devices(&sdev->dev);
|
|
+
|
|
+ mutex_destroy(&hub->lock);
|
|
+}
|
|
+
|
|
+static const struct ssam_device_id ssam_base_hub_match[] = {
|
|
+ { SSAM_VDEV(HUB, 0x02, SSAM_ANY_IID, 0x00) },
|
|
+ { },
|
|
+};
|
|
+
|
|
+static struct ssam_device_driver ssam_base_hub_driver = {
|
|
+ .probe = ssam_base_hub_probe,
|
|
+ .remove = ssam_base_hub_remove,
|
|
+ .match_table = ssam_base_hub_match,
|
|
+ .driver = {
|
|
+ .name = "surface_aggregator_base_hub",
|
|
+ .probe_type = PROBE_PREFER_ASYNCHRONOUS,
|
|
+ .pm = &ssam_base_hub_pm_ops,
|
|
+ },
|
|
+};
|
|
+
|
|
+
|
|
/* -- SSAM platform/meta-hub driver. ---------------------------------------- */
|
|
|
|
static const struct acpi_device_id ssam_platform_hub_match[] = {
|
|
@@ -277,7 +511,32 @@ static struct platform_driver ssam_platform_hub_driver = {
|
|
.probe_type = PROBE_PREFER_ASYNCHRONOUS,
|
|
},
|
|
};
|
|
-module_platform_driver(ssam_platform_hub_driver);
|
|
+
|
|
+
|
|
+/* -- Module initialization. ------------------------------------------------ */
|
|
+
|
|
+static int __init ssam_device_hub_init(void)
|
|
+{
|
|
+ int status;
|
|
+
|
|
+ status = platform_driver_register(&ssam_platform_hub_driver);
|
|
+ if (status)
|
|
+ return status;
|
|
+
|
|
+ status = ssam_device_driver_register(&ssam_base_hub_driver);
|
|
+ if (status)
|
|
+ platform_driver_unregister(&ssam_platform_hub_driver);
|
|
+
|
|
+ return status;
|
|
+}
|
|
+module_init(ssam_device_hub_init);
|
|
+
|
|
+static void __exit ssam_device_hub_exit(void)
|
|
+{
|
|
+ ssam_device_driver_unregister(&ssam_base_hub_driver);
|
|
+ platform_driver_unregister(&ssam_platform_hub_driver);
|
|
+}
|
|
+module_exit(ssam_device_hub_exit);
|
|
|
|
MODULE_AUTHOR("Maximilian Luz <luzmaximilian@gmail.com>");
|
|
MODULE_DESCRIPTION("Device-registry for Surface System Aggregator Module");
|
|
--
|
|
2.32.0
|
|
|
|
From 9652c52f812afe1f5a75508d4fe077cf73d71e3f Mon Sep 17 00:00:00 2001
|
|
From: Maximilian Luz <luzmaximilian@gmail.com>
|
|
Date: Fri, 12 Feb 2021 12:54:36 +0100
|
|
Subject: [PATCH] platform/surface: aggregator_registry: Add battery subsystem
|
|
devices
|
|
|
|
Add battery subsystem (TC=0x02) devices (battery and AC) to the SSAM
|
|
device registry. These devices need to be registered for 7th-generation
|
|
Surface models. On 5th- and 6th-generation models, these devices are
|
|
handled via the standard ACPI battery/AC interface, which in turn
|
|
accesses the same SSAM interface via the Surface ACPI Notify (SAN)
|
|
driver.
|
|
|
|
Signed-off-by: Maximilian Luz <luzmaximilian@gmail.com>
|
|
Link: https://lore.kernel.org/r/20210212115439.1525216-4-luzmaximilian@gmail.com
|
|
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
|
|
Patchset: surface-sam
|
|
---
|
|
.../surface/surface_aggregator_registry.c | 27 +++++++++++++++++++
|
|
1 file changed, 27 insertions(+)
|
|
|
|
diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c
|
|
index 6c23d75a044c..cde279692842 100644
|
|
--- a/drivers/platform/surface/surface_aggregator_registry.c
|
|
+++ b/drivers/platform/surface/surface_aggregator_registry.c
|
|
@@ -47,6 +47,24 @@ static const struct software_node ssam_node_hub_base = {
|
|
.parent = &ssam_node_root,
|
|
};
|
|
|
|
+/* AC adapter. */
|
|
+static const struct software_node ssam_node_bat_ac = {
|
|
+ .name = "ssam:01:02:01:01:01",
|
|
+ .parent = &ssam_node_root,
|
|
+};
|
|
+
|
|
+/* Primary battery. */
|
|
+static const struct software_node ssam_node_bat_main = {
|
|
+ .name = "ssam:01:02:01:01:00",
|
|
+ .parent = &ssam_node_root,
|
|
+};
|
|
+
|
|
+/* Secondary battery (Surface Book 3). */
|
|
+static const struct software_node ssam_node_bat_sb3base = {
|
|
+ .name = "ssam:01:02:02:01:00",
|
|
+ .parent = &ssam_node_hub_base,
|
|
+};
|
|
+
|
|
/* Devices for Surface Book 2. */
|
|
static const struct software_node *ssam_node_group_sb2[] = {
|
|
&ssam_node_root,
|
|
@@ -57,6 +75,9 @@ static const struct software_node *ssam_node_group_sb2[] = {
|
|
static const struct software_node *ssam_node_group_sb3[] = {
|
|
&ssam_node_root,
|
|
&ssam_node_hub_base,
|
|
+ &ssam_node_bat_ac,
|
|
+ &ssam_node_bat_main,
|
|
+ &ssam_node_bat_sb3base,
|
|
NULL,
|
|
};
|
|
|
|
@@ -75,12 +96,16 @@ static const struct software_node *ssam_node_group_sl2[] = {
|
|
/* Devices for Surface Laptop 3. */
|
|
static const struct software_node *ssam_node_group_sl3[] = {
|
|
&ssam_node_root,
|
|
+ &ssam_node_bat_ac,
|
|
+ &ssam_node_bat_main,
|
|
NULL,
|
|
};
|
|
|
|
/* Devices for Surface Laptop Go. */
|
|
static const struct software_node *ssam_node_group_slg1[] = {
|
|
&ssam_node_root,
|
|
+ &ssam_node_bat_ac,
|
|
+ &ssam_node_bat_main,
|
|
NULL,
|
|
};
|
|
|
|
@@ -99,6 +124,8 @@ static const struct software_node *ssam_node_group_sp6[] = {
|
|
/* Devices for Surface Pro 7. */
|
|
static const struct software_node *ssam_node_group_sp7[] = {
|
|
&ssam_node_root,
|
|
+ &ssam_node_bat_ac,
|
|
+ &ssam_node_bat_main,
|
|
NULL,
|
|
};
|
|
|
|
--
|
|
2.32.0
|
|
|
|
From 4d0bc54f28f710ec656a063bf3c5265a57d3b13d Mon Sep 17 00:00:00 2001
|
|
From: Maximilian Luz <luzmaximilian@gmail.com>
|
|
Date: Fri, 12 Feb 2021 12:54:37 +0100
|
|
Subject: [PATCH] platform/surface: aggregator_registry: Add platform profile
|
|
device
|
|
|
|
Add the SSAM platform profile device to the SSAM device registry. This
|
|
device is accessible under the thermal subsystem (TC=0x03) and needs to
|
|
be registered for all Surface models.
|
|
|
|
Signed-off-by: Maximilian Luz <luzmaximilian@gmail.com>
|
|
Link: https://lore.kernel.org/r/20210212115439.1525216-5-luzmaximilian@gmail.com
|
|
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
|
|
Patchset: surface-sam
|
|
---
|
|
.../surface/surface_aggregator_registry.c | 15 +++++++++++++++
|
|
1 file changed, 15 insertions(+)
|
|
|
|
diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c
|
|
index cde279692842..33904613dd4b 100644
|
|
--- a/drivers/platform/surface/surface_aggregator_registry.c
|
|
+++ b/drivers/platform/surface/surface_aggregator_registry.c
|
|
@@ -65,9 +65,16 @@ static const struct software_node ssam_node_bat_sb3base = {
|
|
.parent = &ssam_node_hub_base,
|
|
};
|
|
|
|
+/* Platform profile / performance-mode device. */
|
|
+static const struct software_node ssam_node_tmp_pprof = {
|
|
+ .name = "ssam:01:03:01:00:01",
|
|
+ .parent = &ssam_node_root,
|
|
+};
|
|
+
|
|
/* Devices for Surface Book 2. */
|
|
static const struct software_node *ssam_node_group_sb2[] = {
|
|
&ssam_node_root,
|
|
+ &ssam_node_tmp_pprof,
|
|
NULL,
|
|
};
|
|
|
|
@@ -78,18 +85,21 @@ static const struct software_node *ssam_node_group_sb3[] = {
|
|
&ssam_node_bat_ac,
|
|
&ssam_node_bat_main,
|
|
&ssam_node_bat_sb3base,
|
|
+ &ssam_node_tmp_pprof,
|
|
NULL,
|
|
};
|
|
|
|
/* Devices for Surface Laptop 1. */
|
|
static const struct software_node *ssam_node_group_sl1[] = {
|
|
&ssam_node_root,
|
|
+ &ssam_node_tmp_pprof,
|
|
NULL,
|
|
};
|
|
|
|
/* Devices for Surface Laptop 2. */
|
|
static const struct software_node *ssam_node_group_sl2[] = {
|
|
&ssam_node_root,
|
|
+ &ssam_node_tmp_pprof,
|
|
NULL,
|
|
};
|
|
|
|
@@ -98,6 +108,7 @@ static const struct software_node *ssam_node_group_sl3[] = {
|
|
&ssam_node_root,
|
|
&ssam_node_bat_ac,
|
|
&ssam_node_bat_main,
|
|
+ &ssam_node_tmp_pprof,
|
|
NULL,
|
|
};
|
|
|
|
@@ -106,18 +117,21 @@ static const struct software_node *ssam_node_group_slg1[] = {
|
|
&ssam_node_root,
|
|
&ssam_node_bat_ac,
|
|
&ssam_node_bat_main,
|
|
+ &ssam_node_tmp_pprof,
|
|
NULL,
|
|
};
|
|
|
|
/* Devices for Surface Pro 5. */
|
|
static const struct software_node *ssam_node_group_sp5[] = {
|
|
&ssam_node_root,
|
|
+ &ssam_node_tmp_pprof,
|
|
NULL,
|
|
};
|
|
|
|
/* Devices for Surface Pro 6. */
|
|
static const struct software_node *ssam_node_group_sp6[] = {
|
|
&ssam_node_root,
|
|
+ &ssam_node_tmp_pprof,
|
|
NULL,
|
|
};
|
|
|
|
@@ -126,6 +140,7 @@ static const struct software_node *ssam_node_group_sp7[] = {
|
|
&ssam_node_root,
|
|
&ssam_node_bat_ac,
|
|
&ssam_node_bat_main,
|
|
+ &ssam_node_tmp_pprof,
|
|
NULL,
|
|
};
|
|
|
|
--
|
|
2.32.0
|
|
|
|
From d7dedaa2b75bb813a9fdd78d7a08827f67f0ea31 Mon Sep 17 00:00:00 2001
|
|
From: Maximilian Luz <luzmaximilian@gmail.com>
|
|
Date: Fri, 12 Feb 2021 12:54:38 +0100
|
|
Subject: [PATCH] platform/surface: aggregator_registry: Add DTX device
|
|
|
|
Add the detachment system (DTX) SSAM device for the Surface Book 3. This
|
|
device is accessible under the base (TC=0x11) subsystem.
|
|
|
|
Signed-off-by: Maximilian Luz <luzmaximilian@gmail.com>
|
|
Link: https://lore.kernel.org/r/20210212115439.1525216-6-luzmaximilian@gmail.com
|
|
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
|
|
Patchset: surface-sam
|
|
---
|
|
drivers/platform/surface/surface_aggregator_registry.c | 7 +++++++
|
|
1 file changed, 7 insertions(+)
|
|
|
|
diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c
|
|
index 33904613dd4b..dc044d06828b 100644
|
|
--- a/drivers/platform/surface/surface_aggregator_registry.c
|
|
+++ b/drivers/platform/surface/surface_aggregator_registry.c
|
|
@@ -71,6 +71,12 @@ static const struct software_node ssam_node_tmp_pprof = {
|
|
.parent = &ssam_node_root,
|
|
};
|
|
|
|
+/* DTX / detachment-system device (Surface Book 3). */
|
|
+static const struct software_node ssam_node_bas_dtx = {
|
|
+ .name = "ssam:01:11:01:00:00",
|
|
+ .parent = &ssam_node_root,
|
|
+};
|
|
+
|
|
/* Devices for Surface Book 2. */
|
|
static const struct software_node *ssam_node_group_sb2[] = {
|
|
&ssam_node_root,
|
|
@@ -86,6 +92,7 @@ static const struct software_node *ssam_node_group_sb3[] = {
|
|
&ssam_node_bat_main,
|
|
&ssam_node_bat_sb3base,
|
|
&ssam_node_tmp_pprof,
|
|
+ &ssam_node_bas_dtx,
|
|
NULL,
|
|
};
|
|
|
|
--
|
|
2.32.0
|
|
|
|
From 48e36688edec92d06ede33ddb78c708adbe4179f Mon Sep 17 00:00:00 2001
|
|
From: Maximilian Luz <luzmaximilian@gmail.com>
|
|
Date: Fri, 12 Feb 2021 12:54:39 +0100
|
|
Subject: [PATCH] platform/surface: aggregator_registry: Add HID subsystem
|
|
devices
|
|
|
|
Add HID subsystem (TC=0x15) devices. These devices need to be registered
|
|
for 7th-generation Surface models. On previous generations, these
|
|
devices are either provided as platform devices via ACPI (Surface Laptop
|
|
1 and 2) or implemented as standard USB device.
|
|
|
|
Signed-off-by: Maximilian Luz <luzmaximilian@gmail.com>
|
|
Link: https://lore.kernel.org/r/20210212115439.1525216-7-luzmaximilian@gmail.com
|
|
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
|
|
Patchset: surface-sam
|
|
---
|
|
.../surface/surface_aggregator_registry.c | 49 +++++++++++++++++++
|
|
1 file changed, 49 insertions(+)
|
|
|
|
diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c
|
|
index dc044d06828b..caee90d135c5 100644
|
|
--- a/drivers/platform/surface/surface_aggregator_registry.c
|
|
+++ b/drivers/platform/surface/surface_aggregator_registry.c
|
|
@@ -77,6 +77,48 @@ static const struct software_node ssam_node_bas_dtx = {
|
|
.parent = &ssam_node_root,
|
|
};
|
|
|
|
+/* HID keyboard. */
|
|
+static const struct software_node ssam_node_hid_main_keyboard = {
|
|
+ .name = "ssam:01:15:02:01:00",
|
|
+ .parent = &ssam_node_root,
|
|
+};
|
|
+
|
|
+/* HID touchpad. */
|
|
+static const struct software_node ssam_node_hid_main_touchpad = {
|
|
+ .name = "ssam:01:15:02:03:00",
|
|
+ .parent = &ssam_node_root,
|
|
+};
|
|
+
|
|
+/* HID device instance 5 (unknown HID device). */
|
|
+static const struct software_node ssam_node_hid_main_iid5 = {
|
|
+ .name = "ssam:01:15:02:05:00",
|
|
+ .parent = &ssam_node_root,
|
|
+};
|
|
+
|
|
+/* HID keyboard (base hub). */
|
|
+static const struct software_node ssam_node_hid_base_keyboard = {
|
|
+ .name = "ssam:01:15:02:01:00",
|
|
+ .parent = &ssam_node_hub_base,
|
|
+};
|
|
+
|
|
+/* HID touchpad (base hub). */
|
|
+static const struct software_node ssam_node_hid_base_touchpad = {
|
|
+ .name = "ssam:01:15:02:03:00",
|
|
+ .parent = &ssam_node_hub_base,
|
|
+};
|
|
+
|
|
+/* HID device instance 5 (unknown HID device, base hub). */
|
|
+static const struct software_node ssam_node_hid_base_iid5 = {
|
|
+ .name = "ssam:01:15:02:05:00",
|
|
+ .parent = &ssam_node_hub_base,
|
|
+};
|
|
+
|
|
+/* HID device instance 6 (unknown HID device, base hub). */
|
|
+static const struct software_node ssam_node_hid_base_iid6 = {
|
|
+ .name = "ssam:01:15:02:06:00",
|
|
+ .parent = &ssam_node_hub_base,
|
|
+};
|
|
+
|
|
/* Devices for Surface Book 2. */
|
|
static const struct software_node *ssam_node_group_sb2[] = {
|
|
&ssam_node_root,
|
|
@@ -93,6 +135,10 @@ static const struct software_node *ssam_node_group_sb3[] = {
|
|
&ssam_node_bat_sb3base,
|
|
&ssam_node_tmp_pprof,
|
|
&ssam_node_bas_dtx,
|
|
+ &ssam_node_hid_base_keyboard,
|
|
+ &ssam_node_hid_base_touchpad,
|
|
+ &ssam_node_hid_base_iid5,
|
|
+ &ssam_node_hid_base_iid6,
|
|
NULL,
|
|
};
|
|
|
|
@@ -116,6 +162,9 @@ static const struct software_node *ssam_node_group_sl3[] = {
|
|
&ssam_node_bat_ac,
|
|
&ssam_node_bat_main,
|
|
&ssam_node_tmp_pprof,
|
|
+ &ssam_node_hid_main_keyboard,
|
|
+ &ssam_node_hid_main_touchpad,
|
|
+ &ssam_node_hid_main_iid5,
|
|
NULL,
|
|
};
|
|
|
|
--
|
|
2.32.0
|
|
|
|
From 7178cfe4327a0e70115450be0fe768f5023fe1c3 Mon Sep 17 00:00:00 2001
|
|
From: Maximilian Luz <luzmaximilian@gmail.com>
|
|
Date: Thu, 11 Feb 2021 21:17:03 +0100
|
|
Subject: [PATCH] platform/surface: Add platform profile driver
|
|
|
|
Add a driver to provide platform profile support on 5th- and later
|
|
generation Microsoft Surface devices with a Surface System Aggregator
|
|
Module. On those devices, the platform profile can be used to influence
|
|
cooling behavior and power consumption.
|
|
|
|
For example, the default 'quiet' profile limits fan noise and in turn
|
|
sacrifices performance of the discrete GPU found on Surface Books. Its
|
|
full performance can only be unlocked on the 'performance' profile.
|
|
|
|
Signed-off-by: Maximilian Luz <luzmaximilian@gmail.com>
|
|
Reviewed-by: Hans de Goede <hdegoede@redhat.com>
|
|
Link: https://lore.kernel.org/r/20210211201703.658240-5-luzmaximilian@gmail.com
|
|
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
|
|
Patchset: surface-sam
|
|
---
|
|
MAINTAINERS | 6 +
|
|
drivers/platform/surface/Kconfig | 22 ++
|
|
drivers/platform/surface/Makefile | 1 +
|
|
.../surface/surface_platform_profile.c | 190 ++++++++++++++++++
|
|
4 files changed, 219 insertions(+)
|
|
create mode 100644 drivers/platform/surface/surface_platform_profile.c
|
|
|
|
diff --git a/MAINTAINERS b/MAINTAINERS
|
|
index f6c524630575..fce5cdcefc0b 100644
|
|
--- a/MAINTAINERS
|
|
+++ b/MAINTAINERS
|
|
@@ -11889,6 +11889,12 @@ L: platform-driver-x86@vger.kernel.org
|
|
S: Maintained
|
|
F: drivers/platform/surface/surface_hotplug.c
|
|
|
|
+MICROSOFT SURFACE PLATFORM PROFILE DRIVER
|
|
+M: Maximilian Luz <luzmaximilian@gmail.com>
|
|
+L: platform-driver-x86@vger.kernel.org
|
|
+S: Maintained
|
|
+F: drivers/platform/surface/surface_platform_profile.c
|
|
+
|
|
MICROSOFT SURFACE PRO 3 BUTTON DRIVER
|
|
M: Chen Yu <yu.c.chen@intel.com>
|
|
L: platform-driver-x86@vger.kernel.org
|
|
diff --git a/drivers/platform/surface/Kconfig b/drivers/platform/surface/Kconfig
|
|
index c51c55204b5f..6fb304da845f 100644
|
|
--- a/drivers/platform/surface/Kconfig
|
|
+++ b/drivers/platform/surface/Kconfig
|
|
@@ -139,6 +139,28 @@ config SURFACE_HOTPLUG
|
|
Select M or Y here, if you want to (fully) support hot-plugging of
|
|
dGPU devices on the Surface Book 2 and/or 3 during D3cold.
|
|
|
|
+config SURFACE_PLATFORM_PROFILE
|
|
+ tristate "Surface Platform Profile Driver"
|
|
+ depends on SURFACE_AGGREGATOR_REGISTRY
|
|
+ select ACPI_PLATFORM_PROFILE
|
|
+ help
|
|
+ Provides support for the ACPI platform profile on 5th- and later
|
|
+ generation Microsoft Surface devices.
|
|
+
|
|
+ More specifically, this driver provides ACPI platform profile support
|
|
+ on Microsoft Surface devices with a Surface System Aggregator Module
|
|
+ (SSAM) connected via the Surface Serial Hub (SSH / SAM-over-SSH). In
|
|
+ other words, this driver provides platform profile support on the
|
|
+ Surface Pro 5, Surface Book 2, Surface Laptop, Surface Laptop Go and
|
|
+ later. On those devices, the platform profile can significantly
|
|
+ influence cooling behavior, e.g. setting it to 'quiet' (default) or
|
|
+ 'low-power' can significantly limit performance of the discrete GPU on
|
|
+ Surface Books, while in turn leading to lower power consumption and/or
|
|
+ less fan noise.
|
|
+
|
|
+ Select M or Y here, if you want to include ACPI platform profile
|
|
+ support on the above mentioned devices.
|
|
+
|
|
config SURFACE_PRO3_BUTTON
|
|
tristate "Power/home/volume buttons driver for Microsoft Surface Pro 3/4 tablet"
|
|
depends on INPUT
|
|
diff --git a/drivers/platform/surface/Makefile b/drivers/platform/surface/Makefile
|
|
index ed12676f06e6..f7187bae1729 100644
|
|
--- a/drivers/platform/surface/Makefile
|
|
+++ b/drivers/platform/surface/Makefile
|
|
@@ -14,4 +14,5 @@ obj-$(CONFIG_SURFACE_AGGREGATOR_REGISTRY) += surface_aggregator_registry.o
|
|
obj-$(CONFIG_SURFACE_BOOK1_DGPU_SWITCH) += surfacebook1_dgpu_switch.o
|
|
obj-$(CONFIG_SURFACE_GPE) += surface_gpe.o
|
|
obj-$(CONFIG_SURFACE_HOTPLUG) += surface_hotplug.o
|
|
+obj-$(CONFIG_SURFACE_PLATFORM_PROFILE) += surface_platform_profile.o
|
|
obj-$(CONFIG_SURFACE_PRO3_BUTTON) += surfacepro3_button.o
|
|
diff --git a/drivers/platform/surface/surface_platform_profile.c b/drivers/platform/surface/surface_platform_profile.c
|
|
new file mode 100644
|
|
index 000000000000..0081b01a5b0f
|
|
--- /dev/null
|
|
+++ b/drivers/platform/surface/surface_platform_profile.c
|
|
@@ -0,0 +1,190 @@
|
|
+// SPDX-License-Identifier: GPL-2.0+
|
|
+/*
|
|
+ * Surface Platform Profile / Performance Mode driver for Surface System
|
|
+ * Aggregator Module (thermal subsystem).
|
|
+ *
|
|
+ * Copyright (C) 2021 Maximilian Luz <luzmaximilian@gmail.com>
|
|
+ */
|
|
+
|
|
+#include <asm/unaligned.h>
|
|
+#include <linux/kernel.h>
|
|
+#include <linux/module.h>
|
|
+#include <linux/platform_profile.h>
|
|
+#include <linux/types.h>
|
|
+
|
|
+#include <linux/surface_aggregator/device.h>
|
|
+
|
|
+enum ssam_tmp_profile {
|
|
+ SSAM_TMP_PROFILE_NORMAL = 1,
|
|
+ SSAM_TMP_PROFILE_BATTERY_SAVER = 2,
|
|
+ SSAM_TMP_PROFILE_BETTER_PERFORMANCE = 3,
|
|
+ SSAM_TMP_PROFILE_BEST_PERFORMANCE = 4,
|
|
+};
|
|
+
|
|
+struct ssam_tmp_profile_info {
|
|
+ __le32 profile;
|
|
+ __le16 unknown1;
|
|
+ __le16 unknown2;
|
|
+} __packed;
|
|
+
|
|
+struct ssam_tmp_profile_device {
|
|
+ struct ssam_device *sdev;
|
|
+ struct platform_profile_handler handler;
|
|
+};
|
|
+
|
|
+static SSAM_DEFINE_SYNC_REQUEST_CL_R(__ssam_tmp_profile_get, struct ssam_tmp_profile_info, {
|
|
+ .target_category = SSAM_SSH_TC_TMP,
|
|
+ .command_id = 0x02,
|
|
+});
|
|
+
|
|
+static SSAM_DEFINE_SYNC_REQUEST_CL_W(__ssam_tmp_profile_set, __le32, {
|
|
+ .target_category = SSAM_SSH_TC_TMP,
|
|
+ .command_id = 0x03,
|
|
+});
|
|
+
|
|
+static int ssam_tmp_profile_get(struct ssam_device *sdev, enum ssam_tmp_profile *p)
|
|
+{
|
|
+ struct ssam_tmp_profile_info info;
|
|
+ int status;
|
|
+
|
|
+ status = ssam_retry(__ssam_tmp_profile_get, sdev, &info);
|
|
+ if (status < 0)
|
|
+ return status;
|
|
+
|
|
+ *p = le32_to_cpu(info.profile);
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int ssam_tmp_profile_set(struct ssam_device *sdev, enum ssam_tmp_profile p)
|
|
+{
|
|
+ __le32 profile_le = cpu_to_le32(p);
|
|
+
|
|
+ return ssam_retry(__ssam_tmp_profile_set, sdev, &profile_le);
|
|
+}
|
|
+
|
|
+static int convert_ssam_to_profile(struct ssam_device *sdev, enum ssam_tmp_profile p)
|
|
+{
|
|
+ switch (p) {
|
|
+ case SSAM_TMP_PROFILE_NORMAL:
|
|
+ return PLATFORM_PROFILE_BALANCED;
|
|
+
|
|
+ case SSAM_TMP_PROFILE_BATTERY_SAVER:
|
|
+ return PLATFORM_PROFILE_LOW_POWER;
|
|
+
|
|
+ case SSAM_TMP_PROFILE_BETTER_PERFORMANCE:
|
|
+ return PLATFORM_PROFILE_BALANCED_PERFORMANCE;
|
|
+
|
|
+ case SSAM_TMP_PROFILE_BEST_PERFORMANCE:
|
|
+ return PLATFORM_PROFILE_PERFORMANCE;
|
|
+
|
|
+ default:
|
|
+ dev_err(&sdev->dev, "invalid performance profile: %d", p);
|
|
+ return -EINVAL;
|
|
+ }
|
|
+}
|
|
+
|
|
+static int convert_profile_to_ssam(struct ssam_device *sdev, enum platform_profile_option p)
|
|
+{
|
|
+ switch (p) {
|
|
+ case PLATFORM_PROFILE_LOW_POWER:
|
|
+ return SSAM_TMP_PROFILE_BATTERY_SAVER;
|
|
+
|
|
+ case PLATFORM_PROFILE_BALANCED:
|
|
+ return SSAM_TMP_PROFILE_NORMAL;
|
|
+
|
|
+ case PLATFORM_PROFILE_BALANCED_PERFORMANCE:
|
|
+ return SSAM_TMP_PROFILE_BETTER_PERFORMANCE;
|
|
+
|
|
+ case PLATFORM_PROFILE_PERFORMANCE:
|
|
+ return SSAM_TMP_PROFILE_BEST_PERFORMANCE;
|
|
+
|
|
+ default:
|
|
+ /* This should have already been caught by platform_profile_store(). */
|
|
+ WARN(true, "unsupported platform profile");
|
|
+ return -EOPNOTSUPP;
|
|
+ }
|
|
+}
|
|
+
|
|
+static int ssam_platform_profile_get(struct platform_profile_handler *pprof,
|
|
+ enum platform_profile_option *profile)
|
|
+{
|
|
+ struct ssam_tmp_profile_device *tpd;
|
|
+ enum ssam_tmp_profile tp;
|
|
+ int status;
|
|
+
|
|
+ tpd = container_of(pprof, struct ssam_tmp_profile_device, handler);
|
|
+
|
|
+ status = ssam_tmp_profile_get(tpd->sdev, &tp);
|
|
+ if (status)
|
|
+ return status;
|
|
+
|
|
+ status = convert_ssam_to_profile(tpd->sdev, tp);
|
|
+ if (status < 0)
|
|
+ return status;
|
|
+
|
|
+ *profile = status;
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int ssam_platform_profile_set(struct platform_profile_handler *pprof,
|
|
+ enum platform_profile_option profile)
|
|
+{
|
|
+ struct ssam_tmp_profile_device *tpd;
|
|
+ int tp;
|
|
+
|
|
+ tpd = container_of(pprof, struct ssam_tmp_profile_device, handler);
|
|
+
|
|
+ tp = convert_profile_to_ssam(tpd->sdev, profile);
|
|
+ if (tp < 0)
|
|
+ return tp;
|
|
+
|
|
+ return ssam_tmp_profile_set(tpd->sdev, tp);
|
|
+}
|
|
+
|
|
+static int surface_platform_profile_probe(struct ssam_device *sdev)
|
|
+{
|
|
+ struct ssam_tmp_profile_device *tpd;
|
|
+
|
|
+ tpd = devm_kzalloc(&sdev->dev, sizeof(*tpd), GFP_KERNEL);
|
|
+ if (!tpd)
|
|
+ return -ENOMEM;
|
|
+
|
|
+ tpd->sdev = sdev;
|
|
+
|
|
+ tpd->handler.profile_get = ssam_platform_profile_get;
|
|
+ tpd->handler.profile_set = ssam_platform_profile_set;
|
|
+
|
|
+ set_bit(PLATFORM_PROFILE_LOW_POWER, tpd->handler.choices);
|
|
+ set_bit(PLATFORM_PROFILE_BALANCED, tpd->handler.choices);
|
|
+ set_bit(PLATFORM_PROFILE_BALANCED_PERFORMANCE, tpd->handler.choices);
|
|
+ set_bit(PLATFORM_PROFILE_PERFORMANCE, tpd->handler.choices);
|
|
+
|
|
+ platform_profile_register(&tpd->handler);
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static void surface_platform_profile_remove(struct ssam_device *sdev)
|
|
+{
|
|
+ platform_profile_remove();
|
|
+}
|
|
+
|
|
+static const struct ssam_device_id ssam_platform_profile_match[] = {
|
|
+ { SSAM_SDEV(TMP, 0x01, 0x00, 0x01) },
|
|
+ { },
|
|
+};
|
|
+MODULE_DEVICE_TABLE(ssam, ssam_platform_profile_match);
|
|
+
|
|
+static struct ssam_device_driver surface_platform_profile = {
|
|
+ .probe = surface_platform_profile_probe,
|
|
+ .remove = surface_platform_profile_remove,
|
|
+ .match_table = ssam_platform_profile_match,
|
|
+ .driver = {
|
|
+ .name = "surface_platform_profile",
|
|
+ .probe_type = PROBE_PREFER_ASYNCHRONOUS,
|
|
+ },
|
|
+};
|
|
+module_ssam_device_driver(surface_platform_profile);
|
|
+
|
|
+MODULE_AUTHOR("Maximilian Luz <luzmaximilian@gmail.com>");
|
|
+MODULE_DESCRIPTION("Platform Profile Support for Surface System Aggregator Module");
|
|
+MODULE_LICENSE("GPL");
|
|
--
|
|
2.32.0
|
|
|
|
From 9b2de618ae2ae75163dd8ae3a77704a58aaad345 Mon Sep 17 00:00:00 2001
|
|
From: Maximilian Luz <luzmaximilian@gmail.com>
|
|
Date: Thu, 4 Mar 2021 20:05:24 +0100
|
|
Subject: [PATCH] platform/surface: aggregator: Make SSAM_DEFINE_SYNC_REQUEST_x
|
|
define static functions
|
|
|
|
The SSAM_DEFINE_SYNC_REQUEST_x() macros are intended to reduce
|
|
boiler-plate code for SSAM request definitions by defining a wrapper
|
|
function for the specified request. The client device variants of those
|
|
macros, i.e. SSAM_DEFINE_SYNC_REQUEST_CL_x() in particular rely on the
|
|
multi-device (MD) variants, e.g.:
|
|
|
|
#define SSAM_DEFINE_SYNC_REQUEST_CL_R(name, rtype, spec...) \
|
|
SSAM_DEFINE_SYNC_REQUEST_MD_R(__raw_##name, rtype, spec) \
|
|
int name(struct ssam_device *sdev, rtype *ret) \
|
|
{ \
|
|
return __raw_##name(sdev->ctrl, sdev->uid.target, \
|
|
sdev->uid.instance, ret); \
|
|
}
|
|
|
|
This now creates the problem that it is not possible to declare the
|
|
generated functions static via
|
|
|
|
static SSAM_DEFINE_SYNC_REQUEST_CL_R(...)
|
|
|
|
as this will only apply to the function defined by the multi-device
|
|
macro, i.e. SSAM_DEFINE_SYNC_REQUEST_MD_R(). Thus compiling with
|
|
`-Wmissing-prototypes' rightfully complains that there is a 'static'
|
|
keyword missing.
|
|
|
|
To solve this, make all SSAM_DEFINE_SYNC_REQUEST_x() macros define
|
|
static functions. Non-client-device macros are also changed for
|
|
consistency. In general, we expect those functions to be only used
|
|
locally in the respective drivers for the corresponding interfaces, so
|
|
having to define a wrapper function to be able to export this should be
|
|
the odd case out.
|
|
|
|
Reported-by: kernel test robot <lkp@intel.com>
|
|
Fixes: b78b4982d763 ("platform/surface: Add platform profile driver")
|
|
Signed-off-by: Maximilian Luz <luzmaximilian@gmail.com>
|
|
Link: https://lore.kernel.org/r/20210304190524.1172197-1-luzmaximilian@gmail.com
|
|
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
|
|
Patchset: surface-sam
|
|
---
|
|
.../driver-api/surface_aggregator/client.rst | 4 +-
|
|
.../platform/surface/aggregator/controller.c | 10 +--
|
|
.../surface/surface_aggregator_registry.c | 2 +-
|
|
.../surface/surface_platform_profile.c | 4 +-
|
|
include/linux/surface_aggregator/controller.h | 74 +++++++++----------
|
|
include/linux/surface_aggregator/device.h | 31 ++++----
|
|
6 files changed, 63 insertions(+), 62 deletions(-)
|
|
|
|
diff --git a/Documentation/driver-api/surface_aggregator/client.rst b/Documentation/driver-api/surface_aggregator/client.rst
|
|
index 26d13085a117..e519d374c378 100644
|
|
--- a/Documentation/driver-api/surface_aggregator/client.rst
|
|
+++ b/Documentation/driver-api/surface_aggregator/client.rst
|
|
@@ -248,7 +248,7 @@ This example defines a function
|
|
|
|
.. code-block:: c
|
|
|
|
- int __ssam_tmp_perf_mode_set(struct ssam_controller *ctrl, const __le32 *arg);
|
|
+ static int __ssam_tmp_perf_mode_set(struct ssam_controller *ctrl, const __le32 *arg);
|
|
|
|
executing the specified request, with the controller passed in when calling
|
|
said function. In this example, the argument is provided via the ``arg``
|
|
@@ -296,7 +296,7 @@ This invocation of the macro defines a function
|
|
|
|
.. code-block:: c
|
|
|
|
- int ssam_bat_get_sta(struct ssam_device *sdev, __le32 *ret);
|
|
+ static int ssam_bat_get_sta(struct ssam_device *sdev, __le32 *ret);
|
|
|
|
executing the specified request, using the device IDs and controller given
|
|
in the client device. The full list of such macros for client devices is:
|
|
diff --git a/drivers/platform/surface/aggregator/controller.c b/drivers/platform/surface/aggregator/controller.c
|
|
index d68d51fb24ff..d006a36b2924 100644
|
|
--- a/drivers/platform/surface/aggregator/controller.c
|
|
+++ b/drivers/platform/surface/aggregator/controller.c
|
|
@@ -1750,35 +1750,35 @@ EXPORT_SYMBOL_GPL(ssam_request_sync_with_buffer);
|
|
|
|
/* -- Internal SAM requests. ------------------------------------------------ */
|
|
|
|
-static SSAM_DEFINE_SYNC_REQUEST_R(ssam_ssh_get_firmware_version, __le32, {
|
|
+SSAM_DEFINE_SYNC_REQUEST_R(ssam_ssh_get_firmware_version, __le32, {
|
|
.target_category = SSAM_SSH_TC_SAM,
|
|
.target_id = 0x01,
|
|
.command_id = 0x13,
|
|
.instance_id = 0x00,
|
|
});
|
|
|
|
-static SSAM_DEFINE_SYNC_REQUEST_R(ssam_ssh_notif_display_off, u8, {
|
|
+SSAM_DEFINE_SYNC_REQUEST_R(ssam_ssh_notif_display_off, u8, {
|
|
.target_category = SSAM_SSH_TC_SAM,
|
|
.target_id = 0x01,
|
|
.command_id = 0x15,
|
|
.instance_id = 0x00,
|
|
});
|
|
|
|
-static SSAM_DEFINE_SYNC_REQUEST_R(ssam_ssh_notif_display_on, u8, {
|
|
+SSAM_DEFINE_SYNC_REQUEST_R(ssam_ssh_notif_display_on, u8, {
|
|
.target_category = SSAM_SSH_TC_SAM,
|
|
.target_id = 0x01,
|
|
.command_id = 0x16,
|
|
.instance_id = 0x00,
|
|
});
|
|
|
|
-static SSAM_DEFINE_SYNC_REQUEST_R(ssam_ssh_notif_d0_exit, u8, {
|
|
+SSAM_DEFINE_SYNC_REQUEST_R(ssam_ssh_notif_d0_exit, u8, {
|
|
.target_category = SSAM_SSH_TC_SAM,
|
|
.target_id = 0x01,
|
|
.command_id = 0x33,
|
|
.instance_id = 0x00,
|
|
});
|
|
|
|
-static SSAM_DEFINE_SYNC_REQUEST_R(ssam_ssh_notif_d0_entry, u8, {
|
|
+SSAM_DEFINE_SYNC_REQUEST_R(ssam_ssh_notif_d0_entry, u8, {
|
|
.target_category = SSAM_SSH_TC_SAM,
|
|
.target_id = 0x01,
|
|
.command_id = 0x34,
|
|
diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c
|
|
index caee90d135c5..cdb4a95af3e8 100644
|
|
--- a/drivers/platform/surface/surface_aggregator_registry.c
|
|
+++ b/drivers/platform/surface/surface_aggregator_registry.c
|
|
@@ -302,7 +302,7 @@ struct ssam_base_hub {
|
|
struct ssam_event_notifier notif;
|
|
};
|
|
|
|
-static SSAM_DEFINE_SYNC_REQUEST_R(ssam_bas_query_opmode, u8, {
|
|
+SSAM_DEFINE_SYNC_REQUEST_R(ssam_bas_query_opmode, u8, {
|
|
.target_category = SSAM_SSH_TC_BAS,
|
|
.target_id = 0x01,
|
|
.command_id = 0x0d,
|
|
diff --git a/drivers/platform/surface/surface_platform_profile.c b/drivers/platform/surface/surface_platform_profile.c
|
|
index 0081b01a5b0f..6373d3b5eb7f 100644
|
|
--- a/drivers/platform/surface/surface_platform_profile.c
|
|
+++ b/drivers/platform/surface/surface_platform_profile.c
|
|
@@ -32,12 +32,12 @@ struct ssam_tmp_profile_device {
|
|
struct platform_profile_handler handler;
|
|
};
|
|
|
|
-static SSAM_DEFINE_SYNC_REQUEST_CL_R(__ssam_tmp_profile_get, struct ssam_tmp_profile_info, {
|
|
+SSAM_DEFINE_SYNC_REQUEST_CL_R(__ssam_tmp_profile_get, struct ssam_tmp_profile_info, {
|
|
.target_category = SSAM_SSH_TC_TMP,
|
|
.command_id = 0x02,
|
|
});
|
|
|
|
-static SSAM_DEFINE_SYNC_REQUEST_CL_W(__ssam_tmp_profile_set, __le32, {
|
|
+SSAM_DEFINE_SYNC_REQUEST_CL_W(__ssam_tmp_profile_set, __le32, {
|
|
.target_category = SSAM_SSH_TC_TMP,
|
|
.command_id = 0x03,
|
|
});
|
|
diff --git a/include/linux/surface_aggregator/controller.h b/include/linux/surface_aggregator/controller.h
|
|
index f4b1ba887384..0806796eabcb 100644
|
|
--- a/include/linux/surface_aggregator/controller.h
|
|
+++ b/include/linux/surface_aggregator/controller.h
|
|
@@ -344,16 +344,16 @@ struct ssam_request_spec_md {
|
|
* request has been fully completed. The required transport buffer will be
|
|
* allocated on the stack.
|
|
*
|
|
- * The generated function is defined as ``int name(struct ssam_controller
|
|
- * *ctrl)``, returning the status of the request, which is zero on success and
|
|
- * negative on failure. The ``ctrl`` parameter is the controller via which the
|
|
- * request is being sent.
|
|
+ * The generated function is defined as ``static int name(struct
|
|
+ * ssam_controller *ctrl)``, returning the status of the request, which is
|
|
+ * zero on success and negative on failure. The ``ctrl`` parameter is the
|
|
+ * controller via which the request is being sent.
|
|
*
|
|
* Refer to ssam_request_sync_onstack() for more details on the behavior of
|
|
* the generated function.
|
|
*/
|
|
#define SSAM_DEFINE_SYNC_REQUEST_N(name, spec...) \
|
|
- int name(struct ssam_controller *ctrl) \
|
|
+ static int name(struct ssam_controller *ctrl) \
|
|
{ \
|
|
struct ssam_request_spec s = (struct ssam_request_spec)spec; \
|
|
struct ssam_request rqst; \
|
|
@@ -383,17 +383,17 @@ struct ssam_request_spec_md {
|
|
* returning once the request has been fully completed. The required transport
|
|
* buffer will be allocated on the stack.
|
|
*
|
|
- * The generated function is defined as ``int name(struct ssam_controller
|
|
- * *ctrl, const atype *arg)``, returning the status of the request, which is
|
|
- * zero on success and negative on failure. The ``ctrl`` parameter is the
|
|
- * controller via which the request is sent. The request argument is specified
|
|
- * via the ``arg`` pointer.
|
|
+ * The generated function is defined as ``static int name(struct
|
|
+ * ssam_controller *ctrl, const atype *arg)``, returning the status of the
|
|
+ * request, which is zero on success and negative on failure. The ``ctrl``
|
|
+ * parameter is the controller via which the request is sent. The request
|
|
+ * argument is specified via the ``arg`` pointer.
|
|
*
|
|
* Refer to ssam_request_sync_onstack() for more details on the behavior of
|
|
* the generated function.
|
|
*/
|
|
#define SSAM_DEFINE_SYNC_REQUEST_W(name, atype, spec...) \
|
|
- int name(struct ssam_controller *ctrl, const atype *arg) \
|
|
+ static int name(struct ssam_controller *ctrl, const atype *arg) \
|
|
{ \
|
|
struct ssam_request_spec s = (struct ssam_request_spec)spec; \
|
|
struct ssam_request rqst; \
|
|
@@ -424,17 +424,17 @@ struct ssam_request_spec_md {
|
|
* request itself, returning once the request has been fully completed. The
|
|
* required transport buffer will be allocated on the stack.
|
|
*
|
|
- * The generated function is defined as ``int name(struct ssam_controller
|
|
- * *ctrl, rtype *ret)``, returning the status of the request, which is zero on
|
|
- * success and negative on failure. The ``ctrl`` parameter is the controller
|
|
- * via which the request is sent. The request's return value is written to the
|
|
- * memory pointed to by the ``ret`` parameter.
|
|
+ * The generated function is defined as ``static int name(struct
|
|
+ * ssam_controller *ctrl, rtype *ret)``, returning the status of the request,
|
|
+ * which is zero on success and negative on failure. The ``ctrl`` parameter is
|
|
+ * the controller via which the request is sent. The request's return value is
|
|
+ * written to the memory pointed to by the ``ret`` parameter.
|
|
*
|
|
* Refer to ssam_request_sync_onstack() for more details on the behavior of
|
|
* the generated function.
|
|
*/
|
|
#define SSAM_DEFINE_SYNC_REQUEST_R(name, rtype, spec...) \
|
|
- int name(struct ssam_controller *ctrl, rtype *ret) \
|
|
+ static int name(struct ssam_controller *ctrl, rtype *ret) \
|
|
{ \
|
|
struct ssam_request_spec s = (struct ssam_request_spec)spec; \
|
|
struct ssam_request rqst; \
|
|
@@ -483,17 +483,17 @@ struct ssam_request_spec_md {
|
|
* returning once the request has been fully completed. The required transport
|
|
* buffer will be allocated on the stack.
|
|
*
|
|
- * The generated function is defined as ``int name(struct ssam_controller
|
|
- * *ctrl, u8 tid, u8 iid)``, returning the status of the request, which is
|
|
- * zero on success and negative on failure. The ``ctrl`` parameter is the
|
|
- * controller via which the request is sent, ``tid`` the target ID for the
|
|
- * request, and ``iid`` the instance ID.
|
|
+ * The generated function is defined as ``static int name(struct
|
|
+ * ssam_controller *ctrl, u8 tid, u8 iid)``, returning the status of the
|
|
+ * request, which is zero on success and negative on failure. The ``ctrl``
|
|
+ * parameter is the controller via which the request is sent, ``tid`` the
|
|
+ * target ID for the request, and ``iid`` the instance ID.
|
|
*
|
|
* Refer to ssam_request_sync_onstack() for more details on the behavior of
|
|
* the generated function.
|
|
*/
|
|
#define SSAM_DEFINE_SYNC_REQUEST_MD_N(name, spec...) \
|
|
- int name(struct ssam_controller *ctrl, u8 tid, u8 iid) \
|
|
+ static int name(struct ssam_controller *ctrl, u8 tid, u8 iid) \
|
|
{ \
|
|
struct ssam_request_spec_md s = (struct ssam_request_spec_md)spec; \
|
|
struct ssam_request rqst; \
|
|
@@ -524,18 +524,18 @@ struct ssam_request_spec_md {
|
|
* the request itself, returning once the request has been fully completed.
|
|
* The required transport buffer will be allocated on the stack.
|
|
*
|
|
- * The generated function is defined as ``int name(struct ssam_controller
|
|
- * *ctrl, u8 tid, u8 iid, const atype *arg)``, returning the status of the
|
|
- * request, which is zero on success and negative on failure. The ``ctrl``
|
|
- * parameter is the controller via which the request is sent, ``tid`` the
|
|
- * target ID for the request, and ``iid`` the instance ID. The request argument
|
|
- * is specified via the ``arg`` pointer.
|
|
+ * The generated function is defined as ``static int name(struct
|
|
+ * ssam_controller *ctrl, u8 tid, u8 iid, const atype *arg)``, returning the
|
|
+ * status of the request, which is zero on success and negative on failure.
|
|
+ * The ``ctrl`` parameter is the controller via which the request is sent,
|
|
+ * ``tid`` the target ID for the request, and ``iid`` the instance ID. The
|
|
+ * request argument is specified via the ``arg`` pointer.
|
|
*
|
|
* Refer to ssam_request_sync_onstack() for more details on the behavior of
|
|
* the generated function.
|
|
*/
|
|
#define SSAM_DEFINE_SYNC_REQUEST_MD_W(name, atype, spec...) \
|
|
- int name(struct ssam_controller *ctrl, u8 tid, u8 iid, const atype *arg)\
|
|
+ static int name(struct ssam_controller *ctrl, u8 tid, u8 iid, const atype *arg) \
|
|
{ \
|
|
struct ssam_request_spec_md s = (struct ssam_request_spec_md)spec; \
|
|
struct ssam_request rqst; \
|
|
@@ -567,18 +567,18 @@ struct ssam_request_spec_md {
|
|
* execution of the request itself, returning once the request has been fully
|
|
* completed. The required transport buffer will be allocated on the stack.
|
|
*
|
|
- * The generated function is defined as ``int name(struct ssam_controller
|
|
- * *ctrl, u8 tid, u8 iid, rtype *ret)``, returning the status of the request,
|
|
- * which is zero on success and negative on failure. The ``ctrl`` parameter is
|
|
- * the controller via which the request is sent, ``tid`` the target ID for the
|
|
- * request, and ``iid`` the instance ID. The request's return value is written
|
|
- * to the memory pointed to by the ``ret`` parameter.
|
|
+ * The generated function is defined as ``static int name(struct
|
|
+ * ssam_controller *ctrl, u8 tid, u8 iid, rtype *ret)``, returning the status
|
|
+ * of the request, which is zero on success and negative on failure. The
|
|
+ * ``ctrl`` parameter is the controller via which the request is sent, ``tid``
|
|
+ * the target ID for the request, and ``iid`` the instance ID. The request's
|
|
+ * return value is written to the memory pointed to by the ``ret`` parameter.
|
|
*
|
|
* Refer to ssam_request_sync_onstack() for more details on the behavior of
|
|
* the generated function.
|
|
*/
|
|
#define SSAM_DEFINE_SYNC_REQUEST_MD_R(name, rtype, spec...) \
|
|
- int name(struct ssam_controller *ctrl, u8 tid, u8 iid, rtype *ret) \
|
|
+ static int name(struct ssam_controller *ctrl, u8 tid, u8 iid, rtype *ret) \
|
|
{ \
|
|
struct ssam_request_spec_md s = (struct ssam_request_spec_md)spec; \
|
|
struct ssam_request rqst; \
|
|
diff --git a/include/linux/surface_aggregator/device.h b/include/linux/surface_aggregator/device.h
|
|
index 02f3e06c0a60..4441ad667c3f 100644
|
|
--- a/include/linux/surface_aggregator/device.h
|
|
+++ b/include/linux/surface_aggregator/device.h
|
|
@@ -336,17 +336,18 @@ void ssam_device_driver_unregister(struct ssam_device_driver *d);
|
|
* request has been fully completed. The required transport buffer will be
|
|
* allocated on the stack.
|
|
*
|
|
- * The generated function is defined as ``int name(struct ssam_device *sdev)``,
|
|
- * returning the status of the request, which is zero on success and negative
|
|
- * on failure. The ``sdev`` parameter specifies both the target device of the
|
|
- * request and by association the controller via which the request is sent.
|
|
+ * The generated function is defined as ``static int name(struct ssam_device
|
|
+ * *sdev)``, returning the status of the request, which is zero on success and
|
|
+ * negative on failure. The ``sdev`` parameter specifies both the target
|
|
+ * device of the request and by association the controller via which the
|
|
+ * request is sent.
|
|
*
|
|
* Refer to ssam_request_sync_onstack() for more details on the behavior of
|
|
* the generated function.
|
|
*/
|
|
#define SSAM_DEFINE_SYNC_REQUEST_CL_N(name, spec...) \
|
|
SSAM_DEFINE_SYNC_REQUEST_MD_N(__raw_##name, spec) \
|
|
- int name(struct ssam_device *sdev) \
|
|
+ static int name(struct ssam_device *sdev) \
|
|
{ \
|
|
return __raw_##name(sdev->ctrl, sdev->uid.target, \
|
|
sdev->uid.instance); \
|
|
@@ -368,19 +369,19 @@ void ssam_device_driver_unregister(struct ssam_device_driver *d);
|
|
* itself, returning once the request has been fully completed. The required
|
|
* transport buffer will be allocated on the stack.
|
|
*
|
|
- * The generated function is defined as ``int name(struct ssam_device *sdev,
|
|
- * const atype *arg)``, returning the status of the request, which is zero on
|
|
- * success and negative on failure. The ``sdev`` parameter specifies both the
|
|
- * target device of the request and by association the controller via which
|
|
- * the request is sent. The request's argument is specified via the ``arg``
|
|
- * pointer.
|
|
+ * The generated function is defined as ``static int name(struct ssam_device
|
|
+ * *sdev, const atype *arg)``, returning the status of the request, which is
|
|
+ * zero on success and negative on failure. The ``sdev`` parameter specifies
|
|
+ * both the target device of the request and by association the controller via
|
|
+ * which the request is sent. The request's argument is specified via the
|
|
+ * ``arg`` pointer.
|
|
*
|
|
* Refer to ssam_request_sync_onstack() for more details on the behavior of
|
|
* the generated function.
|
|
*/
|
|
#define SSAM_DEFINE_SYNC_REQUEST_CL_W(name, atype, spec...) \
|
|
SSAM_DEFINE_SYNC_REQUEST_MD_W(__raw_##name, atype, spec) \
|
|
- int name(struct ssam_device *sdev, const atype *arg) \
|
|
+ static int name(struct ssam_device *sdev, const atype *arg) \
|
|
{ \
|
|
return __raw_##name(sdev->ctrl, sdev->uid.target, \
|
|
sdev->uid.instance, arg); \
|
|
@@ -402,8 +403,8 @@ void ssam_device_driver_unregister(struct ssam_device_driver *d);
|
|
* itself, returning once the request has been fully completed. The required
|
|
* transport buffer will be allocated on the stack.
|
|
*
|
|
- * The generated function is defined as ``int name(struct ssam_device *sdev,
|
|
- * rtype *ret)``, returning the status of the request, which is zero on
|
|
+ * The generated function is defined as ``static int name(struct ssam_device
|
|
+ * *sdev, rtype *ret)``, returning the status of the request, which is zero on
|
|
* success and negative on failure. The ``sdev`` parameter specifies both the
|
|
* target device of the request and by association the controller via which
|
|
* the request is sent. The request's return value is written to the memory
|
|
@@ -414,7 +415,7 @@ void ssam_device_driver_unregister(struct ssam_device_driver *d);
|
|
*/
|
|
#define SSAM_DEFINE_SYNC_REQUEST_CL_R(name, rtype, spec...) \
|
|
SSAM_DEFINE_SYNC_REQUEST_MD_R(__raw_##name, rtype, spec) \
|
|
- int name(struct ssam_device *sdev, rtype *ret) \
|
|
+ static int name(struct ssam_device *sdev, rtype *ret) \
|
|
{ \
|
|
return __raw_##name(sdev->ctrl, sdev->uid.target, \
|
|
sdev->uid.instance, ret); \
|
|
--
|
|
2.32.0
|
|
|
|
From 19c9c3b67fc1ea05aea51df4959ea59056dc6e02 Mon Sep 17 00:00:00 2001
|
|
From: Maximilian Luz <luzmaximilian@gmail.com>
|
|
Date: Mon, 8 Mar 2021 19:48:17 +0100
|
|
Subject: [PATCH] platform/surface: Add DTX driver
|
|
|
|
The Microsoft Surface Book series devices consist of a so-called
|
|
clipboard part (containing the CPU, touchscreen, and primary battery)
|
|
and a base part (containing keyboard, secondary battery, and optional
|
|
discrete GPU). These parts can be separated, i.e. the clipboard can be
|
|
detached and used as tablet.
|
|
|
|
This detachment process is initiated by pressing a button. On the
|
|
Surface Book 2 and 3 (targeted with this commit), the Surface Aggregator
|
|
Module (i.e. the embedded controller on those devices) attempts to send
|
|
a notification to any listening client driver and waits for further
|
|
instructions (i.e. whether the detachment process should continue or be
|
|
aborted). If it does not receive a response in a certain time-frame, the
|
|
detachment process (by default) continues and the clipboard can be
|
|
physically separated. In other words, (by default and) without a driver,
|
|
the detachment process takes about 10 seconds to complete.
|
|
|
|
This commit introduces a driver for this detachment system (called DTX).
|
|
This driver allows a user-space daemon to control and influence the
|
|
detachment behavior. Specifically, it forwards any detachment requests
|
|
to user-space, allows user-space to make such requests itself, and
|
|
allows handling of those requests. Requests can be handled by either
|
|
aborting, continuing/allowing, or delaying (i.e. resetting the timeout
|
|
via a heartbeat commend). The user-space API is implemented via the
|
|
/dev/surface/dtx miscdevice.
|
|
|
|
In addition, user-space can change the default behavior on timeout from
|
|
allowing detachment to disallowing it, which is useful if the (optional)
|
|
discrete GPU is in use.
|
|
|
|
Furthermore, this driver allows user-space to receive notifications
|
|
about the state of the base, specifically when it is physically removed
|
|
(as opposed to detachment requested), in what manner it is connected
|
|
(i.e. in reverse-/tent-/studio- or laptop-mode), and what type of base
|
|
is connected. Based on this information, the driver also provides a
|
|
simple tablet-mode switch (aliasing all modes without keyboard access,
|
|
i.e. tablet-mode and studio-mode to its reported tablet-mode).
|
|
|
|
An implementation of such a user-space daemon, allowing configuration of
|
|
detachment behavior via scripts (e.g. safely unmounting USB devices
|
|
connected to the base before continuing) can be found at [1].
|
|
|
|
[1]: https://github.com/linux-surface/surface-dtx-daemon
|
|
|
|
Signed-off-by: Maximilian Luz <luzmaximilian@gmail.com>
|
|
Link: https://lore.kernel.org/r/20210308184819.437438-2-luzmaximilian@gmail.com
|
|
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
|
|
Patchset: surface-sam
|
|
---
|
|
.../userspace-api/ioctl/ioctl-number.rst | 2 +
|
|
MAINTAINERS | 7 +
|
|
drivers/platform/surface/Kconfig | 16 +
|
|
drivers/platform/surface/Makefile | 1 +
|
|
drivers/platform/surface/surface_dtx.c | 1201 +++++++++++++++++
|
|
include/uapi/linux/surface_aggregator/dtx.h | 146 ++
|
|
6 files changed, 1373 insertions(+)
|
|
create mode 100644 drivers/platform/surface/surface_dtx.c
|
|
create mode 100644 include/uapi/linux/surface_aggregator/dtx.h
|
|
|
|
diff --git a/Documentation/userspace-api/ioctl/ioctl-number.rst b/Documentation/userspace-api/ioctl/ioctl-number.rst
|
|
index 599bd4493944..1c28b8ef6677 100644
|
|
--- a/Documentation/userspace-api/ioctl/ioctl-number.rst
|
|
+++ b/Documentation/userspace-api/ioctl/ioctl-number.rst
|
|
@@ -327,6 +327,8 @@ Code Seq# Include File Comments
|
|
0xA4 00-1F uapi/asm/sgx.h <mailto:linux-sgx@vger.kernel.org>
|
|
0xA5 01 linux/surface_aggregator/cdev.h Microsoft Surface Platform System Aggregator
|
|
<mailto:luzmaximilian@gmail.com>
|
|
+0xA5 20-2F linux/surface_aggregator/dtx.h Microsoft Surface DTX driver
|
|
+ <mailto:luzmaximilian@gmail.com>
|
|
0xAA 00-3F linux/uapi/linux/userfaultfd.h
|
|
0xAB 00-1F linux/nbd.h
|
|
0xAC 00-1F linux/raw.h
|
|
diff --git a/MAINTAINERS b/MAINTAINERS
|
|
index fce5cdcefc0b..3917e7363520 100644
|
|
--- a/MAINTAINERS
|
|
+++ b/MAINTAINERS
|
|
@@ -11868,6 +11868,13 @@ F: drivers/scsi/smartpqi/smartpqi*.[ch]
|
|
F: include/linux/cciss*.h
|
|
F: include/uapi/linux/cciss*.h
|
|
|
|
+MICROSOFT SURFACE DTX DRIVER
|
|
+M: Maximilian Luz <luzmaximilian@gmail.com>
|
|
+L: platform-driver-x86@vger.kernel.org
|
|
+S: Maintained
|
|
+F: drivers/platform/surface/surface_dtx.c
|
|
+F: include/uapi/linux/surface_aggregator/dtx.h
|
|
+
|
|
MICROSOFT SURFACE GPE LID SUPPORT DRIVER
|
|
M: Maximilian Luz <luzmaximilian@gmail.com>
|
|
L: platform-driver-x86@vger.kernel.org
|
|
diff --git a/drivers/platform/surface/Kconfig b/drivers/platform/surface/Kconfig
|
|
index 6fb304da845f..41d67bb250fd 100644
|
|
--- a/drivers/platform/surface/Kconfig
|
|
+++ b/drivers/platform/surface/Kconfig
|
|
@@ -111,6 +111,22 @@ config SURFACE_BOOK1_DGPU_SWITCH
|
|
This driver provides a sysfs switch to set the power-state of the
|
|
discrete GPU found on the Microsoft Surface Book 1.
|
|
|
|
+config SURFACE_DTX
|
|
+ tristate "Surface DTX (Detachment System) Driver"
|
|
+ depends on SURFACE_AGGREGATOR
|
|
+ depends on INPUT
|
|
+ help
|
|
+ Driver for the Surface Book clipboard detachment system (DTX).
|
|
+
|
|
+ On the Surface Book series devices, the display part containing the
|
|
+ CPU (called the clipboard) can be detached from the base (containing a
|
|
+ battery, the keyboard, and, optionally, a discrete GPU) by (if
|
|
+ necessary) unlocking and opening the latch connecting both parts.
|
|
+
|
|
+ This driver provides a user-space interface that can influence the
|
|
+ behavior of this process, which includes the option to abort it in
|
|
+ case the base is still in use or speed it up in case it is not.
|
|
+
|
|
config SURFACE_GPE
|
|
tristate "Surface GPE/Lid Support Driver"
|
|
depends on DMI
|
|
diff --git a/drivers/platform/surface/Makefile b/drivers/platform/surface/Makefile
|
|
index f7187bae1729..0cc63440328d 100644
|
|
--- a/drivers/platform/surface/Makefile
|
|
+++ b/drivers/platform/surface/Makefile
|
|
@@ -12,6 +12,7 @@ obj-$(CONFIG_SURFACE_AGGREGATOR) += aggregator/
|
|
obj-$(CONFIG_SURFACE_AGGREGATOR_CDEV) += surface_aggregator_cdev.o
|
|
obj-$(CONFIG_SURFACE_AGGREGATOR_REGISTRY) += surface_aggregator_registry.o
|
|
obj-$(CONFIG_SURFACE_BOOK1_DGPU_SWITCH) += surfacebook1_dgpu_switch.o
|
|
+obj-$(CONFIG_SURFACE_DTX) += surface_dtx.o
|
|
obj-$(CONFIG_SURFACE_GPE) += surface_gpe.o
|
|
obj-$(CONFIG_SURFACE_HOTPLUG) += surface_hotplug.o
|
|
obj-$(CONFIG_SURFACE_PLATFORM_PROFILE) += surface_platform_profile.o
|
|
diff --git a/drivers/platform/surface/surface_dtx.c b/drivers/platform/surface/surface_dtx.c
|
|
new file mode 100644
|
|
index 000000000000..1301fab0ea14
|
|
--- /dev/null
|
|
+++ b/drivers/platform/surface/surface_dtx.c
|
|
@@ -0,0 +1,1201 @@
|
|
+// SPDX-License-Identifier: GPL-2.0+
|
|
+/*
|
|
+ * Surface Book (gen. 2 and later) detachment system (DTX) driver.
|
|
+ *
|
|
+ * Provides a user-space interface to properly handle clipboard/tablet
|
|
+ * (containing screen and processor) detachment from the base of the device
|
|
+ * (containing the keyboard and optionally a discrete GPU). Allows to
|
|
+ * acknowledge (to speed things up), abort (e.g. in case the dGPU is still in
|
|
+ * use), or request detachment via user-space.
|
|
+ *
|
|
+ * Copyright (C) 2019-2021 Maximilian Luz <luzmaximilian@gmail.com>
|
|
+ */
|
|
+
|
|
+#include <linux/fs.h>
|
|
+#include <linux/input.h>
|
|
+#include <linux/ioctl.h>
|
|
+#include <linux/kernel.h>
|
|
+#include <linux/kfifo.h>
|
|
+#include <linux/kref.h>
|
|
+#include <linux/miscdevice.h>
|
|
+#include <linux/module.h>
|
|
+#include <linux/mutex.h>
|
|
+#include <linux/platform_device.h>
|
|
+#include <linux/poll.h>
|
|
+#include <linux/rwsem.h>
|
|
+#include <linux/slab.h>
|
|
+#include <linux/workqueue.h>
|
|
+
|
|
+#include <linux/surface_aggregator/controller.h>
|
|
+#include <linux/surface_aggregator/dtx.h>
|
|
+
|
|
+
|
|
+/* -- SSAM interface. ------------------------------------------------------- */
|
|
+
|
|
+enum sam_event_cid_bas {
|
|
+ SAM_EVENT_CID_DTX_CONNECTION = 0x0c,
|
|
+ SAM_EVENT_CID_DTX_REQUEST = 0x0e,
|
|
+ SAM_EVENT_CID_DTX_CANCEL = 0x0f,
|
|
+ SAM_EVENT_CID_DTX_LATCH_STATUS = 0x11,
|
|
+};
|
|
+
|
|
+enum ssam_bas_base_state {
|
|
+ SSAM_BAS_BASE_STATE_DETACH_SUCCESS = 0x00,
|
|
+ SSAM_BAS_BASE_STATE_ATTACHED = 0x01,
|
|
+ SSAM_BAS_BASE_STATE_NOT_FEASIBLE = 0x02,
|
|
+};
|
|
+
|
|
+enum ssam_bas_latch_status {
|
|
+ SSAM_BAS_LATCH_STATUS_CLOSED = 0x00,
|
|
+ SSAM_BAS_LATCH_STATUS_OPENED = 0x01,
|
|
+ SSAM_BAS_LATCH_STATUS_FAILED_TO_OPEN = 0x02,
|
|
+ SSAM_BAS_LATCH_STATUS_FAILED_TO_REMAIN_OPEN = 0x03,
|
|
+ SSAM_BAS_LATCH_STATUS_FAILED_TO_CLOSE = 0x04,
|
|
+};
|
|
+
|
|
+enum ssam_bas_cancel_reason {
|
|
+ SSAM_BAS_CANCEL_REASON_NOT_FEASIBLE = 0x00, /* Low battery. */
|
|
+ SSAM_BAS_CANCEL_REASON_TIMEOUT = 0x02,
|
|
+ SSAM_BAS_CANCEL_REASON_FAILED_TO_OPEN = 0x03,
|
|
+ SSAM_BAS_CANCEL_REASON_FAILED_TO_REMAIN_OPEN = 0x04,
|
|
+ SSAM_BAS_CANCEL_REASON_FAILED_TO_CLOSE = 0x05,
|
|
+};
|
|
+
|
|
+struct ssam_bas_base_info {
|
|
+ u8 state;
|
|
+ u8 base_id;
|
|
+} __packed;
|
|
+
|
|
+static_assert(sizeof(struct ssam_bas_base_info) == 2);
|
|
+
|
|
+SSAM_DEFINE_SYNC_REQUEST_N(ssam_bas_latch_lock, {
|
|
+ .target_category = SSAM_SSH_TC_BAS,
|
|
+ .target_id = 0x01,
|
|
+ .command_id = 0x06,
|
|
+ .instance_id = 0x00,
|
|
+});
|
|
+
|
|
+SSAM_DEFINE_SYNC_REQUEST_N(ssam_bas_latch_unlock, {
|
|
+ .target_category = SSAM_SSH_TC_BAS,
|
|
+ .target_id = 0x01,
|
|
+ .command_id = 0x07,
|
|
+ .instance_id = 0x00,
|
|
+});
|
|
+
|
|
+SSAM_DEFINE_SYNC_REQUEST_N(ssam_bas_latch_request, {
|
|
+ .target_category = SSAM_SSH_TC_BAS,
|
|
+ .target_id = 0x01,
|
|
+ .command_id = 0x08,
|
|
+ .instance_id = 0x00,
|
|
+});
|
|
+
|
|
+SSAM_DEFINE_SYNC_REQUEST_N(ssam_bas_latch_confirm, {
|
|
+ .target_category = SSAM_SSH_TC_BAS,
|
|
+ .target_id = 0x01,
|
|
+ .command_id = 0x09,
|
|
+ .instance_id = 0x00,
|
|
+});
|
|
+
|
|
+SSAM_DEFINE_SYNC_REQUEST_N(ssam_bas_latch_heartbeat, {
|
|
+ .target_category = SSAM_SSH_TC_BAS,
|
|
+ .target_id = 0x01,
|
|
+ .command_id = 0x0a,
|
|
+ .instance_id = 0x00,
|
|
+});
|
|
+
|
|
+SSAM_DEFINE_SYNC_REQUEST_N(ssam_bas_latch_cancel, {
|
|
+ .target_category = SSAM_SSH_TC_BAS,
|
|
+ .target_id = 0x01,
|
|
+ .command_id = 0x0b,
|
|
+ .instance_id = 0x00,
|
|
+});
|
|
+
|
|
+SSAM_DEFINE_SYNC_REQUEST_R(ssam_bas_get_base, struct ssam_bas_base_info, {
|
|
+ .target_category = SSAM_SSH_TC_BAS,
|
|
+ .target_id = 0x01,
|
|
+ .command_id = 0x0c,
|
|
+ .instance_id = 0x00,
|
|
+});
|
|
+
|
|
+SSAM_DEFINE_SYNC_REQUEST_R(ssam_bas_get_device_mode, u8, {
|
|
+ .target_category = SSAM_SSH_TC_BAS,
|
|
+ .target_id = 0x01,
|
|
+ .command_id = 0x0d,
|
|
+ .instance_id = 0x00,
|
|
+});
|
|
+
|
|
+SSAM_DEFINE_SYNC_REQUEST_R(ssam_bas_get_latch_status, u8, {
|
|
+ .target_category = SSAM_SSH_TC_BAS,
|
|
+ .target_id = 0x01,
|
|
+ .command_id = 0x11,
|
|
+ .instance_id = 0x00,
|
|
+});
|
|
+
|
|
+
|
|
+/* -- Main structures. ------------------------------------------------------ */
|
|
+
|
|
+enum sdtx_device_state {
|
|
+ SDTX_DEVICE_SHUTDOWN_BIT = BIT(0),
|
|
+ SDTX_DEVICE_DIRTY_BASE_BIT = BIT(1),
|
|
+ SDTX_DEVICE_DIRTY_MODE_BIT = BIT(2),
|
|
+ SDTX_DEVICE_DIRTY_LATCH_BIT = BIT(3),
|
|
+};
|
|
+
|
|
+struct sdtx_device {
|
|
+ struct kref kref;
|
|
+ struct rw_semaphore lock; /* Guards device and controller reference. */
|
|
+
|
|
+ struct device *dev;
|
|
+ struct ssam_controller *ctrl;
|
|
+ unsigned long flags;
|
|
+
|
|
+ struct miscdevice mdev;
|
|
+ wait_queue_head_t waitq;
|
|
+ struct mutex write_lock; /* Guards order of events/notifications. */
|
|
+ struct rw_semaphore client_lock; /* Guards client list. */
|
|
+ struct list_head client_list;
|
|
+
|
|
+ struct delayed_work state_work;
|
|
+ struct {
|
|
+ struct ssam_bas_base_info base;
|
|
+ u8 device_mode;
|
|
+ u8 latch_status;
|
|
+ } state;
|
|
+
|
|
+ struct delayed_work mode_work;
|
|
+ struct input_dev *mode_switch;
|
|
+
|
|
+ struct ssam_event_notifier notif;
|
|
+};
|
|
+
|
|
+enum sdtx_client_state {
|
|
+ SDTX_CLIENT_EVENTS_ENABLED_BIT = BIT(0),
|
|
+};
|
|
+
|
|
+struct sdtx_client {
|
|
+ struct sdtx_device *ddev;
|
|
+ struct list_head node;
|
|
+ unsigned long flags;
|
|
+
|
|
+ struct fasync_struct *fasync;
|
|
+
|
|
+ struct mutex read_lock; /* Guards FIFO buffer read access. */
|
|
+ DECLARE_KFIFO(buffer, u8, 512);
|
|
+};
|
|
+
|
|
+static void __sdtx_device_release(struct kref *kref)
|
|
+{
|
|
+ struct sdtx_device *ddev = container_of(kref, struct sdtx_device, kref);
|
|
+
|
|
+ mutex_destroy(&ddev->write_lock);
|
|
+ kfree(ddev);
|
|
+}
|
|
+
|
|
+static struct sdtx_device *sdtx_device_get(struct sdtx_device *ddev)
|
|
+{
|
|
+ if (ddev)
|
|
+ kref_get(&ddev->kref);
|
|
+
|
|
+ return ddev;
|
|
+}
|
|
+
|
|
+static void sdtx_device_put(struct sdtx_device *ddev)
|
|
+{
|
|
+ if (ddev)
|
|
+ kref_put(&ddev->kref, __sdtx_device_release);
|
|
+}
|
|
+
|
|
+
|
|
+/* -- Firmware value translations. ------------------------------------------ */
|
|
+
|
|
+static u16 sdtx_translate_base_state(struct sdtx_device *ddev, u8 state)
|
|
+{
|
|
+ switch (state) {
|
|
+ case SSAM_BAS_BASE_STATE_ATTACHED:
|
|
+ return SDTX_BASE_ATTACHED;
|
|
+
|
|
+ case SSAM_BAS_BASE_STATE_DETACH_SUCCESS:
|
|
+ return SDTX_BASE_DETACHED;
|
|
+
|
|
+ case SSAM_BAS_BASE_STATE_NOT_FEASIBLE:
|
|
+ return SDTX_DETACH_NOT_FEASIBLE;
|
|
+
|
|
+ default:
|
|
+ dev_err(ddev->dev, "unknown base state: %#04x\n", state);
|
|
+ return SDTX_UNKNOWN(state);
|
|
+ }
|
|
+}
|
|
+
|
|
+static u16 sdtx_translate_latch_status(struct sdtx_device *ddev, u8 status)
|
|
+{
|
|
+ switch (status) {
|
|
+ case SSAM_BAS_LATCH_STATUS_CLOSED:
|
|
+ return SDTX_LATCH_CLOSED;
|
|
+
|
|
+ case SSAM_BAS_LATCH_STATUS_OPENED:
|
|
+ return SDTX_LATCH_OPENED;
|
|
+
|
|
+ case SSAM_BAS_LATCH_STATUS_FAILED_TO_OPEN:
|
|
+ return SDTX_ERR_FAILED_TO_OPEN;
|
|
+
|
|
+ case SSAM_BAS_LATCH_STATUS_FAILED_TO_REMAIN_OPEN:
|
|
+ return SDTX_ERR_FAILED_TO_REMAIN_OPEN;
|
|
+
|
|
+ case SSAM_BAS_LATCH_STATUS_FAILED_TO_CLOSE:
|
|
+ return SDTX_ERR_FAILED_TO_CLOSE;
|
|
+
|
|
+ default:
|
|
+ dev_err(ddev->dev, "unknown latch status: %#04x\n", status);
|
|
+ return SDTX_UNKNOWN(status);
|
|
+ }
|
|
+}
|
|
+
|
|
+static u16 sdtx_translate_cancel_reason(struct sdtx_device *ddev, u8 reason)
|
|
+{
|
|
+ switch (reason) {
|
|
+ case SSAM_BAS_CANCEL_REASON_NOT_FEASIBLE:
|
|
+ return SDTX_DETACH_NOT_FEASIBLE;
|
|
+
|
|
+ case SSAM_BAS_CANCEL_REASON_TIMEOUT:
|
|
+ return SDTX_DETACH_TIMEDOUT;
|
|
+
|
|
+ case SSAM_BAS_CANCEL_REASON_FAILED_TO_OPEN:
|
|
+ return SDTX_ERR_FAILED_TO_OPEN;
|
|
+
|
|
+ case SSAM_BAS_CANCEL_REASON_FAILED_TO_REMAIN_OPEN:
|
|
+ return SDTX_ERR_FAILED_TO_REMAIN_OPEN;
|
|
+
|
|
+ case SSAM_BAS_CANCEL_REASON_FAILED_TO_CLOSE:
|
|
+ return SDTX_ERR_FAILED_TO_CLOSE;
|
|
+
|
|
+ default:
|
|
+ dev_err(ddev->dev, "unknown cancel reason: %#04x\n", reason);
|
|
+ return SDTX_UNKNOWN(reason);
|
|
+ }
|
|
+}
|
|
+
|
|
+
|
|
+/* -- IOCTLs. --------------------------------------------------------------- */
|
|
+
|
|
+static int sdtx_ioctl_get_base_info(struct sdtx_device *ddev,
|
|
+ struct sdtx_base_info __user *buf)
|
|
+{
|
|
+ struct ssam_bas_base_info raw;
|
|
+ struct sdtx_base_info info;
|
|
+ int status;
|
|
+
|
|
+ lockdep_assert_held_read(&ddev->lock);
|
|
+
|
|
+ status = ssam_retry(ssam_bas_get_base, ddev->ctrl, &raw);
|
|
+ if (status < 0)
|
|
+ return status;
|
|
+
|
|
+ info.state = sdtx_translate_base_state(ddev, raw.state);
|
|
+ info.base_id = SDTX_BASE_TYPE_SSH(raw.base_id);
|
|
+
|
|
+ if (copy_to_user(buf, &info, sizeof(info)))
|
|
+ return -EFAULT;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int sdtx_ioctl_get_device_mode(struct sdtx_device *ddev, u16 __user *buf)
|
|
+{
|
|
+ u8 mode;
|
|
+ int status;
|
|
+
|
|
+ lockdep_assert_held_read(&ddev->lock);
|
|
+
|
|
+ status = ssam_retry(ssam_bas_get_device_mode, ddev->ctrl, &mode);
|
|
+ if (status < 0)
|
|
+ return status;
|
|
+
|
|
+ return put_user(mode, buf);
|
|
+}
|
|
+
|
|
+static int sdtx_ioctl_get_latch_status(struct sdtx_device *ddev, u16 __user *buf)
|
|
+{
|
|
+ u8 latch;
|
|
+ int status;
|
|
+
|
|
+ lockdep_assert_held_read(&ddev->lock);
|
|
+
|
|
+ status = ssam_retry(ssam_bas_get_latch_status, ddev->ctrl, &latch);
|
|
+ if (status < 0)
|
|
+ return status;
|
|
+
|
|
+ return put_user(sdtx_translate_latch_status(ddev, latch), buf);
|
|
+}
|
|
+
|
|
+static long __surface_dtx_ioctl(struct sdtx_client *client, unsigned int cmd, unsigned long arg)
|
|
+{
|
|
+ struct sdtx_device *ddev = client->ddev;
|
|
+
|
|
+ lockdep_assert_held_read(&ddev->lock);
|
|
+
|
|
+ switch (cmd) {
|
|
+ case SDTX_IOCTL_EVENTS_ENABLE:
|
|
+ set_bit(SDTX_CLIENT_EVENTS_ENABLED_BIT, &client->flags);
|
|
+ return 0;
|
|
+
|
|
+ case SDTX_IOCTL_EVENTS_DISABLE:
|
|
+ clear_bit(SDTX_CLIENT_EVENTS_ENABLED_BIT, &client->flags);
|
|
+ return 0;
|
|
+
|
|
+ case SDTX_IOCTL_LATCH_LOCK:
|
|
+ return ssam_retry(ssam_bas_latch_lock, ddev->ctrl);
|
|
+
|
|
+ case SDTX_IOCTL_LATCH_UNLOCK:
|
|
+ return ssam_retry(ssam_bas_latch_unlock, ddev->ctrl);
|
|
+
|
|
+ case SDTX_IOCTL_LATCH_REQUEST:
|
|
+ return ssam_retry(ssam_bas_latch_request, ddev->ctrl);
|
|
+
|
|
+ case SDTX_IOCTL_LATCH_CONFIRM:
|
|
+ return ssam_retry(ssam_bas_latch_confirm, ddev->ctrl);
|
|
+
|
|
+ case SDTX_IOCTL_LATCH_HEARTBEAT:
|
|
+ return ssam_retry(ssam_bas_latch_heartbeat, ddev->ctrl);
|
|
+
|
|
+ case SDTX_IOCTL_LATCH_CANCEL:
|
|
+ return ssam_retry(ssam_bas_latch_cancel, ddev->ctrl);
|
|
+
|
|
+ case SDTX_IOCTL_GET_BASE_INFO:
|
|
+ return sdtx_ioctl_get_base_info(ddev, (struct sdtx_base_info __user *)arg);
|
|
+
|
|
+ case SDTX_IOCTL_GET_DEVICE_MODE:
|
|
+ return sdtx_ioctl_get_device_mode(ddev, (u16 __user *)arg);
|
|
+
|
|
+ case SDTX_IOCTL_GET_LATCH_STATUS:
|
|
+ return sdtx_ioctl_get_latch_status(ddev, (u16 __user *)arg);
|
|
+
|
|
+ default:
|
|
+ return -EINVAL;
|
|
+ }
|
|
+}
|
|
+
|
|
+static long surface_dtx_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
|
|
+{
|
|
+ struct sdtx_client *client = file->private_data;
|
|
+ long status;
|
|
+
|
|
+ if (down_read_killable(&client->ddev->lock))
|
|
+ return -ERESTARTSYS;
|
|
+
|
|
+ if (test_bit(SDTX_DEVICE_SHUTDOWN_BIT, &client->ddev->flags)) {
|
|
+ up_read(&client->ddev->lock);
|
|
+ return -ENODEV;
|
|
+ }
|
|
+
|
|
+ status = __surface_dtx_ioctl(client, cmd, arg);
|
|
+
|
|
+ up_read(&client->ddev->lock);
|
|
+ return status;
|
|
+}
|
|
+
|
|
+
|
|
+/* -- File operations. ------------------------------------------------------ */
|
|
+
|
|
+static int surface_dtx_open(struct inode *inode, struct file *file)
|
|
+{
|
|
+ struct sdtx_device *ddev = container_of(file->private_data, struct sdtx_device, mdev);
|
|
+ struct sdtx_client *client;
|
|
+
|
|
+ /* Initialize client. */
|
|
+ client = kzalloc(sizeof(*client), GFP_KERNEL);
|
|
+ if (!client)
|
|
+ return -ENOMEM;
|
|
+
|
|
+ client->ddev = sdtx_device_get(ddev);
|
|
+
|
|
+ INIT_LIST_HEAD(&client->node);
|
|
+
|
|
+ mutex_init(&client->read_lock);
|
|
+ INIT_KFIFO(client->buffer);
|
|
+
|
|
+ file->private_data = client;
|
|
+
|
|
+ /* Attach client. */
|
|
+ down_write(&ddev->client_lock);
|
|
+
|
|
+ /*
|
|
+ * Do not add a new client if the device has been shut down. Note that
|
|
+ * it's enough to hold the client_lock here as, during shutdown, we
|
|
+ * only acquire that lock and remove clients after marking the device
|
|
+ * as shut down.
|
|
+ */
|
|
+ if (test_bit(SDTX_DEVICE_SHUTDOWN_BIT, &ddev->flags)) {
|
|
+ up_write(&ddev->client_lock);
|
|
+ sdtx_device_put(client->ddev);
|
|
+ kfree(client);
|
|
+ return -ENODEV;
|
|
+ }
|
|
+
|
|
+ list_add_tail(&client->node, &ddev->client_list);
|
|
+ up_write(&ddev->client_lock);
|
|
+
|
|
+ stream_open(inode, file);
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int surface_dtx_release(struct inode *inode, struct file *file)
|
|
+{
|
|
+ struct sdtx_client *client = file->private_data;
|
|
+
|
|
+ /* Detach client. */
|
|
+ down_write(&client->ddev->client_lock);
|
|
+ list_del(&client->node);
|
|
+ up_write(&client->ddev->client_lock);
|
|
+
|
|
+ /* Free client. */
|
|
+ sdtx_device_put(client->ddev);
|
|
+ mutex_destroy(&client->read_lock);
|
|
+ kfree(client);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static ssize_t surface_dtx_read(struct file *file, char __user *buf, size_t count, loff_t *offs)
|
|
+{
|
|
+ struct sdtx_client *client = file->private_data;
|
|
+ struct sdtx_device *ddev = client->ddev;
|
|
+ unsigned int copied;
|
|
+ int status = 0;
|
|
+
|
|
+ if (down_read_killable(&ddev->lock))
|
|
+ return -ERESTARTSYS;
|
|
+
|
|
+ /* Make sure we're not shut down. */
|
|
+ if (test_bit(SDTX_DEVICE_SHUTDOWN_BIT, &ddev->flags)) {
|
|
+ up_read(&ddev->lock);
|
|
+ return -ENODEV;
|
|
+ }
|
|
+
|
|
+ do {
|
|
+ /* Check availability, wait if necessary. */
|
|
+ if (kfifo_is_empty(&client->buffer)) {
|
|
+ up_read(&ddev->lock);
|
|
+
|
|
+ if (file->f_flags & O_NONBLOCK)
|
|
+ return -EAGAIN;
|
|
+
|
|
+ status = wait_event_interruptible(ddev->waitq,
|
|
+ !kfifo_is_empty(&client->buffer) ||
|
|
+ test_bit(SDTX_DEVICE_SHUTDOWN_BIT,
|
|
+ &ddev->flags));
|
|
+ if (status < 0)
|
|
+ return status;
|
|
+
|
|
+ if (down_read_killable(&client->ddev->lock))
|
|
+ return -ERESTARTSYS;
|
|
+
|
|
+ /* Need to check that we're not shut down again. */
|
|
+ if (test_bit(SDTX_DEVICE_SHUTDOWN_BIT, &ddev->flags)) {
|
|
+ up_read(&ddev->lock);
|
|
+ return -ENODEV;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ /* Try to read from FIFO. */
|
|
+ if (mutex_lock_interruptible(&client->read_lock)) {
|
|
+ up_read(&ddev->lock);
|
|
+ return -ERESTARTSYS;
|
|
+ }
|
|
+
|
|
+ status = kfifo_to_user(&client->buffer, buf, count, &copied);
|
|
+ mutex_unlock(&client->read_lock);
|
|
+
|
|
+ if (status < 0) {
|
|
+ up_read(&ddev->lock);
|
|
+ return status;
|
|
+ }
|
|
+
|
|
+ /* We might not have gotten anything, check this here. */
|
|
+ if (copied == 0 && (file->f_flags & O_NONBLOCK)) {
|
|
+ up_read(&ddev->lock);
|
|
+ return -EAGAIN;
|
|
+ }
|
|
+ } while (copied == 0);
|
|
+
|
|
+ up_read(&ddev->lock);
|
|
+ return copied;
|
|
+}
|
|
+
|
|
+static __poll_t surface_dtx_poll(struct file *file, struct poll_table_struct *pt)
|
|
+{
|
|
+ struct sdtx_client *client = file->private_data;
|
|
+ __poll_t events = 0;
|
|
+
|
|
+ if (down_read_killable(&client->ddev->lock))
|
|
+ return -ERESTARTSYS;
|
|
+
|
|
+ if (test_bit(SDTX_DEVICE_SHUTDOWN_BIT, &client->ddev->flags)) {
|
|
+ up_read(&client->ddev->lock);
|
|
+ return EPOLLHUP | EPOLLERR;
|
|
+ }
|
|
+
|
|
+ poll_wait(file, &client->ddev->waitq, pt);
|
|
+
|
|
+ if (!kfifo_is_empty(&client->buffer))
|
|
+ events |= EPOLLIN | EPOLLRDNORM;
|
|
+
|
|
+ up_read(&client->ddev->lock);
|
|
+ return events;
|
|
+}
|
|
+
|
|
+static int surface_dtx_fasync(int fd, struct file *file, int on)
|
|
+{
|
|
+ struct sdtx_client *client = file->private_data;
|
|
+
|
|
+ return fasync_helper(fd, file, on, &client->fasync);
|
|
+}
|
|
+
|
|
+static const struct file_operations surface_dtx_fops = {
|
|
+ .owner = THIS_MODULE,
|
|
+ .open = surface_dtx_open,
|
|
+ .release = surface_dtx_release,
|
|
+ .read = surface_dtx_read,
|
|
+ .poll = surface_dtx_poll,
|
|
+ .fasync = surface_dtx_fasync,
|
|
+ .unlocked_ioctl = surface_dtx_ioctl,
|
|
+ .compat_ioctl = surface_dtx_ioctl,
|
|
+ .llseek = no_llseek,
|
|
+};
|
|
+
|
|
+
|
|
+/* -- Event handling/forwarding. -------------------------------------------- */
|
|
+
|
|
+/*
|
|
+ * The device operation mode is not immediately updated on the EC when the
|
|
+ * base has been connected, i.e. querying the device mode inside the
|
|
+ * connection event callback yields an outdated value. Thus, we can only
|
|
+ * determine the new tablet-mode switch and device mode values after some
|
|
+ * time.
|
|
+ *
|
|
+ * These delays have been chosen by experimenting. We first delay on connect
|
|
+ * events, then check and validate the device mode against the base state and
|
|
+ * if invalid delay again by the "recheck" delay.
|
|
+ */
|
|
+#define SDTX_DEVICE_MODE_DELAY_CONNECT msecs_to_jiffies(100)
|
|
+#define SDTX_DEVICE_MODE_DELAY_RECHECK msecs_to_jiffies(100)
|
|
+
|
|
+struct sdtx_status_event {
|
|
+ struct sdtx_event e;
|
|
+ __u16 v;
|
|
+} __packed;
|
|
+
|
|
+struct sdtx_base_info_event {
|
|
+ struct sdtx_event e;
|
|
+ struct sdtx_base_info v;
|
|
+} __packed;
|
|
+
|
|
+union sdtx_generic_event {
|
|
+ struct sdtx_event common;
|
|
+ struct sdtx_status_event status;
|
|
+ struct sdtx_base_info_event base;
|
|
+};
|
|
+
|
|
+static void sdtx_update_device_mode(struct sdtx_device *ddev, unsigned long delay);
|
|
+
|
|
+/* Must be executed with ddev->write_lock held. */
|
|
+static void sdtx_push_event(struct sdtx_device *ddev, struct sdtx_event *evt)
|
|
+{
|
|
+ const size_t len = sizeof(struct sdtx_event) + evt->length;
|
|
+ struct sdtx_client *client;
|
|
+
|
|
+ lockdep_assert_held(&ddev->write_lock);
|
|
+
|
|
+ down_read(&ddev->client_lock);
|
|
+ list_for_each_entry(client, &ddev->client_list, node) {
|
|
+ if (!test_bit(SDTX_CLIENT_EVENTS_ENABLED_BIT, &client->flags))
|
|
+ continue;
|
|
+
|
|
+ if (likely(kfifo_avail(&client->buffer) >= len))
|
|
+ kfifo_in(&client->buffer, (const u8 *)evt, len);
|
|
+ else
|
|
+ dev_warn(ddev->dev, "event buffer overrun\n");
|
|
+
|
|
+ kill_fasync(&client->fasync, SIGIO, POLL_IN);
|
|
+ }
|
|
+ up_read(&ddev->client_lock);
|
|
+
|
|
+ wake_up_interruptible(&ddev->waitq);
|
|
+}
|
|
+
|
|
+static u32 sdtx_notifier(struct ssam_event_notifier *nf, const struct ssam_event *in)
|
|
+{
|
|
+ struct sdtx_device *ddev = container_of(nf, struct sdtx_device, notif);
|
|
+ union sdtx_generic_event event;
|
|
+ size_t len;
|
|
+
|
|
+ /* Validate event payload length. */
|
|
+ switch (in->command_id) {
|
|
+ case SAM_EVENT_CID_DTX_CONNECTION:
|
|
+ len = 2 * sizeof(u8);
|
|
+ break;
|
|
+
|
|
+ case SAM_EVENT_CID_DTX_REQUEST:
|
|
+ len = 0;
|
|
+ break;
|
|
+
|
|
+ case SAM_EVENT_CID_DTX_CANCEL:
|
|
+ len = sizeof(u8);
|
|
+ break;
|
|
+
|
|
+ case SAM_EVENT_CID_DTX_LATCH_STATUS:
|
|
+ len = sizeof(u8);
|
|
+ break;
|
|
+
|
|
+ default:
|
|
+ return 0;
|
|
+ };
|
|
+
|
|
+ if (in->length != len) {
|
|
+ dev_err(ddev->dev,
|
|
+ "unexpected payload size for event %#04x: got %u, expected %zu\n",
|
|
+ in->command_id, in->length, len);
|
|
+ return 0;
|
|
+ }
|
|
+
|
|
+ mutex_lock(&ddev->write_lock);
|
|
+
|
|
+ /* Translate event. */
|
|
+ switch (in->command_id) {
|
|
+ case SAM_EVENT_CID_DTX_CONNECTION:
|
|
+ clear_bit(SDTX_DEVICE_DIRTY_BASE_BIT, &ddev->flags);
|
|
+
|
|
+ /* If state has not changed: do not send new event. */
|
|
+ if (ddev->state.base.state == in->data[0] &&
|
|
+ ddev->state.base.base_id == in->data[1])
|
|
+ goto out;
|
|
+
|
|
+ ddev->state.base.state = in->data[0];
|
|
+ ddev->state.base.base_id = in->data[1];
|
|
+
|
|
+ event.base.e.length = sizeof(struct sdtx_base_info);
|
|
+ event.base.e.code = SDTX_EVENT_BASE_CONNECTION;
|
|
+ event.base.v.state = sdtx_translate_base_state(ddev, in->data[0]);
|
|
+ event.base.v.base_id = SDTX_BASE_TYPE_SSH(in->data[1]);
|
|
+ break;
|
|
+
|
|
+ case SAM_EVENT_CID_DTX_REQUEST:
|
|
+ event.common.code = SDTX_EVENT_REQUEST;
|
|
+ event.common.length = 0;
|
|
+ break;
|
|
+
|
|
+ case SAM_EVENT_CID_DTX_CANCEL:
|
|
+ event.status.e.length = sizeof(u16);
|
|
+ event.status.e.code = SDTX_EVENT_CANCEL;
|
|
+ event.status.v = sdtx_translate_cancel_reason(ddev, in->data[0]);
|
|
+ break;
|
|
+
|
|
+ case SAM_EVENT_CID_DTX_LATCH_STATUS:
|
|
+ clear_bit(SDTX_DEVICE_DIRTY_LATCH_BIT, &ddev->flags);
|
|
+
|
|
+ /* If state has not changed: do not send new event. */
|
|
+ if (ddev->state.latch_status == in->data[0])
|
|
+ goto out;
|
|
+
|
|
+ ddev->state.latch_status = in->data[0];
|
|
+
|
|
+ event.status.e.length = sizeof(u16);
|
|
+ event.status.e.code = SDTX_EVENT_LATCH_STATUS;
|
|
+ event.status.v = sdtx_translate_latch_status(ddev, in->data[0]);
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ sdtx_push_event(ddev, &event.common);
|
|
+
|
|
+ /* Update device mode on base connection change. */
|
|
+ if (in->command_id == SAM_EVENT_CID_DTX_CONNECTION) {
|
|
+ unsigned long delay;
|
|
+
|
|
+ delay = in->data[0] ? SDTX_DEVICE_MODE_DELAY_CONNECT : 0;
|
|
+ sdtx_update_device_mode(ddev, delay);
|
|
+ }
|
|
+
|
|
+out:
|
|
+ mutex_unlock(&ddev->write_lock);
|
|
+ return SSAM_NOTIF_HANDLED;
|
|
+}
|
|
+
|
|
+
|
|
+/* -- State update functions. ----------------------------------------------- */
|
|
+
|
|
+static bool sdtx_device_mode_invalid(u8 mode, u8 base_state)
|
|
+{
|
|
+ return ((base_state == SSAM_BAS_BASE_STATE_ATTACHED) &&
|
|
+ (mode == SDTX_DEVICE_MODE_TABLET)) ||
|
|
+ ((base_state == SSAM_BAS_BASE_STATE_DETACH_SUCCESS) &&
|
|
+ (mode != SDTX_DEVICE_MODE_TABLET));
|
|
+}
|
|
+
|
|
+static void sdtx_device_mode_workfn(struct work_struct *work)
|
|
+{
|
|
+ struct sdtx_device *ddev = container_of(work, struct sdtx_device, mode_work.work);
|
|
+ struct sdtx_status_event event;
|
|
+ struct ssam_bas_base_info base;
|
|
+ int status, tablet;
|
|
+ u8 mode;
|
|
+
|
|
+ /* Get operation mode. */
|
|
+ status = ssam_retry(ssam_bas_get_device_mode, ddev->ctrl, &mode);
|
|
+ if (status) {
|
|
+ dev_err(ddev->dev, "failed to get device mode: %d\n", status);
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ /* Get base info. */
|
|
+ status = ssam_retry(ssam_bas_get_base, ddev->ctrl, &base);
|
|
+ if (status) {
|
|
+ dev_err(ddev->dev, "failed to get base info: %d\n", status);
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ /*
|
|
+ * In some cases (specifically when attaching the base), the device
|
|
+ * mode isn't updated right away. Thus we check if the device mode
|
|
+ * makes sense for the given base state and try again later if it
|
|
+ * doesn't.
|
|
+ */
|
|
+ if (sdtx_device_mode_invalid(mode, base.state)) {
|
|
+ dev_dbg(ddev->dev, "device mode is invalid, trying again\n");
|
|
+ sdtx_update_device_mode(ddev, SDTX_DEVICE_MODE_DELAY_RECHECK);
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ mutex_lock(&ddev->write_lock);
|
|
+ clear_bit(SDTX_DEVICE_DIRTY_MODE_BIT, &ddev->flags);
|
|
+
|
|
+ /* Avoid sending duplicate device-mode events. */
|
|
+ if (ddev->state.device_mode == mode) {
|
|
+ mutex_unlock(&ddev->write_lock);
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ ddev->state.device_mode = mode;
|
|
+
|
|
+ event.e.length = sizeof(u16);
|
|
+ event.e.code = SDTX_EVENT_DEVICE_MODE;
|
|
+ event.v = mode;
|
|
+
|
|
+ sdtx_push_event(ddev, &event.e);
|
|
+
|
|
+ /* Send SW_TABLET_MODE event. */
|
|
+ tablet = mode != SDTX_DEVICE_MODE_LAPTOP;
|
|
+ input_report_switch(ddev->mode_switch, SW_TABLET_MODE, tablet);
|
|
+ input_sync(ddev->mode_switch);
|
|
+
|
|
+ mutex_unlock(&ddev->write_lock);
|
|
+}
|
|
+
|
|
+static void sdtx_update_device_mode(struct sdtx_device *ddev, unsigned long delay)
|
|
+{
|
|
+ schedule_delayed_work(&ddev->mode_work, delay);
|
|
+}
|
|
+
|
|
+/* Must be executed with ddev->write_lock held. */
|
|
+static void __sdtx_device_state_update_base(struct sdtx_device *ddev,
|
|
+ struct ssam_bas_base_info info)
|
|
+{
|
|
+ struct sdtx_base_info_event event;
|
|
+
|
|
+ lockdep_assert_held(&ddev->write_lock);
|
|
+
|
|
+ /* Prevent duplicate events. */
|
|
+ if (ddev->state.base.state == info.state &&
|
|
+ ddev->state.base.base_id == info.base_id)
|
|
+ return;
|
|
+
|
|
+ ddev->state.base = info;
|
|
+
|
|
+ event.e.length = sizeof(struct sdtx_base_info);
|
|
+ event.e.code = SDTX_EVENT_BASE_CONNECTION;
|
|
+ event.v.state = sdtx_translate_base_state(ddev, info.state);
|
|
+ event.v.base_id = SDTX_BASE_TYPE_SSH(info.base_id);
|
|
+
|
|
+ sdtx_push_event(ddev, &event.e);
|
|
+}
|
|
+
|
|
+/* Must be executed with ddev->write_lock held. */
|
|
+static void __sdtx_device_state_update_mode(struct sdtx_device *ddev, u8 mode)
|
|
+{
|
|
+ struct sdtx_status_event event;
|
|
+ int tablet;
|
|
+
|
|
+ /*
|
|
+ * Note: This function must be called after updating the base state
|
|
+ * via __sdtx_device_state_update_base(), as we rely on the updated
|
|
+ * base state value in the validity check below.
|
|
+ */
|
|
+
|
|
+ lockdep_assert_held(&ddev->write_lock);
|
|
+
|
|
+ if (sdtx_device_mode_invalid(mode, ddev->state.base.state)) {
|
|
+ dev_dbg(ddev->dev, "device mode is invalid, trying again\n");
|
|
+ sdtx_update_device_mode(ddev, SDTX_DEVICE_MODE_DELAY_RECHECK);
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ /* Prevent duplicate events. */
|
|
+ if (ddev->state.device_mode == mode)
|
|
+ return;
|
|
+
|
|
+ ddev->state.device_mode = mode;
|
|
+
|
|
+ /* Send event. */
|
|
+ event.e.length = sizeof(u16);
|
|
+ event.e.code = SDTX_EVENT_DEVICE_MODE;
|
|
+ event.v = mode;
|
|
+
|
|
+ sdtx_push_event(ddev, &event.e);
|
|
+
|
|
+ /* Send SW_TABLET_MODE event. */
|
|
+ tablet = mode != SDTX_DEVICE_MODE_LAPTOP;
|
|
+ input_report_switch(ddev->mode_switch, SW_TABLET_MODE, tablet);
|
|
+ input_sync(ddev->mode_switch);
|
|
+}
|
|
+
|
|
+/* Must be executed with ddev->write_lock held. */
|
|
+static void __sdtx_device_state_update_latch(struct sdtx_device *ddev, u8 status)
|
|
+{
|
|
+ struct sdtx_status_event event;
|
|
+
|
|
+ lockdep_assert_held(&ddev->write_lock);
|
|
+
|
|
+ /* Prevent duplicate events. */
|
|
+ if (ddev->state.latch_status == status)
|
|
+ return;
|
|
+
|
|
+ ddev->state.latch_status = status;
|
|
+
|
|
+ event.e.length = sizeof(struct sdtx_base_info);
|
|
+ event.e.code = SDTX_EVENT_BASE_CONNECTION;
|
|
+ event.v = sdtx_translate_latch_status(ddev, status);
|
|
+
|
|
+ sdtx_push_event(ddev, &event.e);
|
|
+}
|
|
+
|
|
+static void sdtx_device_state_workfn(struct work_struct *work)
|
|
+{
|
|
+ struct sdtx_device *ddev = container_of(work, struct sdtx_device, state_work.work);
|
|
+ struct ssam_bas_base_info base;
|
|
+ u8 mode, latch;
|
|
+ int status;
|
|
+
|
|
+ /* Mark everything as dirty. */
|
|
+ set_bit(SDTX_DEVICE_DIRTY_BASE_BIT, &ddev->flags);
|
|
+ set_bit(SDTX_DEVICE_DIRTY_MODE_BIT, &ddev->flags);
|
|
+ set_bit(SDTX_DEVICE_DIRTY_LATCH_BIT, &ddev->flags);
|
|
+
|
|
+ /*
|
|
+ * Ensure that the state gets marked as dirty before continuing to
|
|
+ * query it. Necessary to ensure that clear_bit() calls in
|
|
+ * sdtx_notifier() and sdtx_device_mode_workfn() actually clear these
|
|
+ * bits if an event is received while updating the state here.
|
|
+ */
|
|
+ smp_mb__after_atomic();
|
|
+
|
|
+ status = ssam_retry(ssam_bas_get_base, ddev->ctrl, &base);
|
|
+ if (status) {
|
|
+ dev_err(ddev->dev, "failed to get base state: %d\n", status);
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ status = ssam_retry(ssam_bas_get_device_mode, ddev->ctrl, &mode);
|
|
+ if (status) {
|
|
+ dev_err(ddev->dev, "failed to get device mode: %d\n", status);
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ status = ssam_retry(ssam_bas_get_latch_status, ddev->ctrl, &latch);
|
|
+ if (status) {
|
|
+ dev_err(ddev->dev, "failed to get latch status: %d\n", status);
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ mutex_lock(&ddev->write_lock);
|
|
+
|
|
+ /*
|
|
+ * If the respective dirty-bit has been cleared, an event has been
|
|
+ * received, updating this state. The queried state may thus be out of
|
|
+ * date. At this point, we can safely assume that the state provided
|
|
+ * by the event is either up to date, or we're about to receive
|
|
+ * another event updating it.
|
|
+ */
|
|
+
|
|
+ if (test_and_clear_bit(SDTX_DEVICE_DIRTY_BASE_BIT, &ddev->flags))
|
|
+ __sdtx_device_state_update_base(ddev, base);
|
|
+
|
|
+ if (test_and_clear_bit(SDTX_DEVICE_DIRTY_MODE_BIT, &ddev->flags))
|
|
+ __sdtx_device_state_update_mode(ddev, mode);
|
|
+
|
|
+ if (test_and_clear_bit(SDTX_DEVICE_DIRTY_LATCH_BIT, &ddev->flags))
|
|
+ __sdtx_device_state_update_latch(ddev, latch);
|
|
+
|
|
+ mutex_unlock(&ddev->write_lock);
|
|
+}
|
|
+
|
|
+static void sdtx_update_device_state(struct sdtx_device *ddev, unsigned long delay)
|
|
+{
|
|
+ schedule_delayed_work(&ddev->state_work, delay);
|
|
+}
|
|
+
|
|
+
|
|
+/* -- Common device initialization. ----------------------------------------- */
|
|
+
|
|
+static int sdtx_device_init(struct sdtx_device *ddev, struct device *dev,
|
|
+ struct ssam_controller *ctrl)
|
|
+{
|
|
+ int status, tablet_mode;
|
|
+
|
|
+ /* Basic initialization. */
|
|
+ kref_init(&ddev->kref);
|
|
+ init_rwsem(&ddev->lock);
|
|
+ ddev->dev = dev;
|
|
+ ddev->ctrl = ctrl;
|
|
+
|
|
+ ddev->mdev.minor = MISC_DYNAMIC_MINOR;
|
|
+ ddev->mdev.name = "surface_dtx";
|
|
+ ddev->mdev.nodename = "surface/dtx";
|
|
+ ddev->mdev.fops = &surface_dtx_fops;
|
|
+
|
|
+ ddev->notif.base.priority = 1;
|
|
+ ddev->notif.base.fn = sdtx_notifier;
|
|
+ ddev->notif.event.reg = SSAM_EVENT_REGISTRY_SAM;
|
|
+ ddev->notif.event.id.target_category = SSAM_SSH_TC_BAS;
|
|
+ ddev->notif.event.id.instance = 0;
|
|
+ ddev->notif.event.mask = SSAM_EVENT_MASK_NONE;
|
|
+ ddev->notif.event.flags = SSAM_EVENT_SEQUENCED;
|
|
+
|
|
+ init_waitqueue_head(&ddev->waitq);
|
|
+ mutex_init(&ddev->write_lock);
|
|
+ init_rwsem(&ddev->client_lock);
|
|
+ INIT_LIST_HEAD(&ddev->client_list);
|
|
+
|
|
+ INIT_DELAYED_WORK(&ddev->mode_work, sdtx_device_mode_workfn);
|
|
+ INIT_DELAYED_WORK(&ddev->state_work, sdtx_device_state_workfn);
|
|
+
|
|
+ /*
|
|
+ * Get current device state. We want to guarantee that events are only
|
|
+ * sent when state actually changes. Thus we cannot use special
|
|
+ * "uninitialized" values, as that would cause problems when manually
|
|
+ * querying the state in surface_dtx_pm_complete(). I.e. we would not
|
|
+ * be able to detect state changes there if no change event has been
|
|
+ * received between driver initialization and first device suspension.
|
|
+ *
|
|
+ * Note that we also need to do this before registering the event
|
|
+ * notifier, as that may access the state values.
|
|
+ */
|
|
+ status = ssam_retry(ssam_bas_get_base, ddev->ctrl, &ddev->state.base);
|
|
+ if (status)
|
|
+ return status;
|
|
+
|
|
+ status = ssam_retry(ssam_bas_get_device_mode, ddev->ctrl, &ddev->state.device_mode);
|
|
+ if (status)
|
|
+ return status;
|
|
+
|
|
+ status = ssam_retry(ssam_bas_get_latch_status, ddev->ctrl, &ddev->state.latch_status);
|
|
+ if (status)
|
|
+ return status;
|
|
+
|
|
+ /* Set up tablet mode switch. */
|
|
+ ddev->mode_switch = input_allocate_device();
|
|
+ if (!ddev->mode_switch)
|
|
+ return -ENOMEM;
|
|
+
|
|
+ ddev->mode_switch->name = "Microsoft Surface DTX Device Mode Switch";
|
|
+ ddev->mode_switch->phys = "ssam/01:11:01:00:00/input0";
|
|
+ ddev->mode_switch->id.bustype = BUS_HOST;
|
|
+ ddev->mode_switch->dev.parent = ddev->dev;
|
|
+
|
|
+ tablet_mode = (ddev->state.device_mode != SDTX_DEVICE_MODE_LAPTOP);
|
|
+ input_set_capability(ddev->mode_switch, EV_SW, SW_TABLET_MODE);
|
|
+ input_report_switch(ddev->mode_switch, SW_TABLET_MODE, tablet_mode);
|
|
+
|
|
+ status = input_register_device(ddev->mode_switch);
|
|
+ if (status) {
|
|
+ input_free_device(ddev->mode_switch);
|
|
+ return status;
|
|
+ }
|
|
+
|
|
+ /* Set up event notifier. */
|
|
+ status = ssam_notifier_register(ddev->ctrl, &ddev->notif);
|
|
+ if (status)
|
|
+ goto err_notif;
|
|
+
|
|
+ /* Register miscdevice. */
|
|
+ status = misc_register(&ddev->mdev);
|
|
+ if (status)
|
|
+ goto err_mdev;
|
|
+
|
|
+ /*
|
|
+ * Update device state in case it has changed between getting the
|
|
+ * initial mode and registering the event notifier.
|
|
+ */
|
|
+ sdtx_update_device_state(ddev, 0);
|
|
+ return 0;
|
|
+
|
|
+err_notif:
|
|
+ ssam_notifier_unregister(ddev->ctrl, &ddev->notif);
|
|
+ cancel_delayed_work_sync(&ddev->mode_work);
|
|
+err_mdev:
|
|
+ input_unregister_device(ddev->mode_switch);
|
|
+ return status;
|
|
+}
|
|
+
|
|
+static struct sdtx_device *sdtx_device_create(struct device *dev, struct ssam_controller *ctrl)
|
|
+{
|
|
+ struct sdtx_device *ddev;
|
|
+ int status;
|
|
+
|
|
+ ddev = kzalloc(sizeof(*ddev), GFP_KERNEL);
|
|
+ if (!ddev)
|
|
+ return ERR_PTR(-ENOMEM);
|
|
+
|
|
+ status = sdtx_device_init(ddev, dev, ctrl);
|
|
+ if (status) {
|
|
+ sdtx_device_put(ddev);
|
|
+ return ERR_PTR(status);
|
|
+ }
|
|
+
|
|
+ return ddev;
|
|
+}
|
|
+
|
|
+static void sdtx_device_destroy(struct sdtx_device *ddev)
|
|
+{
|
|
+ struct sdtx_client *client;
|
|
+
|
|
+ /*
|
|
+ * Mark device as shut-down. Prevent new clients from being added and
|
|
+ * new operations from being executed.
|
|
+ */
|
|
+ set_bit(SDTX_DEVICE_SHUTDOWN_BIT, &ddev->flags);
|
|
+
|
|
+ /* Disable notifiers, prevent new events from arriving. */
|
|
+ ssam_notifier_unregister(ddev->ctrl, &ddev->notif);
|
|
+
|
|
+ /* Stop mode_work, prevent access to mode_switch. */
|
|
+ cancel_delayed_work_sync(&ddev->mode_work);
|
|
+
|
|
+ /* Stop state_work. */
|
|
+ cancel_delayed_work_sync(&ddev->state_work);
|
|
+
|
|
+ /* With mode_work canceled, we can unregister the mode_switch. */
|
|
+ input_unregister_device(ddev->mode_switch);
|
|
+
|
|
+ /* Wake up async clients. */
|
|
+ down_write(&ddev->client_lock);
|
|
+ list_for_each_entry(client, &ddev->client_list, node) {
|
|
+ kill_fasync(&client->fasync, SIGIO, POLL_HUP);
|
|
+ }
|
|
+ up_write(&ddev->client_lock);
|
|
+
|
|
+ /* Wake up blocking clients. */
|
|
+ wake_up_interruptible(&ddev->waitq);
|
|
+
|
|
+ /*
|
|
+ * Wait for clients to finish their current operation. After this, the
|
|
+ * controller and device references are guaranteed to be no longer in
|
|
+ * use.
|
|
+ */
|
|
+ down_write(&ddev->lock);
|
|
+ ddev->dev = NULL;
|
|
+ ddev->ctrl = NULL;
|
|
+ up_write(&ddev->lock);
|
|
+
|
|
+ /* Finally remove the misc-device. */
|
|
+ misc_deregister(&ddev->mdev);
|
|
+
|
|
+ /*
|
|
+ * We're now guaranteed that sdtx_device_open() won't be called any
|
|
+ * more, so we can now drop out reference.
|
|
+ */
|
|
+ sdtx_device_put(ddev);
|
|
+}
|
|
+
|
|
+
|
|
+/* -- PM ops. --------------------------------------------------------------- */
|
|
+
|
|
+#ifdef CONFIG_PM_SLEEP
|
|
+
|
|
+static void surface_dtx_pm_complete(struct device *dev)
|
|
+{
|
|
+ struct sdtx_device *ddev = dev_get_drvdata(dev);
|
|
+
|
|
+ /*
|
|
+ * Normally, the EC will store events while suspended (i.e. in
|
|
+ * display-off state) and release them when resumed (i.e. transitioned
|
|
+ * to display-on state). During hibernation, however, the EC will be
|
|
+ * shut down and does not store events. Furthermore, events might be
|
|
+ * dropped during prolonged suspension (it is currently unknown how
|
|
+ * big this event buffer is and how it behaves on overruns).
|
|
+ *
|
|
+ * To prevent any problems, we update the device state here. We do
|
|
+ * this delayed to ensure that any events sent by the EC directly
|
|
+ * after resuming will be handled first. The delay below has been
|
|
+ * chosen (experimentally), so that there should be ample time for
|
|
+ * these events to be handled, before we check and, if necessary,
|
|
+ * update the state.
|
|
+ */
|
|
+ sdtx_update_device_state(ddev, msecs_to_jiffies(1000));
|
|
+}
|
|
+
|
|
+static const struct dev_pm_ops surface_dtx_pm_ops = {
|
|
+ .complete = surface_dtx_pm_complete,
|
|
+};
|
|
+
|
|
+#else /* CONFIG_PM_SLEEP */
|
|
+
|
|
+static const struct dev_pm_ops surface_dtx_pm_ops = {};
|
|
+
|
|
+#endif /* CONFIG_PM_SLEEP */
|
|
+
|
|
+
|
|
+/* -- Platform driver. ------------------------------------------------------ */
|
|
+
|
|
+static int surface_dtx_platform_probe(struct platform_device *pdev)
|
|
+{
|
|
+ struct ssam_controller *ctrl;
|
|
+ struct sdtx_device *ddev;
|
|
+
|
|
+ /* Link to EC. */
|
|
+ ctrl = ssam_client_bind(&pdev->dev);
|
|
+ if (IS_ERR(ctrl))
|
|
+ return PTR_ERR(ctrl) == -ENODEV ? -EPROBE_DEFER : PTR_ERR(ctrl);
|
|
+
|
|
+ ddev = sdtx_device_create(&pdev->dev, ctrl);
|
|
+ if (IS_ERR(ddev))
|
|
+ return PTR_ERR(ddev);
|
|
+
|
|
+ platform_set_drvdata(pdev, ddev);
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int surface_dtx_platform_remove(struct platform_device *pdev)
|
|
+{
|
|
+ sdtx_device_destroy(platform_get_drvdata(pdev));
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static const struct acpi_device_id surface_dtx_acpi_match[] = {
|
|
+ { "MSHW0133", 0 },
|
|
+ { },
|
|
+};
|
|
+MODULE_DEVICE_TABLE(acpi, surface_dtx_acpi_match);
|
|
+
|
|
+static struct platform_driver surface_dtx_platform_driver = {
|
|
+ .probe = surface_dtx_platform_probe,
|
|
+ .remove = surface_dtx_platform_remove,
|
|
+ .driver = {
|
|
+ .name = "surface_dtx_pltf",
|
|
+ .acpi_match_table = surface_dtx_acpi_match,
|
|
+ .pm = &surface_dtx_pm_ops,
|
|
+ .probe_type = PROBE_PREFER_ASYNCHRONOUS,
|
|
+ },
|
|
+};
|
|
+module_platform_driver(surface_dtx_platform_driver);
|
|
+
|
|
+MODULE_AUTHOR("Maximilian Luz <luzmaximilian@gmail.com>");
|
|
+MODULE_DESCRIPTION("Detachment-system driver for Surface System Aggregator Module");
|
|
+MODULE_LICENSE("GPL");
|
|
diff --git a/include/uapi/linux/surface_aggregator/dtx.h b/include/uapi/linux/surface_aggregator/dtx.h
|
|
new file mode 100644
|
|
index 000000000000..0833aab0d819
|
|
--- /dev/null
|
|
+++ b/include/uapi/linux/surface_aggregator/dtx.h
|
|
@@ -0,0 +1,146 @@
|
|
+/* SPDX-License-Identifier: GPL-2.0+ WITH Linux-syscall-note */
|
|
+/*
|
|
+ * Surface DTX (clipboard detachment system driver) user-space interface.
|
|
+ *
|
|
+ * Definitions, structs, and IOCTLs for the /dev/surface/dtx misc device. This
|
|
+ * device allows user-space to control the clipboard detachment process on
|
|
+ * Surface Book series devices.
|
|
+ *
|
|
+ * Copyright (C) 2020-2021 Maximilian Luz <luzmaximilian@gmail.com>
|
|
+ */
|
|
+
|
|
+#ifndef _UAPI_LINUX_SURFACE_AGGREGATOR_DTX_H
|
|
+#define _UAPI_LINUX_SURFACE_AGGREGATOR_DTX_H
|
|
+
|
|
+#include <linux/ioctl.h>
|
|
+#include <linux/types.h>
|
|
+
|
|
+/* Status/error categories */
|
|
+#define SDTX_CATEGORY_STATUS 0x0000
|
|
+#define SDTX_CATEGORY_RUNTIME_ERROR 0x1000
|
|
+#define SDTX_CATEGORY_HARDWARE_ERROR 0x2000
|
|
+#define SDTX_CATEGORY_UNKNOWN 0xf000
|
|
+
|
|
+#define SDTX_CATEGORY_MASK 0xf000
|
|
+#define SDTX_CATEGORY(value) ((value) & SDTX_CATEGORY_MASK)
|
|
+
|
|
+#define SDTX_STATUS(code) ((code) | SDTX_CATEGORY_STATUS)
|
|
+#define SDTX_ERR_RT(code) ((code) | SDTX_CATEGORY_RUNTIME_ERROR)
|
|
+#define SDTX_ERR_HW(code) ((code) | SDTX_CATEGORY_HARDWARE_ERROR)
|
|
+#define SDTX_UNKNOWN(code) ((code) | SDTX_CATEGORY_UNKNOWN)
|
|
+
|
|
+#define SDTX_SUCCESS(value) (SDTX_CATEGORY(value) == SDTX_CATEGORY_STATUS)
|
|
+
|
|
+/* Latch status values */
|
|
+#define SDTX_LATCH_CLOSED SDTX_STATUS(0x00)
|
|
+#define SDTX_LATCH_OPENED SDTX_STATUS(0x01)
|
|
+
|
|
+/* Base state values */
|
|
+#define SDTX_BASE_DETACHED SDTX_STATUS(0x00)
|
|
+#define SDTX_BASE_ATTACHED SDTX_STATUS(0x01)
|
|
+
|
|
+/* Runtime errors (non-critical) */
|
|
+#define SDTX_DETACH_NOT_FEASIBLE SDTX_ERR_RT(0x01)
|
|
+#define SDTX_DETACH_TIMEDOUT SDTX_ERR_RT(0x02)
|
|
+
|
|
+/* Hardware errors (critical) */
|
|
+#define SDTX_ERR_FAILED_TO_OPEN SDTX_ERR_HW(0x01)
|
|
+#define SDTX_ERR_FAILED_TO_REMAIN_OPEN SDTX_ERR_HW(0x02)
|
|
+#define SDTX_ERR_FAILED_TO_CLOSE SDTX_ERR_HW(0x03)
|
|
+
|
|
+/* Base types */
|
|
+#define SDTX_DEVICE_TYPE_HID 0x0100
|
|
+#define SDTX_DEVICE_TYPE_SSH 0x0200
|
|
+
|
|
+#define SDTX_DEVICE_TYPE_MASK 0x0f00
|
|
+#define SDTX_DEVICE_TYPE(value) ((value) & SDTX_DEVICE_TYPE_MASK)
|
|
+
|
|
+#define SDTX_BASE_TYPE_HID(id) ((id) | SDTX_DEVICE_TYPE_HID)
|
|
+#define SDTX_BASE_TYPE_SSH(id) ((id) | SDTX_DEVICE_TYPE_SSH)
|
|
+
|
|
+/**
|
|
+ * enum sdtx_device_mode - Mode describing how (and if) the clipboard is
|
|
+ * attached to the base of the device.
|
|
+ * @SDTX_DEVICE_MODE_TABLET: The clipboard is detached from the base and the
|
|
+ * device operates as tablet.
|
|
+ * @SDTX_DEVICE_MODE_LAPTOP: The clipboard is attached normally to the base
|
|
+ * and the device operates as laptop.
|
|
+ * @SDTX_DEVICE_MODE_STUDIO: The clipboard is attached to the base in reverse.
|
|
+ * The device operates as tablet with keyboard and
|
|
+ * touchpad deactivated, however, the base battery
|
|
+ * and, if present in the specific device model, dGPU
|
|
+ * are available to the system.
|
|
+ */
|
|
+enum sdtx_device_mode {
|
|
+ SDTX_DEVICE_MODE_TABLET = 0x00,
|
|
+ SDTX_DEVICE_MODE_LAPTOP = 0x01,
|
|
+ SDTX_DEVICE_MODE_STUDIO = 0x02,
|
|
+};
|
|
+
|
|
+/**
|
|
+ * struct sdtx_event - Event provided by reading from the DTX device file.
|
|
+ * @length: Length of the event payload, in bytes.
|
|
+ * @code: Event code, detailing what type of event this is.
|
|
+ * @data: Payload of the event, containing @length bytes.
|
|
+ *
|
|
+ * See &enum sdtx_event_code for currently valid event codes.
|
|
+ */
|
|
+struct sdtx_event {
|
|
+ __u16 length;
|
|
+ __u16 code;
|
|
+ __u8 data[];
|
|
+} __attribute__((__packed__));
|
|
+
|
|
+/**
|
|
+ * enum sdtx_event_code - Code describing the type of an event.
|
|
+ * @SDTX_EVENT_REQUEST: Detachment request event type.
|
|
+ * @SDTX_EVENT_CANCEL: Cancel detachment process event type.
|
|
+ * @SDTX_EVENT_BASE_CONNECTION: Base/clipboard connection change event type.
|
|
+ * @SDTX_EVENT_LATCH_STATUS: Latch status change event type.
|
|
+ * @SDTX_EVENT_DEVICE_MODE: Device mode change event type.
|
|
+ *
|
|
+ * Used in &struct sdtx_event to describe the type of the event. Further event
|
|
+ * codes are reserved for future use. Any event parser should be able to
|
|
+ * gracefully handle unknown events, i.e. by simply skipping them.
|
|
+ *
|
|
+ * Consult the DTX user-space interface documentation for details regarding
|
|
+ * the individual event types.
|
|
+ */
|
|
+enum sdtx_event_code {
|
|
+ SDTX_EVENT_REQUEST = 1,
|
|
+ SDTX_EVENT_CANCEL = 2,
|
|
+ SDTX_EVENT_BASE_CONNECTION = 3,
|
|
+ SDTX_EVENT_LATCH_STATUS = 4,
|
|
+ SDTX_EVENT_DEVICE_MODE = 5,
|
|
+};
|
|
+
|
|
+/**
|
|
+ * struct sdtx_base_info - Describes if and what type of base is connected.
|
|
+ * @state: The state of the connection. Valid values are %SDTX_BASE_DETACHED,
|
|
+ * %SDTX_BASE_ATTACHED, and %SDTX_DETACH_NOT_FEASIBLE (in case a base
|
|
+ * is attached but low clipboard battery prevents detachment). Other
|
|
+ * values are currently reserved.
|
|
+ * @base_id: The type of base connected. Zero if no base is connected.
|
|
+ */
|
|
+struct sdtx_base_info {
|
|
+ __u16 state;
|
|
+ __u16 base_id;
|
|
+} __attribute__((__packed__));
|
|
+
|
|
+/* IOCTLs */
|
|
+#define SDTX_IOCTL_EVENTS_ENABLE _IO(0xa5, 0x21)
|
|
+#define SDTX_IOCTL_EVENTS_DISABLE _IO(0xa5, 0x22)
|
|
+
|
|
+#define SDTX_IOCTL_LATCH_LOCK _IO(0xa5, 0x23)
|
|
+#define SDTX_IOCTL_LATCH_UNLOCK _IO(0xa5, 0x24)
|
|
+
|
|
+#define SDTX_IOCTL_LATCH_REQUEST _IO(0xa5, 0x25)
|
|
+#define SDTX_IOCTL_LATCH_CONFIRM _IO(0xa5, 0x26)
|
|
+#define SDTX_IOCTL_LATCH_HEARTBEAT _IO(0xa5, 0x27)
|
|
+#define SDTX_IOCTL_LATCH_CANCEL _IO(0xa5, 0x28)
|
|
+
|
|
+#define SDTX_IOCTL_GET_BASE_INFO _IOR(0xa5, 0x29, struct sdtx_base_info)
|
|
+#define SDTX_IOCTL_GET_DEVICE_MODE _IOR(0xa5, 0x2a, __u16)
|
|
+#define SDTX_IOCTL_GET_LATCH_STATUS _IOR(0xa5, 0x2b, __u16)
|
|
+
|
|
+#endif /* _UAPI_LINUX_SURFACE_AGGREGATOR_DTX_H */
|
|
--
|
|
2.32.0
|
|
|
|
From 710cbcbc3064eb0815f9f82a67762e9438fc6f94 Mon Sep 17 00:00:00 2001
|
|
From: Maximilian Luz <luzmaximilian@gmail.com>
|
|
Date: Mon, 8 Mar 2021 19:48:18 +0100
|
|
Subject: [PATCH] platform/surface: dtx: Add support for native SSAM devices
|
|
|
|
Add support for native SSAM devices to the DTX driver. This allows
|
|
support for the Surface Book 3, on which the DTX device is not present
|
|
in ACPI.
|
|
|
|
Signed-off-by: Maximilian Luz <luzmaximilian@gmail.com>
|
|
Link: https://lore.kernel.org/r/20210308184819.437438-3-luzmaximilian@gmail.com
|
|
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
|
|
Patchset: surface-sam
|
|
---
|
|
drivers/platform/surface/Kconfig | 4 ++
|
|
drivers/platform/surface/surface_dtx.c | 90 +++++++++++++++++++++++++-
|
|
2 files changed, 93 insertions(+), 1 deletion(-)
|
|
|
|
diff --git a/drivers/platform/surface/Kconfig b/drivers/platform/surface/Kconfig
|
|
index 41d67bb250fd..53beaedefdd1 100644
|
|
--- a/drivers/platform/surface/Kconfig
|
|
+++ b/drivers/platform/surface/Kconfig
|
|
@@ -127,6 +127,10 @@ config SURFACE_DTX
|
|
behavior of this process, which includes the option to abort it in
|
|
case the base is still in use or speed it up in case it is not.
|
|
|
|
+ Note that this module can be built without support for the Surface
|
|
+ Aggregator Bus (i.e. CONFIG_SURFACE_AGGREGATOR_BUS=n). In that case,
|
|
+ some devices, specifically the Surface Book 3, will not be supported.
|
|
+
|
|
config SURFACE_GPE
|
|
tristate "Surface GPE/Lid Support Driver"
|
|
depends on DMI
|
|
diff --git a/drivers/platform/surface/surface_dtx.c b/drivers/platform/surface/surface_dtx.c
|
|
index 1301fab0ea14..85451eb94d98 100644
|
|
--- a/drivers/platform/surface/surface_dtx.c
|
|
+++ b/drivers/platform/surface/surface_dtx.c
|
|
@@ -27,6 +27,7 @@
|
|
#include <linux/workqueue.h>
|
|
|
|
#include <linux/surface_aggregator/controller.h>
|
|
+#include <linux/surface_aggregator/device.h>
|
|
#include <linux/surface_aggregator/dtx.h>
|
|
|
|
|
|
@@ -1194,7 +1195,94 @@ static struct platform_driver surface_dtx_platform_driver = {
|
|
.probe_type = PROBE_PREFER_ASYNCHRONOUS,
|
|
},
|
|
};
|
|
-module_platform_driver(surface_dtx_platform_driver);
|
|
+
|
|
+
|
|
+/* -- SSAM device driver. --------------------------------------------------- */
|
|
+
|
|
+#ifdef CONFIG_SURFACE_AGGREGATOR_BUS
|
|
+
|
|
+static int surface_dtx_ssam_probe(struct ssam_device *sdev)
|
|
+{
|
|
+ struct sdtx_device *ddev;
|
|
+
|
|
+ ddev = sdtx_device_create(&sdev->dev, sdev->ctrl);
|
|
+ if (IS_ERR(ddev))
|
|
+ return PTR_ERR(ddev);
|
|
+
|
|
+ ssam_device_set_drvdata(sdev, ddev);
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static void surface_dtx_ssam_remove(struct ssam_device *sdev)
|
|
+{
|
|
+ sdtx_device_destroy(ssam_device_get_drvdata(sdev));
|
|
+}
|
|
+
|
|
+static const struct ssam_device_id surface_dtx_ssam_match[] = {
|
|
+ { SSAM_SDEV(BAS, 0x01, 0x00, 0x00) },
|
|
+ { },
|
|
+};
|
|
+MODULE_DEVICE_TABLE(ssam, surface_dtx_ssam_match);
|
|
+
|
|
+static struct ssam_device_driver surface_dtx_ssam_driver = {
|
|
+ .probe = surface_dtx_ssam_probe,
|
|
+ .remove = surface_dtx_ssam_remove,
|
|
+ .match_table = surface_dtx_ssam_match,
|
|
+ .driver = {
|
|
+ .name = "surface_dtx",
|
|
+ .pm = &surface_dtx_pm_ops,
|
|
+ .probe_type = PROBE_PREFER_ASYNCHRONOUS,
|
|
+ },
|
|
+};
|
|
+
|
|
+static int ssam_dtx_driver_register(void)
|
|
+{
|
|
+ return ssam_device_driver_register(&surface_dtx_ssam_driver);
|
|
+}
|
|
+
|
|
+static void ssam_dtx_driver_unregister(void)
|
|
+{
|
|
+ ssam_device_driver_unregister(&surface_dtx_ssam_driver);
|
|
+}
|
|
+
|
|
+#else /* CONFIG_SURFACE_AGGREGATOR_BUS */
|
|
+
|
|
+static int ssam_dtx_driver_register(void)
|
|
+{
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static void ssam_dtx_driver_unregister(void)
|
|
+{
|
|
+}
|
|
+
|
|
+#endif /* CONFIG_SURFACE_AGGREGATOR_BUS */
|
|
+
|
|
+
|
|
+/* -- Module setup. --------------------------------------------------------- */
|
|
+
|
|
+static int __init surface_dtx_init(void)
|
|
+{
|
|
+ int status;
|
|
+
|
|
+ status = ssam_dtx_driver_register();
|
|
+ if (status)
|
|
+ return status;
|
|
+
|
|
+ status = platform_driver_register(&surface_dtx_platform_driver);
|
|
+ if (status)
|
|
+ ssam_dtx_driver_unregister();
|
|
+
|
|
+ return status;
|
|
+}
|
|
+module_init(surface_dtx_init);
|
|
+
|
|
+static void __exit surface_dtx_exit(void)
|
|
+{
|
|
+ platform_driver_unregister(&surface_dtx_platform_driver);
|
|
+ ssam_dtx_driver_unregister();
|
|
+}
|
|
+module_exit(surface_dtx_exit);
|
|
|
|
MODULE_AUTHOR("Maximilian Luz <luzmaximilian@gmail.com>");
|
|
MODULE_DESCRIPTION("Detachment-system driver for Surface System Aggregator Module");
|
|
--
|
|
2.32.0
|
|
|
|
From 6c4d22698f9e940ae13d6f58d46f28f2ae90fde9 Mon Sep 17 00:00:00 2001
|
|
From: Maximilian Luz <luzmaximilian@gmail.com>
|
|
Date: Mon, 8 Mar 2021 19:48:19 +0100
|
|
Subject: [PATCH] docs: driver-api: Add Surface DTX driver documentation
|
|
|
|
Add documentation for the user-space interface of the Surface DTX
|
|
(detachment system) driver, used on Microsoft Surface Book series
|
|
devices.
|
|
|
|
Signed-off-by: Maximilian Luz <luzmaximilian@gmail.com>
|
|
Link: https://lore.kernel.org/r/20210308184819.437438-4-luzmaximilian@gmail.com
|
|
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
|
|
Patchset: surface-sam
|
|
---
|
|
.../surface_aggregator/clients/dtx.rst | 718 ++++++++++++++++++
|
|
.../surface_aggregator/clients/index.rst | 1 +
|
|
MAINTAINERS | 1 +
|
|
3 files changed, 720 insertions(+)
|
|
create mode 100644 Documentation/driver-api/surface_aggregator/clients/dtx.rst
|
|
|
|
diff --git a/Documentation/driver-api/surface_aggregator/clients/dtx.rst b/Documentation/driver-api/surface_aggregator/clients/dtx.rst
|
|
new file mode 100644
|
|
index 000000000000..e7e7c20007f0
|
|
--- /dev/null
|
|
+++ b/Documentation/driver-api/surface_aggregator/clients/dtx.rst
|
|
@@ -0,0 +1,718 @@
|
|
+.. SPDX-License-Identifier: GPL-2.0+
|
|
+
|
|
+.. |__u16| replace:: :c:type:`__u16 <__u16>`
|
|
+.. |sdtx_event| replace:: :c:type:`struct sdtx_event <sdtx_event>`
|
|
+.. |sdtx_event_code| replace:: :c:type:`enum sdtx_event_code <sdtx_event_code>`
|
|
+.. |sdtx_base_info| replace:: :c:type:`struct sdtx_base_info <sdtx_base_info>`
|
|
+.. |sdtx_device_mode| replace:: :c:type:`struct sdtx_device_mode <sdtx_device_mode>`
|
|
+
|
|
+======================================================
|
|
+User-Space DTX (Clipboard Detachment System) Interface
|
|
+======================================================
|
|
+
|
|
+The ``surface_dtx`` driver is responsible for proper clipboard detachment
|
|
+and re-attachment handling. To this end, it provides the ``/dev/surface/dtx``
|
|
+device file, through which it can interface with a user-space daemon. This
|
|
+daemon is then ultimately responsible for determining and taking necessary
|
|
+actions, such as unmounting devices attached to the base,
|
|
+unloading/reloading the graphics-driver, user-notifications, etc.
|
|
+
|
|
+There are two basic communication principles used in this driver: Commands
|
|
+(in other parts of the documentation also referred to as requests) and
|
|
+events. Commands are sent to the EC and may have a different implications in
|
|
+different contexts. Events are sent by the EC upon some internal state
|
|
+change. Commands are always driver-initiated, whereas events are always
|
|
+initiated by the EC.
|
|
+
|
|
+.. contents::
|
|
+
|
|
+Nomenclature
|
|
+============
|
|
+
|
|
+* **Clipboard:**
|
|
+ The detachable upper part of the Surface Book, housing the screen and CPU.
|
|
+
|
|
+* **Base:**
|
|
+ The lower part of the Surface Book from which the clipboard can be
|
|
+ detached, optionally (model dependent) housing the discrete GPU (dGPU).
|
|
+
|
|
+* **Latch:**
|
|
+ The mechanism keeping the clipboard attached to the base in normal
|
|
+ operation and allowing it to be detached when requested.
|
|
+
|
|
+* **Silently ignored commands:**
|
|
+ The command is accepted by the EC as a valid command and acknowledged
|
|
+ (following the standard communication protocol), but the EC does not act
|
|
+ upon it, i.e. ignores it.e upper part of the
|
|
+
|
|
+
|
|
+Detachment Process
|
|
+==================
|
|
+
|
|
+Warning: This part of the documentation is based on reverse engineering and
|
|
+testing and thus may contain errors or be incomplete.
|
|
+
|
|
+Latch States
|
|
+------------
|
|
+
|
|
+The latch mechanism has two major states: *open* and *closed*. In the
|
|
+*closed* state (default), the clipboard is secured to the base, whereas in
|
|
+the *open* state, the clipboard can be removed by a user.
|
|
+
|
|
+The latch can additionally be locked and, correspondingly, unlocked, which
|
|
+can influence the detachment procedure. Specifically, this locking mechanism
|
|
+is intended to prevent the dGPU, positioned in the base of the device, from
|
|
+being hot-unplugged while in use. More details can be found in the
|
|
+documentation for the detachment procedure below. By default, the latch is
|
|
+unlocked.
|
|
+
|
|
+Detachment Procedure
|
|
+--------------------
|
|
+
|
|
+Note that the detachment process is governed fully by the EC. The
|
|
+``surface_dtx`` driver only relays events from the EC to user-space and
|
|
+commands from user-space to the EC, i.e. it does not influence this process.
|
|
+
|
|
+The detachment process is started with the user pressing the *detach* button
|
|
+on the base of the device or executing the ``SDTX_IOCTL_LATCH_REQUEST`` IOCTL.
|
|
+Following that:
|
|
+
|
|
+1. The EC turns on the indicator led on the detach-button, sends a
|
|
+ *detach-request* event (``SDTX_EVENT_REQUEST``), and awaits further
|
|
+ instructions/commands. In case the latch is unlocked, the led will flash
|
|
+ green. If the latch has been locked, the led will be solid red
|
|
+
|
|
+2. The event is, via the ``surface_dtx`` driver, relayed to user-space, where
|
|
+ an appropriate user-space daemon can handle it and send instructions back
|
|
+ to the EC via IOCTLs provided by this driver.
|
|
+
|
|
+3. The EC waits for instructions from user-space and acts according to them.
|
|
+ If the EC does not receive any instructions in a given period, it will
|
|
+ time out and continue as follows:
|
|
+
|
|
+ - If the latch is unlocked, the EC will open the latch and the clipboard
|
|
+ can be detached from the base. This is the exact behavior as without
|
|
+ this driver or any user-space daemon. See the ``SDTX_IOCTL_LATCH_CONFIRM``
|
|
+ description below for more details on the follow-up behavior of the EC.
|
|
+
|
|
+ - If the latch is locked, the EC will *not* open the latch, meaning the
|
|
+ clipboard cannot be detached from the base. Furthermore, the EC sends
|
|
+ an cancel event (``SDTX_EVENT_CANCEL``) detailing this with the cancel
|
|
+ reason ``SDTX_DETACH_TIMEDOUT`` (see :ref:`events` for details).
|
|
+
|
|
+Valid responses by a user-space daemon to a detachment request event are:
|
|
+
|
|
+- Execute ``SDTX_IOCTL_LATCH_REQUEST``. This will immediately abort the
|
|
+ detachment process. Furthermore, the EC will send a detach-request event,
|
|
+ similar to the user pressing the detach-button to cancel said process (see
|
|
+ below).
|
|
+
|
|
+- Execute ``SDTX_IOCTL_LATCH_CONFIRM``. This will cause the EC to open the
|
|
+ latch, after which the user can separate clipboard and base.
|
|
+
|
|
+ As this changes the latch state, a *latch-status* event
|
|
+ (``SDTX_EVENT_LATCH_STATUS``) will be sent once the latch has been opened
|
|
+ successfully. If the EC fails to open the latch, e.g. due to hardware
|
|
+ error or low battery, a latch-cancel event (``SDTX_EVENT_CANCEL``) will be
|
|
+ sent with the cancel reason indicating the specific failure.
|
|
+
|
|
+ If the latch is currently locked, the latch will automatically be
|
|
+ unlocked before it is opened.
|
|
+
|
|
+- Execute ``SDTX_IOCTL_LATCH_HEARTBEAT``. This will reset the internal timeout.
|
|
+ No other actions will be performed, i.e. the detachment process will neither
|
|
+ be completed nor canceled, and the EC will still be waiting for further
|
|
+ responses.
|
|
+
|
|
+- Execute ``SDTX_IOCTL_LATCH_CANCEL``. This will abort the detachment process,
|
|
+ similar to ``SDTX_IOCTL_LATCH_REQUEST``, described above, or the button
|
|
+ press, described below. A *generic request* event (``SDTX_EVENT_REQUEST``)
|
|
+ is send in response to this. In contrast to those, however, this command
|
|
+ does not trigger a new detachment process if none is currently in
|
|
+ progress.
|
|
+
|
|
+- Do nothing. The detachment process eventually times out as described in
|
|
+ point 3.
|
|
+
|
|
+See :ref:`ioctls` for more details on these responses.
|
|
+
|
|
+It is important to note that, if the user presses the detach button at any
|
|
+point when a detachment operation is in progress (i.e. after the EC has sent
|
|
+the initial *detach-request* event (``SDTX_EVENT_REQUEST``) and before it
|
|
+received the corresponding response concluding the process), the detachment
|
|
+process is canceled on the EC-level and an identical event is being sent.
|
|
+Thus a *detach-request* event, by itself, does not signal the start of the
|
|
+detachment process.
|
|
+
|
|
+The detachment process may further be canceled by the EC due to hardware
|
|
+failures or a low clipboard battery. This is done via a cancel event
|
|
+(``SDTX_EVENT_CANCEL``) with the corresponding cancel reason.
|
|
+
|
|
+
|
|
+User-Space Interface Documentation
|
|
+==================================
|
|
+
|
|
+Error Codes and Status Values
|
|
+-----------------------------
|
|
+
|
|
+Error and status codes are divided into different categories, which can be
|
|
+used to determine if the status code is an error, and, if it is, the
|
|
+severity and type of that error. The current categories are:
|
|
+
|
|
+.. flat-table:: Overview of Status/Error Categories.
|
|
+ :widths: 2 1 3
|
|
+ :header-rows: 1
|
|
+
|
|
+ * - Name
|
|
+ - Value
|
|
+ - Short Description
|
|
+
|
|
+ * - ``STATUS``
|
|
+ - ``0x0000``
|
|
+ - Non-error status codes.
|
|
+
|
|
+ * - ``RUNTIME_ERROR``
|
|
+ - ``0x1000``
|
|
+ - Non-critical runtime errors.
|
|
+
|
|
+ * - ``HARDWARE_ERROR``
|
|
+ - ``0x2000``
|
|
+ - Critical hardware failures.
|
|
+
|
|
+ * - ``UNKNOWN``
|
|
+ - ``0xF000``
|
|
+ - Unknown error codes.
|
|
+
|
|
+Other categories are reserved for future use. The ``SDTX_CATEGORY()`` macro
|
|
+can be used to determine the category of any status value. The
|
|
+``SDTX_SUCCESS()`` macro can be used to check if the status value is a
|
|
+success value (``SDTX_CATEGORY_STATUS``) or if it indicates a failure.
|
|
+
|
|
+Unknown status or error codes sent by the EC are assigned to the ``UNKNOWN``
|
|
+category by the driver and may be implemented via their own code in the
|
|
+future.
|
|
+
|
|
+Currently used error codes are:
|
|
+
|
|
+.. flat-table:: Overview of Error Codes.
|
|
+ :widths: 2 1 1 3
|
|
+ :header-rows: 1
|
|
+
|
|
+ * - Name
|
|
+ - Category
|
|
+ - Value
|
|
+ - Short Description
|
|
+
|
|
+ * - ``SDTX_DETACH_NOT_FEASIBLE``
|
|
+ - ``RUNTIME``
|
|
+ - ``0x1001``
|
|
+ - Detachment not feasible due to low clipboard battery.
|
|
+
|
|
+ * - ``SDTX_DETACH_TIMEDOUT``
|
|
+ - ``RUNTIME``
|
|
+ - ``0x1002``
|
|
+ - Detachment process timed out while the latch was locked.
|
|
+
|
|
+ * - ``SDTX_ERR_FAILED_TO_OPEN``
|
|
+ - ``HARDWARE``
|
|
+ - ``0x2001``
|
|
+ - Failed to open latch.
|
|
+
|
|
+ * - ``SDTX_ERR_FAILED_TO_REMAIN_OPEN``
|
|
+ - ``HARDWARE``
|
|
+ - ``0x2002``
|
|
+ - Failed to keep latch open.
|
|
+
|
|
+ * - ``SDTX_ERR_FAILED_TO_CLOSE``
|
|
+ - ``HARDWARE``
|
|
+ - ``0x2003``
|
|
+ - Failed to close latch.
|
|
+
|
|
+Other error codes are reserved for future use. Non-error status codes may
|
|
+overlap and are generally only unique within their use-case:
|
|
+
|
|
+.. flat-table:: Latch Status Codes.
|
|
+ :widths: 2 1 1 3
|
|
+ :header-rows: 1
|
|
+
|
|
+ * - Name
|
|
+ - Category
|
|
+ - Value
|
|
+ - Short Description
|
|
+
|
|
+ * - ``SDTX_LATCH_CLOSED``
|
|
+ - ``STATUS``
|
|
+ - ``0x0000``
|
|
+ - Latch is closed/has been closed.
|
|
+
|
|
+ * - ``SDTX_LATCH_OPENED``
|
|
+ - ``STATUS``
|
|
+ - ``0x0001``
|
|
+ - Latch is open/has been opened.
|
|
+
|
|
+.. flat-table:: Base State Codes.
|
|
+ :widths: 2 1 1 3
|
|
+ :header-rows: 1
|
|
+
|
|
+ * - Name
|
|
+ - Category
|
|
+ - Value
|
|
+ - Short Description
|
|
+
|
|
+ * - ``SDTX_BASE_DETACHED``
|
|
+ - ``STATUS``
|
|
+ - ``0x0000``
|
|
+ - Base has been detached/is not present.
|
|
+
|
|
+ * - ``SDTX_BASE_ATTACHED``
|
|
+ - ``STATUS``
|
|
+ - ``0x0001``
|
|
+ - Base has been attached/is present.
|
|
+
|
|
+Again, other codes are reserved for future use.
|
|
+
|
|
+.. _events:
|
|
+
|
|
+Events
|
|
+------
|
|
+
|
|
+Events can be received by reading from the device file. They are disabled by
|
|
+default and have to be enabled by executing ``SDTX_IOCTL_EVENTS_ENABLE``
|
|
+first. All events follow the layout prescribed by |sdtx_event|. Specific
|
|
+event types can be identified by their event code, described in
|
|
+|sdtx_event_code|. Note that other event codes are reserved for future use,
|
|
+thus an event parser must be able to handle any unknown/unsupported event
|
|
+types gracefully, by relying on the payload length given in the event header.
|
|
+
|
|
+Currently provided event types are:
|
|
+
|
|
+.. flat-table:: Overview of DTX events.
|
|
+ :widths: 2 1 1 3
|
|
+ :header-rows: 1
|
|
+
|
|
+ * - Name
|
|
+ - Code
|
|
+ - Payload
|
|
+ - Short Description
|
|
+
|
|
+ * - ``SDTX_EVENT_REQUEST``
|
|
+ - ``1``
|
|
+ - ``0`` bytes
|
|
+ - Detachment process initiated/aborted.
|
|
+
|
|
+ * - ``SDTX_EVENT_CANCEL``
|
|
+ - ``2``
|
|
+ - ``2`` bytes
|
|
+ - EC canceled detachment process.
|
|
+
|
|
+ * - ``SDTX_EVENT_BASE_CONNECTION``
|
|
+ - ``3``
|
|
+ - ``4`` bytes
|
|
+ - Base connection state changed.
|
|
+
|
|
+ * - ``SDTX_EVENT_LATCH_STATUS``
|
|
+ - ``4``
|
|
+ - ``2`` bytes
|
|
+ - Latch status changed.
|
|
+
|
|
+ * - ``SDTX_EVENT_DEVICE_MODE``
|
|
+ - ``5``
|
|
+ - ``2`` bytes
|
|
+ - Device mode changed.
|
|
+
|
|
+Individual events in more detail:
|
|
+
|
|
+``SDTX_EVENT_REQUEST``
|
|
+^^^^^^^^^^^^^^^^^^^^^^
|
|
+
|
|
+Sent when a detachment process is started or, if in progress, aborted by the
|
|
+user, either via a detach button press or a detach request
|
|
+(``SDTX_IOCTL_LATCH_REQUEST``) being sent from user-space.
|
|
+
|
|
+Does not have any payload.
|
|
+
|
|
+``SDTX_EVENT_CANCEL``
|
|
+^^^^^^^^^^^^^^^^^^^^^
|
|
+
|
|
+Sent when a detachment process is canceled by the EC due to unfulfilled
|
|
+preconditions (e.g. clipboard battery too low to detach) or hardware
|
|
+failure. The reason for cancellation is given in the event payload detailed
|
|
+below and can be one of
|
|
+
|
|
+* ``SDTX_DETACH_TIMEDOUT``: Detachment timed out while the latch was locked.
|
|
+ The latch has neither been opened nor unlocked.
|
|
+
|
|
+* ``SDTX_DETACH_NOT_FEASIBLE``: Detachment not feasible due to low clipboard
|
|
+ battery.
|
|
+
|
|
+* ``SDTX_ERR_FAILED_TO_OPEN``: Could not open the latch (hardware failure).
|
|
+
|
|
+* ``SDTX_ERR_FAILED_TO_REMAIN_OPEN``: Could not keep the latch open (hardware
|
|
+ failure).
|
|
+
|
|
+* ``SDTX_ERR_FAILED_TO_CLOSE``: Could not close the latch (hardware failure).
|
|
+
|
|
+Other error codes in this context are reserved for future use.
|
|
+
|
|
+These codes can be classified via the ``SDTX_CATEGORY()`` macro to discern
|
|
+between critical hardware errors (``SDTX_CATEGORY_HARDWARE_ERROR``) or
|
|
+runtime errors (``SDTX_CATEGORY_RUNTIME_ERROR``), the latter of which may
|
|
+happen during normal operation if certain preconditions for detachment are
|
|
+not given.
|
|
+
|
|
+.. flat-table:: Detachment Cancel Event Payload
|
|
+ :widths: 1 1 4
|
|
+ :header-rows: 1
|
|
+
|
|
+ * - Field
|
|
+ - Type
|
|
+ - Description
|
|
+
|
|
+ * - ``reason``
|
|
+ - |__u16|
|
|
+ - Reason for cancellation.
|
|
+
|
|
+``SDTX_EVENT_BASE_CONNECTION``
|
|
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
+
|
|
+Sent when the base connection state has changed, i.e. when the base has been
|
|
+attached, detached, or detachment has become infeasible due to low clipboard
|
|
+battery. The new state and, if a base is connected, ID of the base is
|
|
+provided as payload of type |sdtx_base_info| with its layout presented
|
|
+below:
|
|
+
|
|
+.. flat-table:: Base-Connection-Change Event Payload
|
|
+ :widths: 1 1 4
|
|
+ :header-rows: 1
|
|
+
|
|
+ * - Field
|
|
+ - Type
|
|
+ - Description
|
|
+
|
|
+ * - ``state``
|
|
+ - |__u16|
|
|
+ - Base connection state.
|
|
+
|
|
+ * - ``base_id``
|
|
+ - |__u16|
|
|
+ - Type of base connected (zero if none).
|
|
+
|
|
+Possible values for ``state`` are:
|
|
+
|
|
+* ``SDTX_BASE_DETACHED``,
|
|
+* ``SDTX_BASE_ATTACHED``, and
|
|
+* ``SDTX_DETACH_NOT_FEASIBLE``.
|
|
+
|
|
+Other values are reserved for future use.
|
|
+
|
|
+``SDTX_EVENT_LATCH_STATUS``
|
|
+^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
+
|
|
+Sent when the latch status has changed, i.e. when the latch has been opened,
|
|
+closed, or an error occurred. The current status is provided as payload:
|
|
+
|
|
+.. flat-table:: Latch-Status-Change Event Payload
|
|
+ :widths: 1 1 4
|
|
+ :header-rows: 1
|
|
+
|
|
+ * - Field
|
|
+ - Type
|
|
+ - Description
|
|
+
|
|
+ * - ``status``
|
|
+ - |__u16|
|
|
+ - Latch status.
|
|
+
|
|
+Possible values for ``status`` are:
|
|
+
|
|
+* ``SDTX_LATCH_CLOSED``,
|
|
+* ``SDTX_LATCH_OPENED``,
|
|
+* ``SDTX_ERR_FAILED_TO_OPEN``,
|
|
+* ``SDTX_ERR_FAILED_TO_REMAIN_OPEN``, and
|
|
+* ``SDTX_ERR_FAILED_TO_CLOSE``.
|
|
+
|
|
+Other values are reserved for future use.
|
|
+
|
|
+``SDTX_EVENT_DEVICE_MODE``
|
|
+^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
+
|
|
+Sent when the device mode has changed. The new device mode is provided as
|
|
+payload:
|
|
+
|
|
+.. flat-table:: Device-Mode-Change Event Payload
|
|
+ :widths: 1 1 4
|
|
+ :header-rows: 1
|
|
+
|
|
+ * - Field
|
|
+ - Type
|
|
+ - Description
|
|
+
|
|
+ * - ``mode``
|
|
+ - |__u16|
|
|
+ - Device operation mode.
|
|
+
|
|
+Possible values for ``mode`` are:
|
|
+
|
|
+* ``SDTX_DEVICE_MODE_TABLET``,
|
|
+* ``SDTX_DEVICE_MODE_LAPTOP``, and
|
|
+* ``SDTX_DEVICE_MODE_STUDIO``.
|
|
+
|
|
+Other values are reserved for future use.
|
|
+
|
|
+.. _ioctls:
|
|
+
|
|
+IOCTLs
|
|
+------
|
|
+
|
|
+The following IOCTLs are provided:
|
|
+
|
|
+.. flat-table:: Overview of DTX IOCTLs
|
|
+ :widths: 1 1 1 1 4
|
|
+ :header-rows: 1
|
|
+
|
|
+ * - Type
|
|
+ - Number
|
|
+ - Direction
|
|
+ - Name
|
|
+ - Description
|
|
+
|
|
+ * - ``0xA5``
|
|
+ - ``0x21``
|
|
+ - ``-``
|
|
+ - ``EVENTS_ENABLE``
|
|
+ - Enable events for the current file descriptor.
|
|
+
|
|
+ * - ``0xA5``
|
|
+ - ``0x22``
|
|
+ - ``-``
|
|
+ - ``EVENTS_DISABLE``
|
|
+ - Disable events for the current file descriptor.
|
|
+
|
|
+ * - ``0xA5``
|
|
+ - ``0x23``
|
|
+ - ``-``
|
|
+ - ``LATCH_LOCK``
|
|
+ - Lock the latch.
|
|
+
|
|
+ * - ``0xA5``
|
|
+ - ``0x24``
|
|
+ - ``-``
|
|
+ - ``LATCH_UNLOCK``
|
|
+ - Unlock the latch.
|
|
+
|
|
+ * - ``0xA5``
|
|
+ - ``0x25``
|
|
+ - ``-``
|
|
+ - ``LATCH_REQUEST``
|
|
+ - Request clipboard detachment.
|
|
+
|
|
+ * - ``0xA5``
|
|
+ - ``0x26``
|
|
+ - ``-``
|
|
+ - ``LATCH_CONFIRM``
|
|
+ - Confirm clipboard detachment request.
|
|
+
|
|
+ * - ``0xA5``
|
|
+ - ``0x27``
|
|
+ - ``-``
|
|
+ - ``LATCH_HEARTBEAT``
|
|
+ - Send heartbeat signal to EC.
|
|
+
|
|
+ * - ``0xA5``
|
|
+ - ``0x28``
|
|
+ - ``-``
|
|
+ - ``LATCH_CANCEL``
|
|
+ - Cancel detachment process.
|
|
+
|
|
+ * - ``0xA5``
|
|
+ - ``0x29``
|
|
+ - ``R``
|
|
+ - ``GET_BASE_INFO``
|
|
+ - Get current base/connection information.
|
|
+
|
|
+ * - ``0xA5``
|
|
+ - ``0x2A``
|
|
+ - ``R``
|
|
+ - ``GET_DEVICE_MODE``
|
|
+ - Get current device operation mode.
|
|
+
|
|
+ * - ``0xA5``
|
|
+ - ``0x2B``
|
|
+ - ``R``
|
|
+ - ``GET_LATCH_STATUS``
|
|
+ - Get current device latch status.
|
|
+
|
|
+``SDTX_IOCTL_EVENTS_ENABLE``
|
|
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
+
|
|
+Defined as ``_IO(0xA5, 0x22)``.
|
|
+
|
|
+Enable events for the current file descriptor. Events can be obtained by
|
|
+reading from the device, if enabled. Events are disabled by default.
|
|
+
|
|
+``SDTX_IOCTL_EVENTS_DISABLE``
|
|
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
+
|
|
+Defined as ``_IO(0xA5, 0x22)``.
|
|
+
|
|
+Disable events for the current file descriptor. Events can be obtained by
|
|
+reading from the device, if enabled. Events are disabled by default.
|
|
+
|
|
+``SDTX_IOCTL_LATCH_LOCK``
|
|
+^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
+
|
|
+Defined as ``_IO(0xA5, 0x23)``.
|
|
+
|
|
+Locks the latch, causing the detachment procedure to abort without opening
|
|
+the latch on timeout. The latch is unlocked by default. This command will be
|
|
+silently ignored if the latch is already locked.
|
|
+
|
|
+``SDTX_IOCTL_LATCH_UNLOCK``
|
|
+^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
+
|
|
+Defined as ``_IO(0xA5, 0x24)``.
|
|
+
|
|
+Unlocks the latch, causing the detachment procedure to open the latch on
|
|
+timeout. The latch is unlocked by default. This command will not open the
|
|
+latch when sent during an ongoing detachment process. It will be silently
|
|
+ignored if the latch is already unlocked.
|
|
+
|
|
+``SDTX_IOCTL_LATCH_REQUEST``
|
|
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
+
|
|
+Defined as ``_IO(0xA5, 0x25)``.
|
|
+
|
|
+Generic latch request. Behavior depends on the context: If no
|
|
+detachment-process is active, detachment is requested. Otherwise the
|
|
+currently active detachment-process will be aborted.
|
|
+
|
|
+If a detachment process is canceled by this operation, a generic detachment
|
|
+request event (``SDTX_EVENT_REQUEST``) will be sent.
|
|
+
|
|
+This essentially behaves the same as a detachment button press.
|
|
+
|
|
+``SDTX_IOCTL_LATCH_CONFIRM``
|
|
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
+
|
|
+Defined as ``_IO(0xA5, 0x26)``.
|
|
+
|
|
+Acknowledges and confirms a latch request. If sent during an ongoing
|
|
+detachment process, this command causes the latch to be opened immediately.
|
|
+The latch will also be opened if it has been locked. In this case, the latch
|
|
+lock is reset to the unlocked state.
|
|
+
|
|
+This command will be silently ignored if there is currently no detachment
|
|
+procedure in progress.
|
|
+
|
|
+``SDTX_IOCTL_LATCH_HEARTBEAT``
|
|
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
+
|
|
+Defined as ``_IO(0xA5, 0x27)``.
|
|
+
|
|
+Sends a heartbeat, essentially resetting the detachment timeout. This
|
|
+command can be used to keep the detachment process alive while work required
|
|
+for the detachment to succeed is still in progress.
|
|
+
|
|
+This command will be silently ignored if there is currently no detachment
|
|
+procedure in progress.
|
|
+
|
|
+``SDTX_IOCTL_LATCH_CANCEL``
|
|
+^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
+
|
|
+Defined as ``_IO(0xA5, 0x28)``.
|
|
+
|
|
+Cancels detachment in progress (if any). If a detachment process is canceled
|
|
+by this operation, a generic detachment request event
|
|
+(``SDTX_EVENT_REQUEST``) will be sent.
|
|
+
|
|
+This command will be silently ignored if there is currently no detachment
|
|
+procedure in progress.
|
|
+
|
|
+``SDTX_IOCTL_GET_BASE_INFO``
|
|
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
+
|
|
+Defined as ``_IOR(0xA5, 0x29, struct sdtx_base_info)``.
|
|
+
|
|
+Get the current base connection state (i.e. attached/detached) and the type
|
|
+of the base connected to the clipboard. This is command essentially provides
|
|
+a way to query the information provided by the base connection change event
|
|
+(``SDTX_EVENT_BASE_CONNECTION``).
|
|
+
|
|
+Possible values for ``struct sdtx_base_info.state`` are:
|
|
+
|
|
+* ``SDTX_BASE_DETACHED``,
|
|
+* ``SDTX_BASE_ATTACHED``, and
|
|
+* ``SDTX_DETACH_NOT_FEASIBLE``.
|
|
+
|
|
+Other values are reserved for future use.
|
|
+
|
|
+``SDTX_IOCTL_GET_DEVICE_MODE``
|
|
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
+
|
|
+Defined as ``_IOR(0xA5, 0x2A, __u16)``.
|
|
+
|
|
+Returns the device operation mode, indicating if and how the base is
|
|
+attached to the clipboard. This is command essentially provides a way to
|
|
+query the information provided by the device mode change event
|
|
+(``SDTX_EVENT_DEVICE_MODE``).
|
|
+
|
|
+Returned values are:
|
|
+
|
|
+* ``SDTX_DEVICE_MODE_LAPTOP``
|
|
+* ``SDTX_DEVICE_MODE_TABLET``
|
|
+* ``SDTX_DEVICE_MODE_STUDIO``
|
|
+
|
|
+See |sdtx_device_mode| for details. Other values are reserved for future
|
|
+use.
|
|
+
|
|
+
|
|
+``SDTX_IOCTL_GET_LATCH_STATUS``
|
|
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
+
|
|
+Defined as ``_IOR(0xA5, 0x2B, __u16)``.
|
|
+
|
|
+Get the current latch status or (presumably) the last error encountered when
|
|
+trying to open/close the latch. This is command essentially provides a way
|
|
+to query the information provided by the latch status change event
|
|
+(``SDTX_EVENT_LATCH_STATUS``).
|
|
+
|
|
+Returned values are:
|
|
+
|
|
+* ``SDTX_LATCH_CLOSED``,
|
|
+* ``SDTX_LATCH_OPENED``,
|
|
+* ``SDTX_ERR_FAILED_TO_OPEN``,
|
|
+* ``SDTX_ERR_FAILED_TO_REMAIN_OPEN``, and
|
|
+* ``SDTX_ERR_FAILED_TO_CLOSE``.
|
|
+
|
|
+Other values are reserved for future use.
|
|
+
|
|
+A Note on Base IDs
|
|
+------------------
|
|
+
|
|
+Base types/IDs provided via ``SDTX_EVENT_BASE_CONNECTION`` or
|
|
+``SDTX_IOCTL_GET_BASE_INFO`` are directly forwarded from the EC in the lower
|
|
+byte of the combined |__u16| value, with the driver storing the EC type from
|
|
+which this ID comes in the high byte (without this, base IDs over different
|
|
+types of ECs may be overlapping).
|
|
+
|
|
+The ``SDTX_DEVICE_TYPE()`` macro can be used to determine the EC device
|
|
+type. This can be one of
|
|
+
|
|
+* ``SDTX_DEVICE_TYPE_HID``, for Surface Aggregator Module over HID, and
|
|
+
|
|
+* ``SDTX_DEVICE_TYPE_SSH``, for Surface Aggregator Module over Surface Serial
|
|
+ Hub.
|
|
+
|
|
+Note that currently only the ``SSH`` type EC is supported, however ``HID``
|
|
+type is reserved for future use.
|
|
+
|
|
+Structures and Enums
|
|
+--------------------
|
|
+
|
|
+.. kernel-doc:: include/uapi/linux/surface_aggregator/dtx.h
|
|
+
|
|
+API Users
|
|
+=========
|
|
+
|
|
+A user-space daemon utilizing this API can be found at
|
|
+https://github.com/linux-surface/surface-dtx-daemon.
|
|
diff --git a/Documentation/driver-api/surface_aggregator/clients/index.rst b/Documentation/driver-api/surface_aggregator/clients/index.rst
|
|
index 3ccabce23271..98ea9946b8a2 100644
|
|
--- a/Documentation/driver-api/surface_aggregator/clients/index.rst
|
|
+++ b/Documentation/driver-api/surface_aggregator/clients/index.rst
|
|
@@ -11,6 +11,7 @@ This is the documentation for client drivers themselves. Refer to
|
|
:maxdepth: 1
|
|
|
|
cdev
|
|
+ dtx
|
|
san
|
|
|
|
.. only:: subproject and html
|
|
diff --git a/MAINTAINERS b/MAINTAINERS
|
|
index 3917e7363520..da1487d672a8 100644
|
|
--- a/MAINTAINERS
|
|
+++ b/MAINTAINERS
|
|
@@ -11872,6 +11872,7 @@ MICROSOFT SURFACE DTX DRIVER
|
|
M: Maximilian Luz <luzmaximilian@gmail.com>
|
|
L: platform-driver-x86@vger.kernel.org
|
|
S: Maintained
|
|
+F: Documentation/driver-api/surface_aggregator/clients/dtx.rst
|
|
F: drivers/platform/surface/surface_dtx.c
|
|
F: include/uapi/linux/surface_aggregator/dtx.h
|
|
|
|
--
|
|
2.32.0
|
|
|
|
From 2ba03413577d1c0491ae621c72e9d11898fe1cb0 Mon Sep 17 00:00:00 2001
|
|
From: Wei Yongjun <weiyongjun1@huawei.com>
|
|
Date: Tue, 9 Mar 2021 13:15:00 +0000
|
|
Subject: [PATCH] platform/surface: aggregator_registry: Make symbol
|
|
'ssam_base_hub_group' static
|
|
|
|
The sparse tool complains as follows:
|
|
|
|
drivers/platform/surface/surface_aggregator_registry.c:355:30: warning:
|
|
symbol 'ssam_base_hub_group' was not declared. Should it be static?
|
|
|
|
This symbol is not used outside of surface_aggregator_registry.c, so this
|
|
commit marks it static.
|
|
|
|
Fixes: 797e78564634 ("platform/surface: aggregator_registry: Add base device hub")
|
|
Reported-by: Hulk Robot <hulkci@huawei.com>
|
|
Signed-off-by: Wei Yongjun <weiyongjun1@huawei.com>
|
|
Reviewed-by: Maximilian Luz <luzmaximilian@gmail.com>
|
|
Link: https://lore.kernel.org/r/20210309131500.1885772-1-weiyongjun1@huawei.com
|
|
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
|
|
Patchset: surface-sam
|
|
---
|
|
drivers/platform/surface/surface_aggregator_registry.c | 2 +-
|
|
1 file changed, 1 insertion(+), 1 deletion(-)
|
|
|
|
diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c
|
|
index cdb4a95af3e8..86cff5fce3cd 100644
|
|
--- a/drivers/platform/surface/surface_aggregator_registry.c
|
|
+++ b/drivers/platform/surface/surface_aggregator_registry.c
|
|
@@ -352,7 +352,7 @@ static struct attribute *ssam_base_hub_attrs[] = {
|
|
NULL,
|
|
};
|
|
|
|
-const struct attribute_group ssam_base_hub_group = {
|
|
+static const struct attribute_group ssam_base_hub_group = {
|
|
.attrs = ssam_base_hub_attrs,
|
|
};
|
|
|
|
--
|
|
2.32.0
|
|
|
|
From 7751eb2a68de5841822b775f1fb8dd49163feb24 Mon Sep 17 00:00:00 2001
|
|
From: Maximilian Luz <luzmaximilian@gmail.com>
|
|
Date: Tue, 9 Mar 2021 17:25:50 +0100
|
|
Subject: [PATCH] platform/surface: aggregator_registry: Add support for
|
|
Surface Pro 7+
|
|
|
|
The Surface Pro 7+ is essentially a refresh of the Surface Pro 7 with
|
|
updated hardware and a new WSID identifier.
|
|
|
|
Signed-off-by: Maximilian Luz <luzmaximilian@gmail.com>
|
|
Link: https://lore.kernel.org/r/20210309162550.302161-1-luzmaximilian@gmail.com
|
|
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
|
|
Patchset: surface-sam
|
|
---
|
|
drivers/platform/surface/surface_aggregator_registry.c | 5 ++++-
|
|
1 file changed, 4 insertions(+), 1 deletion(-)
|
|
|
|
diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c
|
|
index 86cff5fce3cd..eccb9d1007cd 100644
|
|
--- a/drivers/platform/surface/surface_aggregator_registry.c
|
|
+++ b/drivers/platform/surface/surface_aggregator_registry.c
|
|
@@ -191,7 +191,7 @@ static const struct software_node *ssam_node_group_sp6[] = {
|
|
NULL,
|
|
};
|
|
|
|
-/* Devices for Surface Pro 7. */
|
|
+/* Devices for Surface Pro 7 and Surface Pro 7+. */
|
|
static const struct software_node *ssam_node_group_sp7[] = {
|
|
&ssam_node_root,
|
|
&ssam_node_bat_ac,
|
|
@@ -521,6 +521,9 @@ static const struct acpi_device_id ssam_platform_hub_match[] = {
|
|
/* Surface Pro 7 */
|
|
{ "MSHW0116", (unsigned long)ssam_node_group_sp7 },
|
|
|
|
+ /* Surface Pro 7+ */
|
|
+ { "MSHW0119", (unsigned long)ssam_node_group_sp7 },
|
|
+
|
|
/* Surface Book 2 */
|
|
{ "MSHW0107", (unsigned long)ssam_node_group_sb2 },
|
|
|
|
--
|
|
2.32.0
|
|
|
|
From 31da7032ce116c1d3678049476fb2a6bd0570d26 Mon Sep 17 00:00:00 2001
|
|
From: kernel test robot <lkp@intel.com>
|
|
Date: Fri, 19 Mar 2021 13:19:19 +0800
|
|
Subject: [PATCH] platform/surface: fix semicolon.cocci warnings
|
|
|
|
drivers/platform/surface/surface_dtx.c:651:2-3: Unneeded semicolon
|
|
|
|
Remove unneeded semicolon.
|
|
|
|
Generated by: scripts/coccinelle/misc/semicolon.cocci
|
|
|
|
Fixes: 1d609992832e ("platform/surface: Add DTX driver")
|
|
CC: Maximilian Luz <luzmaximilian@gmail.com>
|
|
Reported-by: kernel test robot <lkp@intel.com>
|
|
Signed-off-by: kernel test robot <lkp@intel.com>
|
|
Reviewed-by: Maximilian Luz <luzmaximilian@gmail.com>
|
|
Link: https://lore.kernel.org/r/20210319051919.GA39801@ae4f36e4f012
|
|
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
|
|
Patchset: surface-sam
|
|
---
|
|
drivers/platform/surface/surface_dtx.c | 2 +-
|
|
1 file changed, 1 insertion(+), 1 deletion(-)
|
|
|
|
diff --git a/drivers/platform/surface/surface_dtx.c b/drivers/platform/surface/surface_dtx.c
|
|
index 85451eb94d98..1fedacf74050 100644
|
|
--- a/drivers/platform/surface/surface_dtx.c
|
|
+++ b/drivers/platform/surface/surface_dtx.c
|
|
@@ -649,7 +649,7 @@ static u32 sdtx_notifier(struct ssam_event_notifier *nf, const struct ssam_event
|
|
|
|
default:
|
|
return 0;
|
|
- };
|
|
+ }
|
|
|
|
if (in->length != len) {
|
|
dev_err(ddev->dev,
|
|
--
|
|
2.32.0
|
|
|
|
From 2f1c22a481481c8623e98d8cc8b78d287755f3e3 Mon Sep 17 00:00:00 2001
|
|
From: Dan Carpenter <dan.carpenter@oracle.com>
|
|
Date: Fri, 26 Mar 2021 15:28:48 +0300
|
|
Subject: [PATCH] platform/surface: clean up a variable in surface_dtx_read()
|
|
|
|
The "&client->ddev->lock" and "&ddev->lock" are the same thing. Let's
|
|
use "&ddev->lock" consistently.
|
|
|
|
Signed-off-by: Dan Carpenter <dan.carpenter@oracle.com>
|
|
Reviewed-by: Maximilian Luz <luzmaximilian@gmail.com>
|
|
Link: https://lore.kernel.org/r/YF3TgCcpcCYl3a//@mwanda
|
|
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
|
|
Patchset: surface-sam
|
|
---
|
|
drivers/platform/surface/surface_dtx.c | 2 +-
|
|
1 file changed, 1 insertion(+), 1 deletion(-)
|
|
|
|
diff --git a/drivers/platform/surface/surface_dtx.c b/drivers/platform/surface/surface_dtx.c
|
|
index 1fedacf74050..63ce587e79e3 100644
|
|
--- a/drivers/platform/surface/surface_dtx.c
|
|
+++ b/drivers/platform/surface/surface_dtx.c
|
|
@@ -487,7 +487,7 @@ static ssize_t surface_dtx_read(struct file *file, char __user *buf, size_t coun
|
|
if (status < 0)
|
|
return status;
|
|
|
|
- if (down_read_killable(&client->ddev->lock))
|
|
+ if (down_read_killable(&ddev->lock))
|
|
return -ERESTARTSYS;
|
|
|
|
/* Need to check that we're not shut down again. */
|
|
--
|
|
2.32.0
|
|
|
|
From c0a095e902338a17e4b090ac78837aedd32c5abf Mon Sep 17 00:00:00 2001
|
|
From: Maximilian Luz <luzmaximilian@gmail.com>
|
|
Date: Tue, 6 Apr 2021 01:12:22 +0200
|
|
Subject: [PATCH] platform/surface: aggregator_registry: Give devices time to
|
|
set up when connecting
|
|
|
|
Sometimes, the "base connected" event that we rely on to (re-)attach the
|
|
device connected to the base is sent a bit too early. When this happens,
|
|
some devices may not be completely ready yet.
|
|
|
|
Specifically, the battery has been observed to report zero-values for
|
|
things like full charge capacity, which, however, is only loaded once
|
|
when the driver for that device probes. This can thus result in battery
|
|
readings being unavailable.
|
|
|
|
As we cannot easily and reliably discern between devices that are not
|
|
ready yet and devices that are not connected (i.e. will never be ready),
|
|
delay adding these devices. This should give them enough time to set up.
|
|
|
|
The delay is set to 2.5 seconds, which should give us a good safety
|
|
margin based on testing and still be fairly responsive for users.
|
|
|
|
To achieve that delay switch to updating via a delayed work struct,
|
|
which means that we can also get rid of some locking.
|
|
|
|
Signed-off-by: Maximilian Luz <luzmaximilian@gmail.com>
|
|
Link: https://lore.kernel.org/r/20210405231222.358113-1-luzmaximilian@gmail.com
|
|
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
|
|
Patchset: surface-sam
|
|
---
|
|
.../surface/surface_aggregator_registry.c | 98 ++++++++-----------
|
|
1 file changed, 40 insertions(+), 58 deletions(-)
|
|
|
|
diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c
|
|
index eccb9d1007cd..685d37a7add1 100644
|
|
--- a/drivers/platform/surface/surface_aggregator_registry.c
|
|
+++ b/drivers/platform/surface/surface_aggregator_registry.c
|
|
@@ -13,10 +13,10 @@
|
|
#include <linux/kernel.h>
|
|
#include <linux/limits.h>
|
|
#include <linux/module.h>
|
|
-#include <linux/mutex.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/property.h>
|
|
#include <linux/types.h>
|
|
+#include <linux/workqueue.h>
|
|
|
|
#include <linux/surface_aggregator/controller.h>
|
|
#include <linux/surface_aggregator/device.h>
|
|
@@ -287,6 +287,13 @@ static int ssam_hub_add_devices(struct device *parent, struct ssam_controller *c
|
|
|
|
/* -- SSAM base-hub driver. ------------------------------------------------- */
|
|
|
|
+/*
|
|
+ * Some devices (especially battery) may need a bit of time to be fully usable
|
|
+ * after being (re-)connected. This delay has been determined via
|
|
+ * experimentation.
|
|
+ */
|
|
+#define SSAM_BASE_UPDATE_CONNECT_DELAY msecs_to_jiffies(2500)
|
|
+
|
|
enum ssam_base_hub_state {
|
|
SSAM_BASE_HUB_UNINITIALIZED,
|
|
SSAM_BASE_HUB_CONNECTED,
|
|
@@ -296,8 +303,8 @@ enum ssam_base_hub_state {
|
|
struct ssam_base_hub {
|
|
struct ssam_device *sdev;
|
|
|
|
- struct mutex lock; /* Guards state update checks and transitions. */
|
|
enum ssam_base_hub_state state;
|
|
+ struct delayed_work update_work;
|
|
|
|
struct ssam_event_notifier notif;
|
|
};
|
|
@@ -335,11 +342,7 @@ static ssize_t ssam_base_hub_state_show(struct device *dev, struct device_attrib
|
|
char *buf)
|
|
{
|
|
struct ssam_base_hub *hub = dev_get_drvdata(dev);
|
|
- bool connected;
|
|
-
|
|
- mutex_lock(&hub->lock);
|
|
- connected = hub->state == SSAM_BASE_HUB_CONNECTED;
|
|
- mutex_unlock(&hub->lock);
|
|
+ bool connected = hub->state == SSAM_BASE_HUB_CONNECTED;
|
|
|
|
return sysfs_emit(buf, "%d\n", connected);
|
|
}
|
|
@@ -356,16 +359,20 @@ static const struct attribute_group ssam_base_hub_group = {
|
|
.attrs = ssam_base_hub_attrs,
|
|
};
|
|
|
|
-static int __ssam_base_hub_update(struct ssam_base_hub *hub, enum ssam_base_hub_state new)
|
|
+static void ssam_base_hub_update_workfn(struct work_struct *work)
|
|
{
|
|
+ struct ssam_base_hub *hub = container_of(work, struct ssam_base_hub, update_work.work);
|
|
struct fwnode_handle *node = dev_fwnode(&hub->sdev->dev);
|
|
+ enum ssam_base_hub_state state;
|
|
int status = 0;
|
|
|
|
- lockdep_assert_held(&hub->lock);
|
|
+ status = ssam_base_hub_query_state(hub, &state);
|
|
+ if (status)
|
|
+ return;
|
|
|
|
- if (hub->state == new)
|
|
- return 0;
|
|
- hub->state = new;
|
|
+ if (hub->state == state)
|
|
+ return;
|
|
+ hub->state = state;
|
|
|
|
if (hub->state == SSAM_BASE_HUB_CONNECTED)
|
|
status = ssam_hub_add_devices(&hub->sdev->dev, hub->sdev->ctrl, node);
|
|
@@ -374,51 +381,28 @@ static int __ssam_base_hub_update(struct ssam_base_hub *hub, enum ssam_base_hub_
|
|
|
|
if (status)
|
|
dev_err(&hub->sdev->dev, "failed to update base-hub devices: %d\n", status);
|
|
-
|
|
- return status;
|
|
-}
|
|
-
|
|
-static int ssam_base_hub_update(struct ssam_base_hub *hub)
|
|
-{
|
|
- enum ssam_base_hub_state state;
|
|
- int status;
|
|
-
|
|
- mutex_lock(&hub->lock);
|
|
-
|
|
- status = ssam_base_hub_query_state(hub, &state);
|
|
- if (!status)
|
|
- status = __ssam_base_hub_update(hub, state);
|
|
-
|
|
- mutex_unlock(&hub->lock);
|
|
- return status;
|
|
}
|
|
|
|
static u32 ssam_base_hub_notif(struct ssam_event_notifier *nf, const struct ssam_event *event)
|
|
{
|
|
- struct ssam_base_hub *hub;
|
|
- struct ssam_device *sdev;
|
|
- enum ssam_base_hub_state new;
|
|
-
|
|
- hub = container_of(nf, struct ssam_base_hub, notif);
|
|
- sdev = hub->sdev;
|
|
+ struct ssam_base_hub *hub = container_of(nf, struct ssam_base_hub, notif);
|
|
+ unsigned long delay;
|
|
|
|
if (event->command_id != SSAM_EVENT_BAS_CID_CONNECTION)
|
|
return 0;
|
|
|
|
if (event->length < 1) {
|
|
- dev_err(&sdev->dev, "unexpected payload size: %u\n",
|
|
- event->length);
|
|
+ dev_err(&hub->sdev->dev, "unexpected payload size: %u\n", event->length);
|
|
return 0;
|
|
}
|
|
|
|
- if (event->data[0])
|
|
- new = SSAM_BASE_HUB_CONNECTED;
|
|
- else
|
|
- new = SSAM_BASE_HUB_DISCONNECTED;
|
|
+ /*
|
|
+ * Delay update when the base is being connected to give devices/EC
|
|
+ * some time to set up.
|
|
+ */
|
|
+ delay = event->data[0] ? SSAM_BASE_UPDATE_CONNECT_DELAY : 0;
|
|
|
|
- mutex_lock(&hub->lock);
|
|
- __ssam_base_hub_update(hub, new);
|
|
- mutex_unlock(&hub->lock);
|
|
+ schedule_delayed_work(&hub->update_work, delay);
|
|
|
|
/*
|
|
* Do not return SSAM_NOTIF_HANDLED: The event should be picked up and
|
|
@@ -430,7 +414,10 @@ static u32 ssam_base_hub_notif(struct ssam_event_notifier *nf, const struct ssam
|
|
|
|
static int __maybe_unused ssam_base_hub_resume(struct device *dev)
|
|
{
|
|
- return ssam_base_hub_update(dev_get_drvdata(dev));
|
|
+ struct ssam_base_hub *hub = dev_get_drvdata(dev);
|
|
+
|
|
+ schedule_delayed_work(&hub->update_work, 0);
|
|
+ return 0;
|
|
}
|
|
static SIMPLE_DEV_PM_OPS(ssam_base_hub_pm_ops, NULL, ssam_base_hub_resume);
|
|
|
|
@@ -443,8 +430,6 @@ static int ssam_base_hub_probe(struct ssam_device *sdev)
|
|
if (!hub)
|
|
return -ENOMEM;
|
|
|
|
- mutex_init(&hub->lock);
|
|
-
|
|
hub->sdev = sdev;
|
|
hub->state = SSAM_BASE_HUB_UNINITIALIZED;
|
|
|
|
@@ -456,27 +441,25 @@ static int ssam_base_hub_probe(struct ssam_device *sdev)
|
|
hub->notif.event.mask = SSAM_EVENT_MASK_NONE;
|
|
hub->notif.event.flags = SSAM_EVENT_SEQUENCED;
|
|
|
|
+ INIT_DELAYED_WORK(&hub->update_work, ssam_base_hub_update_workfn);
|
|
+
|
|
ssam_device_set_drvdata(sdev, hub);
|
|
|
|
status = ssam_notifier_register(sdev->ctrl, &hub->notif);
|
|
if (status)
|
|
- goto err_register;
|
|
-
|
|
- status = ssam_base_hub_update(hub);
|
|
- if (status)
|
|
- goto err_update;
|
|
+ return status;
|
|
|
|
status = sysfs_create_group(&sdev->dev.kobj, &ssam_base_hub_group);
|
|
if (status)
|
|
- goto err_update;
|
|
+ goto err;
|
|
|
|
+ schedule_delayed_work(&hub->update_work, 0);
|
|
return 0;
|
|
|
|
-err_update:
|
|
+err:
|
|
ssam_notifier_unregister(sdev->ctrl, &hub->notif);
|
|
+ cancel_delayed_work_sync(&hub->update_work);
|
|
ssam_hub_remove_devices(&sdev->dev);
|
|
-err_register:
|
|
- mutex_destroy(&hub->lock);
|
|
return status;
|
|
}
|
|
|
|
@@ -487,9 +470,8 @@ static void ssam_base_hub_remove(struct ssam_device *sdev)
|
|
sysfs_remove_group(&sdev->dev.kobj, &ssam_base_hub_group);
|
|
|
|
ssam_notifier_unregister(sdev->ctrl, &hub->notif);
|
|
+ cancel_delayed_work_sync(&hub->update_work);
|
|
ssam_hub_remove_devices(&sdev->dev);
|
|
-
|
|
- mutex_destroy(&hub->lock);
|
|
}
|
|
|
|
static const struct ssam_device_id ssam_base_hub_match[] = {
|
|
--
|
|
2.32.0
|
|
|
|
From 09e3d34a400dfbcf06674de5bfe68e16bb9e53d4 Mon Sep 17 00:00:00 2001
|
|
From: Barry Song <song.bao.hua@hisilicon.com>
|
|
Date: Wed, 3 Mar 2021 11:49:15 +1300
|
|
Subject: [PATCH] genirq: Add IRQF_NO_AUTOEN for request_irq/nmi()
|
|
|
|
Many drivers don't want interrupts enabled automatically via request_irq().
|
|
So they are handling this issue by either way of the below two:
|
|
|
|
(1)
|
|
irq_set_status_flags(irq, IRQ_NOAUTOEN);
|
|
request_irq(dev, irq...);
|
|
|
|
(2)
|
|
request_irq(dev, irq...);
|
|
disable_irq(irq);
|
|
|
|
The code in the second way is silly and unsafe. In the small time gap
|
|
between request_irq() and disable_irq(), interrupts can still come.
|
|
|
|
The code in the first way is safe though it's subobtimal.
|
|
|
|
Add a new IRQF_NO_AUTOEN flag which can be handed in by drivers to
|
|
request_irq() and request_nmi(). It prevents the automatic enabling of the
|
|
requested interrupt/nmi in the same safe way as #1 above. With that the
|
|
various usage sites of #1 and #2 above can be simplified and corrected.
|
|
|
|
Signed-off-by: Barry Song <song.bao.hua@hisilicon.com>
|
|
Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
|
|
Signed-off-by: Ingo Molnar <mingo@kernel.org>
|
|
Cc: dmitry.torokhov@gmail.com
|
|
Link: https://lore.kernel.org/r/20210302224916.13980-2-song.bao.hua@hisilicon.com
|
|
Patchset: surface-sam
|
|
---
|
|
include/linux/interrupt.h | 4 ++++
|
|
kernel/irq/manage.c | 11 +++++++++--
|
|
2 files changed, 13 insertions(+), 2 deletions(-)
|
|
|
|
diff --git a/include/linux/interrupt.h b/include/linux/interrupt.h
|
|
index 967e25767153..76f1161a441a 100644
|
|
--- a/include/linux/interrupt.h
|
|
+++ b/include/linux/interrupt.h
|
|
@@ -61,6 +61,9 @@
|
|
* interrupt handler after suspending interrupts. For system
|
|
* wakeup devices users need to implement wakeup detection in
|
|
* their interrupt handlers.
|
|
+ * IRQF_NO_AUTOEN - Don't enable IRQ or NMI automatically when users request it.
|
|
+ * Users will enable it explicitly by enable_irq() or enable_nmi()
|
|
+ * later.
|
|
*/
|
|
#define IRQF_SHARED 0x00000080
|
|
#define IRQF_PROBE_SHARED 0x00000100
|
|
@@ -74,6 +77,7 @@
|
|
#define IRQF_NO_THREAD 0x00010000
|
|
#define IRQF_EARLY_RESUME 0x00020000
|
|
#define IRQF_COND_SUSPEND 0x00040000
|
|
+#define IRQF_NO_AUTOEN 0x00080000
|
|
|
|
#define IRQF_TIMER (__IRQF_TIMER | IRQF_NO_SUSPEND | IRQF_NO_THREAD)
|
|
|
|
diff --git a/kernel/irq/manage.c b/kernel/irq/manage.c
|
|
index 21ea370fccda..49288e941365 100644
|
|
--- a/kernel/irq/manage.c
|
|
+++ b/kernel/irq/manage.c
|
|
@@ -1697,7 +1697,8 @@ __setup_irq(unsigned int irq, struct irq_desc *desc, struct irqaction *new)
|
|
irqd_set(&desc->irq_data, IRQD_NO_BALANCING);
|
|
}
|
|
|
|
- if (irq_settings_can_autoenable(desc)) {
|
|
+ if (!(new->flags & IRQF_NO_AUTOEN) &&
|
|
+ irq_settings_can_autoenable(desc)) {
|
|
irq_startup(desc, IRQ_RESEND, IRQ_START_COND);
|
|
} else {
|
|
/*
|
|
@@ -2090,10 +2091,15 @@ int request_threaded_irq(unsigned int irq, irq_handler_t handler,
|
|
* which interrupt is which (messes up the interrupt freeing
|
|
* logic etc).
|
|
*
|
|
+ * Also shared interrupts do not go well with disabling auto enable.
|
|
+ * The sharing interrupt might request it while it's still disabled
|
|
+ * and then wait for interrupts forever.
|
|
+ *
|
|
* Also IRQF_COND_SUSPEND only makes sense for shared interrupts and
|
|
* it cannot be set along with IRQF_NO_SUSPEND.
|
|
*/
|
|
if (((irqflags & IRQF_SHARED) && !dev_id) ||
|
|
+ ((irqflags & IRQF_SHARED) && (irqflags & IRQF_NO_AUTOEN)) ||
|
|
(!(irqflags & IRQF_SHARED) && (irqflags & IRQF_COND_SUSPEND)) ||
|
|
((irqflags & IRQF_NO_SUSPEND) && (irqflags & IRQF_COND_SUSPEND)))
|
|
return -EINVAL;
|
|
@@ -2249,7 +2255,8 @@ int request_nmi(unsigned int irq, irq_handler_t handler,
|
|
|
|
desc = irq_to_desc(irq);
|
|
|
|
- if (!desc || irq_settings_can_autoenable(desc) ||
|
|
+ if (!desc || (irq_settings_can_autoenable(desc) &&
|
|
+ !(irqflags & IRQF_NO_AUTOEN)) ||
|
|
!irq_settings_can_request(desc) ||
|
|
WARN_ON(irq_settings_is_per_cpu_devid(desc)) ||
|
|
!irq_supports_nmi(desc))
|
|
--
|
|
2.32.0
|
|
|
|
From 71e1f5ac8a599f20e9389bdcc05251d1e14a2b9a Mon Sep 17 00:00:00 2001
|
|
From: Tian Tao <tiantao6@hisilicon.com>
|
|
Date: Wed, 7 Apr 2021 15:00:52 +0800
|
|
Subject: [PATCH] platform/surface: aggregator: move to use request_irq by
|
|
IRQF_NO_AUTOEN flag
|
|
|
|
disable_irq() after request_irq() still has a time gap in which
|
|
interrupts can come. request_irq() with IRQF_NO_AUTOEN flag will
|
|
disable IRQ auto-enable because of requesting.
|
|
|
|
this patch is made base on "add IRQF_NO_AUTOEN for request_irq" which
|
|
is being merged: https://lore.kernel.org/patchwork/patch/1388765/
|
|
|
|
Signed-off-by: Tian Tao <tiantao6@hisilicon.com>
|
|
Reviewed-by: Maximilian Luz <luzmaximilian@gmail.com>
|
|
Link: https://lore.kernel.org/r/1617778852-26492-1-git-send-email-tiantao6@hisilicon.com
|
|
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
|
|
Patchset: surface-sam
|
|
---
|
|
drivers/platform/surface/aggregator/controller.c | 4 ++--
|
|
1 file changed, 2 insertions(+), 2 deletions(-)
|
|
|
|
diff --git a/drivers/platform/surface/aggregator/controller.c b/drivers/platform/surface/aggregator/controller.c
|
|
index d006a36b2924..eace5e9374fe 100644
|
|
--- a/drivers/platform/surface/aggregator/controller.c
|
|
+++ b/drivers/platform/surface/aggregator/controller.c
|
|
@@ -2483,7 +2483,8 @@ int ssam_irq_setup(struct ssam_controller *ctrl)
|
|
* interrupt, and let the SAM resume callback during the controller
|
|
* resume process clear it.
|
|
*/
|
|
- const int irqf = IRQF_SHARED | IRQF_ONESHOT | IRQF_TRIGGER_RISING;
|
|
+ const int irqf = IRQF_SHARED | IRQF_ONESHOT |
|
|
+ IRQF_TRIGGER_RISING | IRQF_NO_AUTOEN;
|
|
|
|
gpiod = gpiod_get(dev, "ssam_wakeup-int", GPIOD_ASIS);
|
|
if (IS_ERR(gpiod))
|
|
@@ -2501,7 +2502,6 @@ int ssam_irq_setup(struct ssam_controller *ctrl)
|
|
return status;
|
|
|
|
ctrl->irq.num = irq;
|
|
- disable_irq(ctrl->irq.num);
|
|
return 0;
|
|
}
|
|
|
|
--
|
|
2.32.0
|
|
|
|
From d9deaec079e9f7b1e732ef6edec03989d99a2691 Mon Sep 17 00:00:00 2001
|
|
From: Maximilian Luz <luzmaximilian@gmail.com>
|
|
Date: Wed, 5 May 2021 14:53:45 +0200
|
|
Subject: [PATCH] platform/surface: aggregator: Do not mark interrupt as shared
|
|
|
|
Having both IRQF_NO_AUTOEN and IRQF_SHARED set causes
|
|
request_threaded_irq() to return with -EINVAL (see comment in flag
|
|
validation in that function). As the interrupt is currently not shared
|
|
between multiple devices, drop the IRQF_SHARED flag.
|
|
|
|
Fixes: 507cf5a2f1e2 ("platform/surface: aggregator: move to use request_irq by IRQF_NO_AUTOEN flag")
|
|
Signed-off-by: Maximilian Luz <luzmaximilian@gmail.com>
|
|
Patchset: surface-sam
|
|
---
|
|
drivers/platform/surface/aggregator/controller.c | 3 +--
|
|
1 file changed, 1 insertion(+), 2 deletions(-)
|
|
|
|
diff --git a/drivers/platform/surface/aggregator/controller.c b/drivers/platform/surface/aggregator/controller.c
|
|
index eace5e9374fe..a06964aa96e7 100644
|
|
--- a/drivers/platform/surface/aggregator/controller.c
|
|
+++ b/drivers/platform/surface/aggregator/controller.c
|
|
@@ -2483,8 +2483,7 @@ int ssam_irq_setup(struct ssam_controller *ctrl)
|
|
* interrupt, and let the SAM resume callback during the controller
|
|
* resume process clear it.
|
|
*/
|
|
- const int irqf = IRQF_SHARED | IRQF_ONESHOT |
|
|
- IRQF_TRIGGER_RISING | IRQF_NO_AUTOEN;
|
|
+ const int irqf = IRQF_ONESHOT | IRQF_TRIGGER_RISING | IRQF_NO_AUTOEN;
|
|
|
|
gpiod = gpiod_get(dev, "ssam_wakeup-int", GPIOD_ASIS);
|
|
if (IS_ERR(gpiod))
|
|
--
|
|
2.32.0
|
|
|
|
From 6083e10f0f23437f53a013052f42293f333d0018 Mon Sep 17 00:00:00 2001
|
|
From: Arnd Bergmann <arnd@arndb.de>
|
|
Date: Fri, 14 May 2021 22:04:36 +0200
|
|
Subject: [PATCH] platform/surface: aggregator: avoid clang
|
|
-Wconstant-conversion warning
|
|
|
|
Clang complains about the assignment of SSAM_ANY_IID to
|
|
ssam_device_uid->instance:
|
|
|
|
drivers/platform/surface/surface_aggregator_registry.c:478:25: error: implicit conversion from 'int' to '__u8' (aka 'unsigned char') changes value from 65535 to 255 [-Werror,-Wconstant-conversion]
|
|
{ SSAM_VDEV(HUB, 0x02, SSAM_ANY_IID, 0x00) },
|
|
~ ^~~~~~~~~~~~
|
|
include/linux/surface_aggregator/device.h:71:23: note: expanded from macro 'SSAM_ANY_IID'
|
|
#define SSAM_ANY_IID 0xffff
|
|
^~~~~~
|
|
include/linux/surface_aggregator/device.h:126:63: note: expanded from macro 'SSAM_VDEV'
|
|
SSAM_DEVICE(SSAM_DOMAIN_VIRTUAL, SSAM_VIRTUAL_TC_##cat, tid, iid, fun)
|
|
^~~
|
|
include/linux/surface_aggregator/device.h:102:41: note: expanded from macro 'SSAM_DEVICE'
|
|
.instance = ((iid) != SSAM_ANY_IID) ? (iid) : 0, \
|
|
^~~
|
|
|
|
The assignment doesn't actually happen, but clang checks the type limits
|
|
before checking whether this assignment is reached. Replace the ?:
|
|
operator with a __builtin_choose_expr() invocation that avoids the
|
|
warning for the untaken part.
|
|
|
|
Fixes: eb0e90a82098 ("platform/surface: aggregator: Add dedicated bus and device type")
|
|
Cc: platform-driver-x86@vger.kernel.org
|
|
Signed-off-by: Arnd Bergmann <arnd@arndb.de>
|
|
Reviewed-by: Nathan Chancellor <nathan@kernel.org>
|
|
Reviewed-by: Maximilian Luz <luzmaximilian@gmail.com>
|
|
Link: https://lore.kernel.org/r/20210514200453.1542978-1-arnd@kernel.org
|
|
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
|
|
Patchset: surface-sam
|
|
---
|
|
include/linux/surface_aggregator/device.h | 6 +++---
|
|
1 file changed, 3 insertions(+), 3 deletions(-)
|
|
|
|
diff --git a/include/linux/surface_aggregator/device.h b/include/linux/surface_aggregator/device.h
|
|
index 4441ad667c3f..6ff9c58b3e17 100644
|
|
--- a/include/linux/surface_aggregator/device.h
|
|
+++ b/include/linux/surface_aggregator/device.h
|
|
@@ -98,9 +98,9 @@ struct ssam_device_uid {
|
|
| (((fun) != SSAM_ANY_FUN) ? SSAM_MATCH_FUNCTION : 0), \
|
|
.domain = d, \
|
|
.category = cat, \
|
|
- .target = ((tid) != SSAM_ANY_TID) ? (tid) : 0, \
|
|
- .instance = ((iid) != SSAM_ANY_IID) ? (iid) : 0, \
|
|
- .function = ((fun) != SSAM_ANY_FUN) ? (fun) : 0 \
|
|
+ .target = __builtin_choose_expr((tid) != SSAM_ANY_TID, (tid), 0), \
|
|
+ .instance = __builtin_choose_expr((iid) != SSAM_ANY_IID, (iid), 0), \
|
|
+ .function = __builtin_choose_expr((fun) != SSAM_ANY_FUN, (fun), 0)
|
|
|
|
/**
|
|
* SSAM_VDEV() - Initialize a &struct ssam_device_id as virtual device with
|
|
--
|
|
2.32.0
|
|
|
|
From 9c33d72e05577956908445f2093720ab105b781d Mon Sep 17 00:00:00 2001
|
|
From: Maximilian Luz <luzmaximilian@gmail.com>
|
|
Date: Thu, 13 May 2021 15:44:37 +0200
|
|
Subject: [PATCH] platform/surface: dtx: Fix poll function
|
|
|
|
The poll function should not return -ERESTARTSYS.
|
|
|
|
Furthermore, locking in this function is completely unnecessary. The
|
|
ddev->lock protects access to the main device and controller (ddev->dev
|
|
and ddev->ctrl), ensuring that both are and remain valid while being
|
|
accessed by clients. Both are, however, never accessed in the poll
|
|
function. The shutdown test (via atomic bit flags) be safely done
|
|
without locking, so drop locking here entirely.
|
|
|
|
Reported-by: kernel test robot <lkp@intel.com>
|
|
Fixes: 1d609992832e ("platform/surface: Add DTX driver)
|
|
Signed-off-by: Maximilian Luz <luzmaximilian@gmail.com>
|
|
Link: https://lore.kernel.org/r/20210513134437.2431022-1-luzmaximilian@gmail.com
|
|
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
|
|
Patchset: surface-sam
|
|
---
|
|
drivers/platform/surface/surface_dtx.c | 8 +-------
|
|
1 file changed, 1 insertion(+), 7 deletions(-)
|
|
|
|
diff --git a/drivers/platform/surface/surface_dtx.c b/drivers/platform/surface/surface_dtx.c
|
|
index 63ce587e79e3..5d9b758a99bb 100644
|
|
--- a/drivers/platform/surface/surface_dtx.c
|
|
+++ b/drivers/platform/surface/surface_dtx.c
|
|
@@ -527,20 +527,14 @@ static __poll_t surface_dtx_poll(struct file *file, struct poll_table_struct *pt
|
|
struct sdtx_client *client = file->private_data;
|
|
__poll_t events = 0;
|
|
|
|
- if (down_read_killable(&client->ddev->lock))
|
|
- return -ERESTARTSYS;
|
|
-
|
|
- if (test_bit(SDTX_DEVICE_SHUTDOWN_BIT, &client->ddev->flags)) {
|
|
- up_read(&client->ddev->lock);
|
|
+ if (test_bit(SDTX_DEVICE_SHUTDOWN_BIT, &client->ddev->flags))
|
|
return EPOLLHUP | EPOLLERR;
|
|
- }
|
|
|
|
poll_wait(file, &client->ddev->waitq, pt);
|
|
|
|
if (!kfifo_is_empty(&client->buffer))
|
|
events |= EPOLLIN | EPOLLRDNORM;
|
|
|
|
- up_read(&client->ddev->lock);
|
|
return events;
|
|
}
|
|
|
|
--
|
|
2.32.0
|
|
|
|
From 4311553db3d140a6a78e82bdc6514c731203d17e Mon Sep 17 00:00:00 2001
|
|
From: Maximilian Luz <luzmaximilian@gmail.com>
|
|
Date: Sun, 23 May 2021 14:35:37 +0200
|
|
Subject: [PATCH] platform/surface: aggregator_registry: Update comments for
|
|
15" AMD Surface Laptop 4
|
|
|
|
The 15" AMD version of the Surface Laptop 4 shares its WSID HID with the
|
|
15" AMD version of the Surface Laptop 3. Update the comments
|
|
accordingly.
|
|
|
|
Signed-off-by: Maximilian Luz <luzmaximilian@gmail.com>
|
|
Patchset: surface-sam
|
|
---
|
|
drivers/platform/surface/surface_aggregator_registry.c | 4 ++--
|
|
1 file changed, 2 insertions(+), 2 deletions(-)
|
|
|
|
diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c
|
|
index 685d37a7add1..bdc09305aab7 100644
|
|
--- a/drivers/platform/surface/surface_aggregator_registry.c
|
|
+++ b/drivers/platform/surface/surface_aggregator_registry.c
|
|
@@ -156,7 +156,7 @@ static const struct software_node *ssam_node_group_sl2[] = {
|
|
NULL,
|
|
};
|
|
|
|
-/* Devices for Surface Laptop 3. */
|
|
+/* Devices for Surface Laptop 3 and 4. */
|
|
static const struct software_node *ssam_node_group_sl3[] = {
|
|
&ssam_node_root,
|
|
&ssam_node_bat_ac,
|
|
@@ -521,7 +521,7 @@ static const struct acpi_device_id ssam_platform_hub_match[] = {
|
|
/* Surface Laptop 3 (13", Intel) */
|
|
{ "MSHW0114", (unsigned long)ssam_node_group_sl3 },
|
|
|
|
- /* Surface Laptop 3 (15", AMD) */
|
|
+ /* Surface Laptop 3 (15", AMD) and 4 (15", AMD) */
|
|
{ "MSHW0110", (unsigned long)ssam_node_group_sl3 },
|
|
|
|
/* Surface Laptop Go 1 */
|
|
--
|
|
2.32.0
|
|
|
|
From a46d9237d3bf025f577319510993da8f72244c33 Mon Sep 17 00:00:00 2001
|
|
From: Maximilian Luz <luzmaximilian@gmail.com>
|
|
Date: Sun, 23 May 2021 14:36:36 +0200
|
|
Subject: [PATCH] platform/surface: aggregator_registry: Add support for 13"
|
|
Intel Surface Laptop 4
|
|
|
|
Add support for the 13" Intel version of the Surface Laptop 4.
|
|
|
|
Use the existing node group for the Surface Laptop 3 since the 15" AMD
|
|
version already shares its WSID HID with its predecessor and there don't
|
|
seem to be any significant differences with regards to SAM.
|
|
|
|
Signed-off-by: Maximilian Luz <luzmaximilian@gmail.com>
|
|
Patchset: surface-sam
|
|
---
|
|
drivers/platform/surface/surface_aggregator_registry.c | 3 +++
|
|
1 file changed, 3 insertions(+)
|
|
|
|
diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c
|
|
index bdc09305aab7..ef83461fa536 100644
|
|
--- a/drivers/platform/surface/surface_aggregator_registry.c
|
|
+++ b/drivers/platform/surface/surface_aggregator_registry.c
|
|
@@ -524,6 +524,9 @@ static const struct acpi_device_id ssam_platform_hub_match[] = {
|
|
/* Surface Laptop 3 (15", AMD) and 4 (15", AMD) */
|
|
{ "MSHW0110", (unsigned long)ssam_node_group_sl3 },
|
|
|
|
+ /* Surface Laptop 4 (13", Intel) */
|
|
+ { "MSHW0250", (unsigned long)ssam_node_group_sl3 },
|
|
+
|
|
/* Surface Laptop Go 1 */
|
|
{ "MSHW0118", (unsigned long)ssam_node_group_slg1 },
|
|
|
|
--
|
|
2.32.0
|
|
|
|
From 45ebc764a23cc78d163036f28e6a59a4150b8971 Mon Sep 17 00:00:00 2001
|
|
From: Maximilian Luz <luzmaximilian@gmail.com>
|
|
Date: Sun, 23 May 2021 14:09:42 +0200
|
|
Subject: [PATCH] platform/surface: aggregator_registry: Consolidate node
|
|
groups for 5th- and 6th-gen devices
|
|
|
|
5th- and 6th-generation Surface devices have all SAM clients defined in
|
|
ACPI, except for the platform profile/performance mode which his handled
|
|
via the WSID (Windows Surface Integration Device). Thus, the node groups
|
|
for those devices are the same and we can just use a single one instead
|
|
of re-defining the same one over and over again.
|
|
|
|
Signed-off-by: Maximilian Luz <luzmaximilian@gmail.com>
|
|
Patchset: surface-sam
|
|
---
|
|
.../surface/surface_aggregator_registry.c | 47 +++++--------------
|
|
1 file changed, 12 insertions(+), 35 deletions(-)
|
|
|
|
diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c
|
|
index ef83461fa536..4428c4330229 100644
|
|
--- a/drivers/platform/surface/surface_aggregator_registry.c
|
|
+++ b/drivers/platform/surface/surface_aggregator_registry.c
|
|
@@ -119,8 +119,13 @@ static const struct software_node ssam_node_hid_base_iid6 = {
|
|
.parent = &ssam_node_hub_base,
|
|
};
|
|
|
|
-/* Devices for Surface Book 2. */
|
|
-static const struct software_node *ssam_node_group_sb2[] = {
|
|
+/*
|
|
+ * Devices for 5th- and 6th-generations models:
|
|
+ * - Surface Book 2,
|
|
+ * - Surface Laptop 1 and 2,
|
|
+ * - Surface Pro 5 and 6.
|
|
+ */
|
|
+static const struct software_node *ssam_node_group_gen5[] = {
|
|
&ssam_node_root,
|
|
&ssam_node_tmp_pprof,
|
|
NULL,
|
|
@@ -142,20 +147,6 @@ static const struct software_node *ssam_node_group_sb3[] = {
|
|
NULL,
|
|
};
|
|
|
|
-/* Devices for Surface Laptop 1. */
|
|
-static const struct software_node *ssam_node_group_sl1[] = {
|
|
- &ssam_node_root,
|
|
- &ssam_node_tmp_pprof,
|
|
- NULL,
|
|
-};
|
|
-
|
|
-/* Devices for Surface Laptop 2. */
|
|
-static const struct software_node *ssam_node_group_sl2[] = {
|
|
- &ssam_node_root,
|
|
- &ssam_node_tmp_pprof,
|
|
- NULL,
|
|
-};
|
|
-
|
|
/* Devices for Surface Laptop 3 and 4. */
|
|
static const struct software_node *ssam_node_group_sl3[] = {
|
|
&ssam_node_root,
|
|
@@ -177,20 +168,6 @@ static const struct software_node *ssam_node_group_slg1[] = {
|
|
NULL,
|
|
};
|
|
|
|
-/* Devices for Surface Pro 5. */
|
|
-static const struct software_node *ssam_node_group_sp5[] = {
|
|
- &ssam_node_root,
|
|
- &ssam_node_tmp_pprof,
|
|
- NULL,
|
|
-};
|
|
-
|
|
-/* Devices for Surface Pro 6. */
|
|
-static const struct software_node *ssam_node_group_sp6[] = {
|
|
- &ssam_node_root,
|
|
- &ssam_node_tmp_pprof,
|
|
- NULL,
|
|
-};
|
|
-
|
|
/* Devices for Surface Pro 7 and Surface Pro 7+. */
|
|
static const struct software_node *ssam_node_group_sp7[] = {
|
|
&ssam_node_root,
|
|
@@ -495,10 +472,10 @@ static struct ssam_device_driver ssam_base_hub_driver = {
|
|
|
|
static const struct acpi_device_id ssam_platform_hub_match[] = {
|
|
/* Surface Pro 4, 5, and 6 (OMBR < 0x10) */
|
|
- { "MSHW0081", (unsigned long)ssam_node_group_sp5 },
|
|
+ { "MSHW0081", (unsigned long)ssam_node_group_gen5 },
|
|
|
|
/* Surface Pro 6 (OMBR >= 0x10) */
|
|
- { "MSHW0111", (unsigned long)ssam_node_group_sp6 },
|
|
+ { "MSHW0111", (unsigned long)ssam_node_group_gen5 },
|
|
|
|
/* Surface Pro 7 */
|
|
{ "MSHW0116", (unsigned long)ssam_node_group_sp7 },
|
|
@@ -507,16 +484,16 @@ static const struct acpi_device_id ssam_platform_hub_match[] = {
|
|
{ "MSHW0119", (unsigned long)ssam_node_group_sp7 },
|
|
|
|
/* Surface Book 2 */
|
|
- { "MSHW0107", (unsigned long)ssam_node_group_sb2 },
|
|
+ { "MSHW0107", (unsigned long)ssam_node_group_gen5 },
|
|
|
|
/* Surface Book 3 */
|
|
{ "MSHW0117", (unsigned long)ssam_node_group_sb3 },
|
|
|
|
/* Surface Laptop 1 */
|
|
- { "MSHW0086", (unsigned long)ssam_node_group_sl1 },
|
|
+ { "MSHW0086", (unsigned long)ssam_node_group_gen5 },
|
|
|
|
/* Surface Laptop 2 */
|
|
- { "MSHW0112", (unsigned long)ssam_node_group_sl2 },
|
|
+ { "MSHW0112", (unsigned long)ssam_node_group_gen5 },
|
|
|
|
/* Surface Laptop 3 (13", Intel) */
|
|
{ "MSHW0114", (unsigned long)ssam_node_group_sl3 },
|
|
--
|
|
2.32.0
|
|
|
|
From 2b18fb4c666e9c129abe78fc276c417e5e90c744 Mon Sep 17 00:00:00 2001
|
|
From: Maximilian Luz <luzmaximilian@gmail.com>
|
|
Date: Wed, 10 Mar 2021 23:53:28 +0100
|
|
Subject: [PATCH] HID: Add support for Surface Aggregator Module HID transport
|
|
MIME-Version: 1.0
|
|
Content-Type: text/plain; charset=UTF-8
|
|
Content-Transfer-Encoding: 8bit
|
|
|
|
Add a HID transport driver to support integrated HID devices on newer
|
|
Microsoft Surface models (specifically 7th-generation, i.e. Surface
|
|
Laptop 3, Surface Book 3, and later).
|
|
|
|
On those models, the internal keyboard and touchpad (as well as some
|
|
other HID devices with currently unknown function) are connected via the
|
|
generic HID subsystem (TC=0x15) of the Surface System Aggregator Module
|
|
(SSAM). This subsystem provides a generic HID transport layer, support
|
|
for which is implemented by this driver.
|
|
|
|
Co-developed-by: Blaž Hrastnik <blaz@mxxn.io>
|
|
Signed-off-by: Blaž Hrastnik <blaz@mxxn.io>
|
|
Signed-off-by: Maximilian Luz <luzmaximilian@gmail.com>
|
|
Signed-off-by: Jiri Kosina <jkosina@suse.cz>
|
|
Patchset: surface-sam
|
|
---
|
|
MAINTAINERS | 7 +
|
|
drivers/hid/Kconfig | 2 +
|
|
drivers/hid/Makefile | 2 +
|
|
drivers/hid/surface-hid/Kconfig | 28 +++
|
|
drivers/hid/surface-hid/Makefile | 6 +
|
|
drivers/hid/surface-hid/surface_hid.c | 253 +++++++++++++++++++
|
|
drivers/hid/surface-hid/surface_hid_core.c | 272 +++++++++++++++++++++
|
|
drivers/hid/surface-hid/surface_hid_core.h | 77 ++++++
|
|
8 files changed, 647 insertions(+)
|
|
create mode 100644 drivers/hid/surface-hid/Kconfig
|
|
create mode 100644 drivers/hid/surface-hid/Makefile
|
|
create mode 100644 drivers/hid/surface-hid/surface_hid.c
|
|
create mode 100644 drivers/hid/surface-hid/surface_hid_core.c
|
|
create mode 100644 drivers/hid/surface-hid/surface_hid_core.h
|
|
|
|
diff --git a/MAINTAINERS b/MAINTAINERS
|
|
index da1487d672a8..f54b22333ec6 100644
|
|
--- a/MAINTAINERS
|
|
+++ b/MAINTAINERS
|
|
@@ -11891,6 +11891,13 @@ S: Maintained
|
|
T: git git://git.kernel.org/pub/scm/linux/kernel/git/pdx86/platform-drivers-x86.git
|
|
F: drivers/platform/surface/
|
|
|
|
+MICROSOFT SURFACE HID TRANSPORT DRIVER
|
|
+M: Maximilian Luz <luzmaximilian@gmail.com>
|
|
+L: linux-input@vger.kernel.org
|
|
+L: platform-driver-x86@vger.kernel.org
|
|
+S: Maintained
|
|
+F: drivers/hid/surface-hid/
|
|
+
|
|
MICROSOFT SURFACE HOT-PLUG DRIVER
|
|
M: Maximilian Luz <luzmaximilian@gmail.com>
|
|
L: platform-driver-x86@vger.kernel.org
|
|
diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
|
|
index c6a643f4fc5f..a0913a67f614 100644
|
|
--- a/drivers/hid/Kconfig
|
|
+++ b/drivers/hid/Kconfig
|
|
@@ -1206,4 +1206,6 @@ source "drivers/hid/intel-ish-hid/Kconfig"
|
|
|
|
source "drivers/hid/amd-sfh-hid/Kconfig"
|
|
|
|
+source "drivers/hid/surface-hid/Kconfig"
|
|
+
|
|
endmenu
|
|
diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile
|
|
index c4f6d5c613dc..1044ed238856 100644
|
|
--- a/drivers/hid/Makefile
|
|
+++ b/drivers/hid/Makefile
|
|
@@ -145,3 +145,5 @@ obj-$(CONFIG_INTEL_ISH_HID) += intel-ish-hid/
|
|
obj-$(INTEL_ISH_FIRMWARE_DOWNLOADER) += intel-ish-hid/
|
|
|
|
obj-$(CONFIG_AMD_SFH_HID) += amd-sfh-hid/
|
|
+
|
|
+obj-$(CONFIG_SURFACE_HID_CORE) += surface-hid/
|
|
diff --git a/drivers/hid/surface-hid/Kconfig b/drivers/hid/surface-hid/Kconfig
|
|
new file mode 100644
|
|
index 000000000000..642c7f0e64fe
|
|
--- /dev/null
|
|
+++ b/drivers/hid/surface-hid/Kconfig
|
|
@@ -0,0 +1,28 @@
|
|
+# SPDX-License-Identifier: GPL-2.0+
|
|
+menu "Surface System Aggregator Module HID support"
|
|
+ depends on SURFACE_AGGREGATOR
|
|
+ depends on INPUT
|
|
+
|
|
+config SURFACE_HID
|
|
+ tristate "HID transport driver for Surface System Aggregator Module"
|
|
+ depends on SURFACE_AGGREGATOR_REGISTRY
|
|
+ select SURFACE_HID_CORE
|
|
+ help
|
|
+ Driver to support integrated HID devices on newer Microsoft Surface
|
|
+ models.
|
|
+
|
|
+ This driver provides support for the HID transport protocol provided
|
|
+ by the Surface Aggregator Module (i.e. the embedded controller) on
|
|
+ 7th-generation Microsoft Surface devices, i.e. Surface Book 3 and
|
|
+ Surface Laptop 3. On those models, it is mainly used to connect the
|
|
+ integrated touchpad and keyboard.
|
|
+
|
|
+ Say M or Y here, if you want support for integrated HID devices, i.e.
|
|
+ integrated touchpad and keyboard, on 7th generation Microsoft Surface
|
|
+ models.
|
|
+
|
|
+endmenu
|
|
+
|
|
+config SURFACE_HID_CORE
|
|
+ tristate
|
|
+ select HID
|
|
diff --git a/drivers/hid/surface-hid/Makefile b/drivers/hid/surface-hid/Makefile
|
|
new file mode 100644
|
|
index 000000000000..62fc04632d3d
|
|
--- /dev/null
|
|
+++ b/drivers/hid/surface-hid/Makefile
|
|
@@ -0,0 +1,6 @@
|
|
+# SPDX-License-Identifier: GPL-2.0+
|
|
+#
|
|
+# Makefile - Surface System Aggregator Module (SSAM) HID transport driver.
|
|
+#
|
|
+obj-$(CONFIG_SURFACE_HID_CORE) += surface_hid_core.o
|
|
+obj-$(CONFIG_SURFACE_HID) += surface_hid.o
|
|
diff --git a/drivers/hid/surface-hid/surface_hid.c b/drivers/hid/surface-hid/surface_hid.c
|
|
new file mode 100644
|
|
index 000000000000..3477b31611ae
|
|
--- /dev/null
|
|
+++ b/drivers/hid/surface-hid/surface_hid.c
|
|
@@ -0,0 +1,253 @@
|
|
+// 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_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_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 = 0;
|
|
+ rqst.length = sizeof(rprt_id);
|
|
+ rqst.payload = &rprt_id;
|
|
+
|
|
+ rsp.capacity = len;
|
|
+ rsp.length = 0;
|
|
+ rsp.pointer = buf;
|
|
+
|
|
+ return ssam_retry(ssam_request_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;
|
|
+ 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, 0x02, SSAM_ANY_IID, 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");
|
|
diff --git a/drivers/hid/surface-hid/surface_hid_core.c b/drivers/hid/surface-hid/surface_hid_core.c
|
|
new file mode 100644
|
|
index 000000000000..7b27ec392232
|
|
--- /dev/null
|
|
+++ b/drivers/hid/surface-hid/surface_hid_core.c
|
|
@@ -0,0 +1,272 @@
|
|
+// SPDX-License-Identifier: GPL-2.0+
|
|
+/*
|
|
+ * Common/core components for the Surface System Aggregator Module (SSAM) HID
|
|
+ * transport driver. Provides support for integrated HID devices on Microsoft
|
|
+ * Surface models.
|
|
+ *
|
|
+ * Copyright (C) 2019-2021 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/usb/ch9.h>
|
|
+
|
|
+#include <linux/surface_aggregator/controller.h>
|
|
+
|
|
+#include "surface_hid_core.h"
|
|
+
|
|
+
|
|
+/* -- Device descriptor access. --------------------------------------------- */
|
|
+
|
|
+static int surface_hid_load_hid_descriptor(struct surface_hid_device *shid)
|
|
+{
|
|
+ int status;
|
|
+
|
|
+ status = shid->ops.get_descriptor(shid, SURFACE_HID_DESC_HID,
|
|
+ (u8 *)&shid->hid_desc, sizeof(shid->hid_desc));
|
|
+ if (status)
|
|
+ return status;
|
|
+
|
|
+ if (shid->hid_desc.desc_len != sizeof(shid->hid_desc)) {
|
|
+ dev_err(shid->dev, "unexpected HID descriptor length: got %u, expected %zu\n",
|
|
+ shid->hid_desc.desc_len, sizeof(shid->hid_desc));
|
|
+ return -EPROTO;
|
|
+ }
|
|
+
|
|
+ if (shid->hid_desc.desc_type != HID_DT_HID) {
|
|
+ dev_err(shid->dev, "unexpected HID descriptor type: got %#04x, expected %#04x\n",
|
|
+ shid->hid_desc.desc_type, HID_DT_HID);
|
|
+ return -EPROTO;
|
|
+ }
|
|
+
|
|
+ if (shid->hid_desc.num_descriptors != 1) {
|
|
+ dev_err(shid->dev, "unexpected number of descriptors: got %u, expected 1\n",
|
|
+ shid->hid_desc.num_descriptors);
|
|
+ return -EPROTO;
|
|
+ }
|
|
+
|
|
+ if (shid->hid_desc.report_desc_type != HID_DT_REPORT) {
|
|
+ dev_err(shid->dev, "unexpected report descriptor type: got %#04x, expected %#04x\n",
|
|
+ shid->hid_desc.report_desc_type, HID_DT_REPORT);
|
|
+ return -EPROTO;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int surface_hid_load_device_attributes(struct surface_hid_device *shid)
|
|
+{
|
|
+ int status;
|
|
+
|
|
+ status = shid->ops.get_descriptor(shid, SURFACE_HID_DESC_ATTRS,
|
|
+ (u8 *)&shid->attrs, sizeof(shid->attrs));
|
|
+ if (status)
|
|
+ return status;
|
|
+
|
|
+ if (get_unaligned_le32(&shid->attrs.length) != sizeof(shid->attrs)) {
|
|
+ dev_err(shid->dev, "unexpected attribute length: got %u, expected %zu\n",
|
|
+ get_unaligned_le32(&shid->attrs.length), sizeof(shid->attrs));
|
|
+ return -EPROTO;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+
|
|
+/* -- Transport driver (common). -------------------------------------------- */
|
|
+
|
|
+static int surface_hid_start(struct hid_device *hid)
|
|
+{
|
|
+ struct surface_hid_device *shid = hid->driver_data;
|
|
+
|
|
+ return ssam_notifier_register(shid->ctrl, &shid->notif);
|
|
+}
|
|
+
|
|
+static void surface_hid_stop(struct hid_device *hid)
|
|
+{
|
|
+ struct surface_hid_device *shid = hid->driver_data;
|
|
+
|
|
+ /* Note: This call will log errors for us, so ignore them here. */
|
|
+ ssam_notifier_unregister(shid->ctrl, &shid->notif);
|
|
+}
|
|
+
|
|
+static int surface_hid_open(struct hid_device *hid)
|
|
+{
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static void surface_hid_close(struct hid_device *hid)
|
|
+{
|
|
+}
|
|
+
|
|
+static int surface_hid_parse(struct hid_device *hid)
|
|
+{
|
|
+ struct surface_hid_device *shid = hid->driver_data;
|
|
+ size_t len = get_unaligned_le16(&shid->hid_desc.report_desc_len);
|
|
+ u8 *buf;
|
|
+ int status;
|
|
+
|
|
+ buf = kzalloc(len, GFP_KERNEL);
|
|
+ if (!buf)
|
|
+ return -ENOMEM;
|
|
+
|
|
+ status = shid->ops.get_descriptor(shid, SURFACE_HID_DESC_REPORT, buf, len);
|
|
+ if (!status)
|
|
+ status = hid_parse_report(hid, buf, len);
|
|
+
|
|
+ kfree(buf);
|
|
+ return status;
|
|
+}
|
|
+
|
|
+static int surface_hid_raw_request(struct hid_device *hid, unsigned char reportnum, u8 *buf,
|
|
+ size_t len, unsigned char rtype, int reqtype)
|
|
+{
|
|
+ struct surface_hid_device *shid = hid->driver_data;
|
|
+
|
|
+ if (rtype == HID_OUTPUT_REPORT && reqtype == HID_REQ_SET_REPORT)
|
|
+ return shid->ops.output_report(shid, reportnum, buf, len);
|
|
+
|
|
+ else if (rtype == HID_FEATURE_REPORT && reqtype == HID_REQ_GET_REPORT)
|
|
+ return shid->ops.get_feature_report(shid, reportnum, buf, len);
|
|
+
|
|
+ else if (rtype == HID_FEATURE_REPORT && reqtype == HID_REQ_SET_REPORT)
|
|
+ return shid->ops.set_feature_report(shid, reportnum, buf, len);
|
|
+
|
|
+ return -EIO;
|
|
+}
|
|
+
|
|
+static struct hid_ll_driver surface_hid_ll_driver = {
|
|
+ .start = surface_hid_start,
|
|
+ .stop = surface_hid_stop,
|
|
+ .open = surface_hid_open,
|
|
+ .close = surface_hid_close,
|
|
+ .parse = surface_hid_parse,
|
|
+ .raw_request = surface_hid_raw_request,
|
|
+};
|
|
+
|
|
+
|
|
+/* -- Common device setup. -------------------------------------------------- */
|
|
+
|
|
+int surface_hid_device_add(struct surface_hid_device *shid)
|
|
+{
|
|
+ int status;
|
|
+
|
|
+ status = surface_hid_load_hid_descriptor(shid);
|
|
+ if (status)
|
|
+ return status;
|
|
+
|
|
+ status = surface_hid_load_device_attributes(shid);
|
|
+ if (status)
|
|
+ return status;
|
|
+
|
|
+ shid->hid = hid_allocate_device();
|
|
+ if (IS_ERR(shid->hid))
|
|
+ return PTR_ERR(shid->hid);
|
|
+
|
|
+ shid->hid->dev.parent = shid->dev;
|
|
+ shid->hid->bus = BUS_HOST;
|
|
+ shid->hid->vendor = cpu_to_le16(shid->attrs.vendor);
|
|
+ shid->hid->product = cpu_to_le16(shid->attrs.product);
|
|
+ shid->hid->version = cpu_to_le16(shid->hid_desc.hid_version);
|
|
+ shid->hid->country = shid->hid_desc.country_code;
|
|
+
|
|
+ snprintf(shid->hid->name, sizeof(shid->hid->name), "Microsoft Surface %04X:%04X",
|
|
+ shid->hid->vendor, shid->hid->product);
|
|
+
|
|
+ strscpy(shid->hid->phys, dev_name(shid->dev), sizeof(shid->hid->phys));
|
|
+
|
|
+ shid->hid->driver_data = shid;
|
|
+ shid->hid->ll_driver = &surface_hid_ll_driver;
|
|
+
|
|
+ status = hid_add_device(shid->hid);
|
|
+ if (status)
|
|
+ hid_destroy_device(shid->hid);
|
|
+
|
|
+ return status;
|
|
+}
|
|
+EXPORT_SYMBOL_GPL(surface_hid_device_add);
|
|
+
|
|
+void surface_hid_device_destroy(struct surface_hid_device *shid)
|
|
+{
|
|
+ hid_destroy_device(shid->hid);
|
|
+}
|
|
+EXPORT_SYMBOL_GPL(surface_hid_device_destroy);
|
|
+
|
|
+
|
|
+/* -- PM ops. --------------------------------------------------------------- */
|
|
+
|
|
+#ifdef CONFIG_PM_SLEEP
|
|
+
|
|
+static int surface_hid_suspend(struct device *dev)
|
|
+{
|
|
+ struct surface_hid_device *d = dev_get_drvdata(dev);
|
|
+
|
|
+ if (d->hid->driver && d->hid->driver->suspend)
|
|
+ return d->hid->driver->suspend(d->hid, PMSG_SUSPEND);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int surface_hid_resume(struct device *dev)
|
|
+{
|
|
+ struct surface_hid_device *d = dev_get_drvdata(dev);
|
|
+
|
|
+ if (d->hid->driver && d->hid->driver->resume)
|
|
+ return d->hid->driver->resume(d->hid);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int surface_hid_freeze(struct device *dev)
|
|
+{
|
|
+ struct surface_hid_device *d = dev_get_drvdata(dev);
|
|
+
|
|
+ if (d->hid->driver && d->hid->driver->suspend)
|
|
+ return d->hid->driver->suspend(d->hid, PMSG_FREEZE);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int surface_hid_poweroff(struct device *dev)
|
|
+{
|
|
+ struct surface_hid_device *d = dev_get_drvdata(dev);
|
|
+
|
|
+ if (d->hid->driver && d->hid->driver->suspend)
|
|
+ return d->hid->driver->suspend(d->hid, PMSG_HIBERNATE);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int surface_hid_restore(struct device *dev)
|
|
+{
|
|
+ struct surface_hid_device *d = dev_get_drvdata(dev);
|
|
+
|
|
+ if (d->hid->driver && d->hid->driver->reset_resume)
|
|
+ return d->hid->driver->reset_resume(d->hid);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+const struct dev_pm_ops surface_hid_pm_ops = {
|
|
+ .freeze = surface_hid_freeze,
|
|
+ .thaw = surface_hid_resume,
|
|
+ .suspend = surface_hid_suspend,
|
|
+ .resume = surface_hid_resume,
|
|
+ .poweroff = surface_hid_poweroff,
|
|
+ .restore = surface_hid_restore,
|
|
+};
|
|
+EXPORT_SYMBOL_GPL(surface_hid_pm_ops);
|
|
+
|
|
+#else /* CONFIG_PM_SLEEP */
|
|
+
|
|
+const struct dev_pm_ops surface_hid_pm_ops = { };
|
|
+EXPORT_SYMBOL_GPL(surface_hid_pm_ops);
|
|
+
|
|
+#endif /* CONFIG_PM_SLEEP */
|
|
+
|
|
+MODULE_AUTHOR("Maximilian Luz <luzmaximilian@gmail.com>");
|
|
+MODULE_DESCRIPTION("HID transport driver core for Surface System Aggregator Module");
|
|
+MODULE_LICENSE("GPL");
|
|
diff --git a/drivers/hid/surface-hid/surface_hid_core.h b/drivers/hid/surface-hid/surface_hid_core.h
|
|
new file mode 100644
|
|
index 000000000000..4b1a7b57e035
|
|
--- /dev/null
|
|
+++ b/drivers/hid/surface-hid/surface_hid_core.h
|
|
@@ -0,0 +1,77 @@
|
|
+/* SPDX-License-Identifier: GPL-2.0+ */
|
|
+/*
|
|
+ * Common/core components for the Surface System Aggregator Module (SSAM) HID
|
|
+ * transport driver. Provides support for integrated HID devices on Microsoft
|
|
+ * Surface models.
|
|
+ *
|
|
+ * Copyright (C) 2019-2021 Maximilian Luz <luzmaximilian@gmail.com>
|
|
+ */
|
|
+
|
|
+#ifndef SURFACE_HID_CORE_H
|
|
+#define SURFACE_HID_CORE_H
|
|
+
|
|
+#include <linux/hid.h>
|
|
+#include <linux/pm.h>
|
|
+#include <linux/types.h>
|
|
+
|
|
+#include <linux/surface_aggregator/controller.h>
|
|
+#include <linux/surface_aggregator/device.h>
|
|
+
|
|
+enum surface_hid_descriptor_entry {
|
|
+ SURFACE_HID_DESC_HID = 0,
|
|
+ SURFACE_HID_DESC_REPORT = 1,
|
|
+ SURFACE_HID_DESC_ATTRS = 2,
|
|
+};
|
|
+
|
|
+struct surface_hid_descriptor {
|
|
+ __u8 desc_len; /* = 9 */
|
|
+ __u8 desc_type; /* = HID_DT_HID */
|
|
+ __le16 hid_version;
|
|
+ __u8 country_code;
|
|
+ __u8 num_descriptors; /* = 1 */
|
|
+
|
|
+ __u8 report_desc_type; /* = HID_DT_REPORT */
|
|
+ __le16 report_desc_len;
|
|
+} __packed;
|
|
+
|
|
+static_assert(sizeof(struct surface_hid_descriptor) == 9);
|
|
+
|
|
+struct surface_hid_attributes {
|
|
+ __le32 length;
|
|
+ __le16 vendor;
|
|
+ __le16 product;
|
|
+ __le16 version;
|
|
+ __u8 _unknown[22];
|
|
+} __packed;
|
|
+
|
|
+static_assert(sizeof(struct surface_hid_attributes) == 32);
|
|
+
|
|
+struct surface_hid_device;
|
|
+
|
|
+struct surface_hid_device_ops {
|
|
+ int (*get_descriptor)(struct surface_hid_device *shid, u8 entry, u8 *buf, size_t len);
|
|
+ int (*output_report)(struct surface_hid_device *shid, u8 rprt_id, u8 *buf, size_t len);
|
|
+ int (*get_feature_report)(struct surface_hid_device *shid, u8 rprt_id, u8 *buf, size_t len);
|
|
+ int (*set_feature_report)(struct surface_hid_device *shid, u8 rprt_id, u8 *buf, size_t len);
|
|
+};
|
|
+
|
|
+struct surface_hid_device {
|
|
+ struct device *dev;
|
|
+ struct ssam_controller *ctrl;
|
|
+ struct ssam_device_uid uid;
|
|
+
|
|
+ struct surface_hid_descriptor hid_desc;
|
|
+ struct surface_hid_attributes attrs;
|
|
+
|
|
+ struct ssam_event_notifier notif;
|
|
+ struct hid_device *hid;
|
|
+
|
|
+ struct surface_hid_device_ops ops;
|
|
+};
|
|
+
|
|
+int surface_hid_device_add(struct surface_hid_device *shid);
|
|
+void surface_hid_device_destroy(struct surface_hid_device *shid);
|
|
+
|
|
+extern const struct dev_pm_ops surface_hid_pm_ops;
|
|
+
|
|
+#endif /* SURFACE_HID_CORE_H */
|
|
--
|
|
2.32.0
|
|
|
|
From ca407872def8e83d86ddaa834c2ec02fe725a1b2 Mon Sep 17 00:00:00 2001
|
|
From: Maximilian Luz <luzmaximilian@gmail.com>
|
|
Date: Wed, 10 Mar 2021 23:53:29 +0100
|
|
Subject: [PATCH] HID: surface-hid: Add support for legacy keyboard interface
|
|
|
|
Add support for the legacy keyboard (KBD/TC=0x08) HID transport layer of
|
|
the Surface System Aggregator Module (SSAM) to the Surface HID driver.
|
|
On Surface Laptops 1 and 2, this interface is used to connect the
|
|
integrated keyboard.
|
|
|
|
Note that this subsystem interface essentially provides a limited HID
|
|
transport layer. In contrast to the generic HID interface (TC=0x15) used
|
|
on newer Surface models, this interface only allows (as far as we know)
|
|
for a single device to be connected and is otherwise severely limited in
|
|
terms of support for feature- and output-reports. Specifically, only
|
|
caps-lock-LED output-reports and a single read-only feature-report are
|
|
supported.
|
|
|
|
Signed-off-by: Maximilian Luz <luzmaximilian@gmail.com>
|
|
Signed-off-by: Jiri Kosina <jkosina@suse.cz>
|
|
Patchset: surface-sam
|
|
---
|
|
drivers/hid/surface-hid/Kconfig | 14 ++
|
|
drivers/hid/surface-hid/Makefile | 1 +
|
|
drivers/hid/surface-hid/surface_kbd.c | 300 ++++++++++++++++++++++++++
|
|
3 files changed, 315 insertions(+)
|
|
create mode 100644 drivers/hid/surface-hid/surface_kbd.c
|
|
|
|
diff --git a/drivers/hid/surface-hid/Kconfig b/drivers/hid/surface-hid/Kconfig
|
|
index 642c7f0e64fe..7ce9b5d641eb 100644
|
|
--- a/drivers/hid/surface-hid/Kconfig
|
|
+++ b/drivers/hid/surface-hid/Kconfig
|
|
@@ -21,6 +21,20 @@ config SURFACE_HID
|
|
integrated touchpad and keyboard, on 7th generation Microsoft Surface
|
|
models.
|
|
|
|
+config SURFACE_KBD
|
|
+ tristate "HID keyboard transport driver for Surface System Aggregator Module"
|
|
+ select SURFACE_HID_CORE
|
|
+ help
|
|
+ Driver to support HID keyboards on Surface Laptop 1 and 2 devices.
|
|
+
|
|
+ This driver provides support for the HID transport protocol provided
|
|
+ by the Surface Aggregator Module (i.e. the embedded controller) on
|
|
+ Microsoft Surface Laptops 1 and 2. It is used to connect the
|
|
+ integrated keyboard on those devices.
|
|
+
|
|
+ Say M or Y here, if you want support for the integrated keyboard on
|
|
+ Microsoft Surface Laptops 1 and 2.
|
|
+
|
|
endmenu
|
|
|
|
config SURFACE_HID_CORE
|
|
diff --git a/drivers/hid/surface-hid/Makefile b/drivers/hid/surface-hid/Makefile
|
|
index 62fc04632d3d..4ae11cf09b25 100644
|
|
--- a/drivers/hid/surface-hid/Makefile
|
|
+++ b/drivers/hid/surface-hid/Makefile
|
|
@@ -4,3 +4,4 @@
|
|
#
|
|
obj-$(CONFIG_SURFACE_HID_CORE) += surface_hid_core.o
|
|
obj-$(CONFIG_SURFACE_HID) += surface_hid.o
|
|
+obj-$(CONFIG_SURFACE_KBD) += surface_kbd.o
|
|
diff --git a/drivers/hid/surface-hid/surface_kbd.c b/drivers/hid/surface-hid/surface_kbd.c
|
|
new file mode 100644
|
|
index 000000000000..0635341bc517
|
|
--- /dev/null
|
|
+++ b/drivers/hid/surface-hid/surface_kbd.c
|
|
@@ -0,0 +1,300 @@
|
|
+// SPDX-License-Identifier: GPL-2.0+
|
|
+/*
|
|
+ * Surface System Aggregator Module (SSAM) HID transport driver for the legacy
|
|
+ * keyboard interface (KBD/TC=0x08 subsystem). Provides support for the
|
|
+ * integrated HID keyboard on Surface Laptops 1 and 2.
|
|
+ *
|
|
+ * Copyright (C) 2019-2021 Maximilian Luz <luzmaximilian@gmail.com>
|
|
+ */
|
|
+
|
|
+#include <asm/unaligned.h>
|
|
+#include <linux/hid.h>
|
|
+#include <linux/kernel.h>
|
|
+#include <linux/module.h>
|
|
+#include <linux/platform_device.h>
|
|
+#include <linux/types.h>
|
|
+
|
|
+#include <linux/surface_aggregator/controller.h>
|
|
+
|
|
+#include "surface_hid_core.h"
|
|
+
|
|
+
|
|
+/* -- SAM interface (KBD). -------------------------------------------------- */
|
|
+
|
|
+#define KBD_FEATURE_REPORT_SIZE 7 /* 6 + report ID */
|
|
+
|
|
+enum surface_kbd_cid {
|
|
+ SURFACE_KBD_CID_GET_DESCRIPTOR = 0x00,
|
|
+ SURFACE_KBD_CID_SET_CAPSLOCK_LED = 0x01,
|
|
+ SURFACE_KBD_CID_EVT_INPUT_GENERIC = 0x03,
|
|
+ SURFACE_KBD_CID_EVT_INPUT_HOTKEYS = 0x04,
|
|
+ SURFACE_KBD_CID_GET_FEATURE_REPORT = 0x0b,
|
|
+};
|
|
+
|
|
+static int ssam_kbd_get_descriptor(struct surface_hid_device *shid, u8 entry, u8 *buf, size_t len)
|
|
+{
|
|
+ struct ssam_request rqst;
|
|
+ struct ssam_response rsp;
|
|
+ int status;
|
|
+
|
|
+ rqst.target_category = shid->uid.category;
|
|
+ rqst.target_id = shid->uid.target;
|
|
+ rqst.command_id = SURFACE_KBD_CID_GET_DESCRIPTOR;
|
|
+ rqst.instance_id = shid->uid.instance;
|
|
+ rqst.flags = SSAM_REQUEST_HAS_RESPONSE;
|
|
+ rqst.length = sizeof(entry);
|
|
+ rqst.payload = &entry;
|
|
+
|
|
+ rsp.capacity = len;
|
|
+ rsp.length = 0;
|
|
+ rsp.pointer = buf;
|
|
+
|
|
+ status = ssam_retry(ssam_request_sync_onstack, shid->ctrl, &rqst, &rsp, sizeof(entry));
|
|
+ if (status)
|
|
+ return status;
|
|
+
|
|
+ if (rsp.length != len) {
|
|
+ dev_err(shid->dev, "invalid descriptor length: got %zu, expected, %zu\n",
|
|
+ rsp.length, len);
|
|
+ return -EPROTO;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int ssam_kbd_set_caps_led(struct surface_hid_device *shid, bool value)
|
|
+{
|
|
+ struct ssam_request rqst;
|
|
+ u8 value_u8 = value;
|
|
+
|
|
+ rqst.target_category = shid->uid.category;
|
|
+ rqst.target_id = shid->uid.target;
|
|
+ rqst.command_id = SURFACE_KBD_CID_SET_CAPSLOCK_LED;
|
|
+ rqst.instance_id = shid->uid.instance;
|
|
+ rqst.flags = 0;
|
|
+ rqst.length = sizeof(value_u8);
|
|
+ rqst.payload = &value_u8;
|
|
+
|
|
+ return ssam_retry(ssam_request_sync_onstack, shid->ctrl, &rqst, NULL, sizeof(value_u8));
|
|
+}
|
|
+
|
|
+static int ssam_kbd_get_feature_report(struct surface_hid_device *shid, u8 *buf, size_t len)
|
|
+{
|
|
+ struct ssam_request rqst;
|
|
+ struct ssam_response rsp;
|
|
+ u8 payload = 0;
|
|
+ int status;
|
|
+
|
|
+ rqst.target_category = shid->uid.category;
|
|
+ rqst.target_id = shid->uid.target;
|
|
+ rqst.command_id = SURFACE_KBD_CID_GET_FEATURE_REPORT;
|
|
+ rqst.instance_id = shid->uid.instance;
|
|
+ rqst.flags = SSAM_REQUEST_HAS_RESPONSE;
|
|
+ rqst.length = sizeof(payload);
|
|
+ rqst.payload = &payload;
|
|
+
|
|
+ rsp.capacity = len;
|
|
+ rsp.length = 0;
|
|
+ rsp.pointer = buf;
|
|
+
|
|
+ status = ssam_retry(ssam_request_sync_onstack, shid->ctrl, &rqst, &rsp, sizeof(payload));
|
|
+ if (status)
|
|
+ return status;
|
|
+
|
|
+ if (rsp.length != len) {
|
|
+ dev_err(shid->dev, "invalid feature report length: got %zu, expected, %zu\n",
|
|
+ rsp.length, len);
|
|
+ return -EPROTO;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static bool ssam_kbd_is_input_event(const struct ssam_event *event)
|
|
+{
|
|
+ if (event->command_id == SURFACE_KBD_CID_EVT_INPUT_GENERIC)
|
|
+ return true;
|
|
+
|
|
+ if (event->command_id == SURFACE_KBD_CID_EVT_INPUT_HOTKEYS)
|
|
+ return true;
|
|
+
|
|
+ return false;
|
|
+}
|
|
+
|
|
+static u32 ssam_kbd_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);
|
|
+
|
|
+ /*
|
|
+ * Check against device UID manually, as registry and device target
|
|
+ * category doesn't line up.
|
|
+ */
|
|
+
|
|
+ if (shid->uid.category != event->target_category)
|
|
+ return 0;
|
|
+
|
|
+ if (shid->uid.target != event->target_id)
|
|
+ return 0;
|
|
+
|
|
+ if (shid->uid.instance != event->instance_id)
|
|
+ return 0;
|
|
+
|
|
+ if (!ssam_kbd_is_input_event(event))
|
|
+ return 0;
|
|
+
|
|
+ hid_input_report(shid->hid, HID_INPUT_REPORT, (u8 *)&event->data[0], event->length, 0);
|
|
+ return SSAM_NOTIF_HANDLED;
|
|
+}
|
|
+
|
|
+
|
|
+/* -- Transport driver (KBD). ----------------------------------------------- */
|
|
+
|
|
+static int skbd_get_caps_led_value(struct hid_device *hid, u8 rprt_id, u8 *buf, size_t len)
|
|
+{
|
|
+ struct hid_field *field;
|
|
+ unsigned int offset, size;
|
|
+ int i;
|
|
+
|
|
+ /* Get LED field. */
|
|
+ field = hidinput_get_led_field(hid);
|
|
+ if (!field)
|
|
+ return -ENOENT;
|
|
+
|
|
+ /* Check if we got the correct report. */
|
|
+ if (len != hid_report_len(field->report))
|
|
+ return -ENOENT;
|
|
+
|
|
+ if (rprt_id != field->report->id)
|
|
+ return -ENOENT;
|
|
+
|
|
+ /* Get caps lock LED index. */
|
|
+ for (i = 0; i < field->report_count; i++)
|
|
+ if ((field->usage[i].hid & 0xffff) == 0x02)
|
|
+ break;
|
|
+
|
|
+ if (i == field->report_count)
|
|
+ return -ENOENT;
|
|
+
|
|
+ /* Extract value. */
|
|
+ size = field->report_size;
|
|
+ offset = field->report_offset + i * size;
|
|
+ return !!hid_field_extract(hid, buf + 1, size, offset);
|
|
+}
|
|
+
|
|
+static int skbd_output_report(struct surface_hid_device *shid, u8 rprt_id, u8 *buf, size_t len)
|
|
+{
|
|
+ int caps_led;
|
|
+ int status;
|
|
+
|
|
+ caps_led = skbd_get_caps_led_value(shid->hid, rprt_id, buf, len);
|
|
+ if (caps_led < 0)
|
|
+ return -EIO; /* Only caps LED output reports are supported. */
|
|
+
|
|
+ status = ssam_kbd_set_caps_led(shid, caps_led);
|
|
+ if (status < 0)
|
|
+ return status;
|
|
+
|
|
+ return len;
|
|
+}
|
|
+
|
|
+static int skbd_get_feature_report(struct surface_hid_device *shid, u8 rprt_id, u8 *buf, size_t len)
|
|
+{
|
|
+ u8 report[KBD_FEATURE_REPORT_SIZE];
|
|
+ int status;
|
|
+
|
|
+ /*
|
|
+ * The keyboard only has a single hard-coded read-only feature report
|
|
+ * of size KBD_FEATURE_REPORT_SIZE. Try to load it and compare its
|
|
+ * report ID against the requested one.
|
|
+ */
|
|
+
|
|
+ if (len < ARRAY_SIZE(report))
|
|
+ return -ENOSPC;
|
|
+
|
|
+ status = ssam_kbd_get_feature_report(shid, report, ARRAY_SIZE(report));
|
|
+ if (status < 0)
|
|
+ return status;
|
|
+
|
|
+ if (rprt_id != report[0])
|
|
+ return -ENOENT;
|
|
+
|
|
+ memcpy(buf, report, ARRAY_SIZE(report));
|
|
+ return len;
|
|
+}
|
|
+
|
|
+static int skbd_set_feature_report(struct surface_hid_device *shid, u8 rprt_id, u8 *buf, size_t len)
|
|
+{
|
|
+ /* Not supported. See skbd_get_feature_report() for details. */
|
|
+ return -EIO;
|
|
+}
|
|
+
|
|
+
|
|
+/* -- Driver setup. --------------------------------------------------------- */
|
|
+
|
|
+static int surface_kbd_probe(struct platform_device *pdev)
|
|
+{
|
|
+ struct ssam_controller *ctrl;
|
|
+ struct surface_hid_device *shid;
|
|
+
|
|
+ /* Add device link to EC. */
|
|
+ ctrl = ssam_client_bind(&pdev->dev);
|
|
+ if (IS_ERR(ctrl))
|
|
+ return PTR_ERR(ctrl) == -ENODEV ? -EPROBE_DEFER : PTR_ERR(ctrl);
|
|
+
|
|
+ shid = devm_kzalloc(&pdev->dev, sizeof(*shid), GFP_KERNEL);
|
|
+ if (!shid)
|
|
+ return -ENOMEM;
|
|
+
|
|
+ shid->dev = &pdev->dev;
|
|
+ shid->ctrl = ctrl;
|
|
+
|
|
+ shid->uid.domain = SSAM_DOMAIN_SERIALHUB;
|
|
+ shid->uid.category = SSAM_SSH_TC_KBD;
|
|
+ shid->uid.target = 2;
|
|
+ shid->uid.instance = 0;
|
|
+ shid->uid.function = 0;
|
|
+
|
|
+ shid->notif.base.priority = 1;
|
|
+ shid->notif.base.fn = ssam_kbd_event_fn;
|
|
+ shid->notif.event.reg = SSAM_EVENT_REGISTRY_SAM;
|
|
+ shid->notif.event.id.target_category = shid->uid.category;
|
|
+ shid->notif.event.id.instance = shid->uid.instance;
|
|
+ shid->notif.event.mask = SSAM_EVENT_MASK_NONE;
|
|
+ shid->notif.event.flags = 0;
|
|
+
|
|
+ shid->ops.get_descriptor = ssam_kbd_get_descriptor;
|
|
+ shid->ops.output_report = skbd_output_report;
|
|
+ shid->ops.get_feature_report = skbd_get_feature_report;
|
|
+ shid->ops.set_feature_report = skbd_set_feature_report;
|
|
+
|
|
+ platform_set_drvdata(pdev, shid);
|
|
+ return surface_hid_device_add(shid);
|
|
+}
|
|
+
|
|
+static int surface_kbd_remove(struct platform_device *pdev)
|
|
+{
|
|
+ surface_hid_device_destroy(platform_get_drvdata(pdev));
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static const struct acpi_device_id surface_kbd_match[] = {
|
|
+ { "MSHW0096" },
|
|
+ { },
|
|
+};
|
|
+MODULE_DEVICE_TABLE(acpi, surface_kbd_match);
|
|
+
|
|
+static struct platform_driver surface_kbd_driver = {
|
|
+ .probe = surface_kbd_probe,
|
|
+ .remove = surface_kbd_remove,
|
|
+ .driver = {
|
|
+ .name = "surface_keyboard",
|
|
+ .acpi_match_table = surface_kbd_match,
|
|
+ .pm = &surface_hid_pm_ops,
|
|
+ .probe_type = PROBE_PREFER_ASYNCHRONOUS,
|
|
+ },
|
|
+};
|
|
+module_platform_driver(surface_kbd_driver);
|
|
+
|
|
+MODULE_AUTHOR("Maximilian Luz <luzmaximilian@gmail.com>");
|
|
+MODULE_DESCRIPTION("HID legacy transport driver for Surface System Aggregator Module");
|
|
+MODULE_LICENSE("GPL");
|
|
--
|
|
2.32.0
|
|
|
|
From bf74b38c3b0fca5cf1255a61760acab783272854 Mon Sep 17 00:00:00 2001
|
|
From: Maximilian Luz <luzmaximilian@gmail.com>
|
|
Date: Fri, 23 Apr 2021 00:51:22 +0200
|
|
Subject: [PATCH] HID: surface-hid: Fix integer endian conversion
|
|
|
|
We want to convert from 16 bit (unsigned) little endian values contained
|
|
in a packed struct to CPU native endian values here, not the other way
|
|
around. So replace cpu_to_le16() with get_unaligned_le16(), using the
|
|
latter instead of le16_to_cpu() to acknowledge that we are reading from
|
|
a packed struct.
|
|
|
|
Reported-by: kernel test robot <lkp@intel.com>
|
|
Fixes: b05ff1002a5c ("HID: Add support for Surface Aggregator Module HID transport")
|
|
Signed-off-by: Maximilian Luz <luzmaximilian@gmail.com>
|
|
Patchset: surface-sam
|
|
---
|
|
drivers/hid/surface-hid/surface_hid_core.c | 6 +++---
|
|
1 file changed, 3 insertions(+), 3 deletions(-)
|
|
|
|
diff --git a/drivers/hid/surface-hid/surface_hid_core.c b/drivers/hid/surface-hid/surface_hid_core.c
|
|
index 7b27ec392232..5571e74abe91 100644
|
|
--- a/drivers/hid/surface-hid/surface_hid_core.c
|
|
+++ b/drivers/hid/surface-hid/surface_hid_core.c
|
|
@@ -168,9 +168,9 @@ int surface_hid_device_add(struct surface_hid_device *shid)
|
|
|
|
shid->hid->dev.parent = shid->dev;
|
|
shid->hid->bus = BUS_HOST;
|
|
- shid->hid->vendor = cpu_to_le16(shid->attrs.vendor);
|
|
- shid->hid->product = cpu_to_le16(shid->attrs.product);
|
|
- shid->hid->version = cpu_to_le16(shid->hid_desc.hid_version);
|
|
+ shid->hid->vendor = get_unaligned_le16(&shid->attrs.vendor);
|
|
+ shid->hid->product = get_unaligned_le16(&shid->attrs.product);
|
|
+ shid->hid->version = get_unaligned_le16(&shid->hid_desc.hid_version);
|
|
shid->hid->country = shid->hid_desc.country_code;
|
|
|
|
snprintf(shid->hid->name, sizeof(shid->hid->name), "Microsoft Surface %04X:%04X",
|
|
--
|
|
2.32.0
|
|
|
|
From df72fbd50fbc91af28598541702ef8f829b709c6 Mon Sep 17 00:00:00 2001
|
|
From: Maximilian Luz <luzmaximilian@gmail.com>
|
|
Date: Mon, 7 Jun 2021 21:56:22 +0200
|
|
Subject: [PATCH] HID: surface-hid: Fix get-report request
|
|
|
|
Getting a report (e.g. feature report) from a device requires us to send
|
|
a request indicating which report we want to retreive and then waiting
|
|
for the corresponding response containing that report. We already
|
|
provide the response structure to the request call, but the request
|
|
isn't marked as a request that expects a response. Thus the request
|
|
returns before we receive the response and the response buffer indicates
|
|
a zero length response due to that.
|
|
|
|
This essentially means that the get-report calls are broken and will
|
|
always indicate that a report of length zero has been read.
|
|
|
|
Fix this by appropriately marking the request.
|
|
|
|
Fixes: b05ff1002a5c ("HID: Add support for Surface Aggregator Module HID transport")
|
|
Signed-off-by: Maximilian Luz <luzmaximilian@gmail.com>
|
|
Patchset: surface-sam
|
|
---
|
|
drivers/hid/surface-hid/surface_hid.c | 2 +-
|
|
1 file changed, 1 insertion(+), 1 deletion(-)
|
|
|
|
diff --git a/drivers/hid/surface-hid/surface_hid.c b/drivers/hid/surface-hid/surface_hid.c
|
|
index 3477b31611ae..a3a70e4f3f6c 100644
|
|
--- a/drivers/hid/surface-hid/surface_hid.c
|
|
+++ b/drivers/hid/surface-hid/surface_hid.c
|
|
@@ -143,7 +143,7 @@ static int ssam_hid_get_raw_report(struct surface_hid_device *shid, u8 rprt_id,
|
|
rqst.target_id = shid->uid.target;
|
|
rqst.instance_id = shid->uid.instance;
|
|
rqst.command_id = SURFACE_HID_CID_GET_FEATURE_REPORT;
|
|
- rqst.flags = 0;
|
|
+ rqst.flags = SSAM_REQUEST_HAS_RESPONSE;
|
|
rqst.length = sizeof(rprt_id);
|
|
rqst.payload = &rprt_id;
|
|
|
|
--
|
|
2.32.0
|
|
|
|
From 4a2ca25f24557a8c8f8eb65d6a7cb49f62b2fb23 Mon Sep 17 00:00:00 2001
|
|
From: Maximilian Luz <luzmaximilian@gmail.com>
|
|
Date: Tue, 6 Apr 2021 01:41:25 +0200
|
|
Subject: [PATCH] power: supply: Add battery driver for Surface Aggregator
|
|
Module
|
|
|
|
On newer Microsoft Surface models (specifically 7th-generation, i.e.
|
|
Surface Pro 7, Surface Book 3, Surface Laptop 3, and Surface Laptop Go),
|
|
battery and AC status/information is no longer handled via standard ACPI
|
|
devices, but instead directly via the Surface System Aggregator Module
|
|
(SSAM), i.e. the embedded controller on those devices.
|
|
|
|
While on previous generation models, battery status is also handled via
|
|
SSAM, an ACPI shim was present to translate the standard ACPI battery
|
|
interface to SSAM requests. The SSAM interface itself, which is modeled
|
|
closely after the ACPI interface, has not changed.
|
|
|
|
This commit introduces a new SSAM client device driver to support
|
|
battery status/information via the aforementioned interface on said
|
|
Surface models. It is in parts based on the standard ACPI battery
|
|
driver.
|
|
|
|
Signed-off-by: Maximilian Luz <luzmaximilian@gmail.com>
|
|
Signed-off-by: Sebastian Reichel <sebastian.reichel@collabora.com>
|
|
Patchset: surface-sam
|
|
---
|
|
.../ABI/testing/sysfs-class-power-surface | 15 +
|
|
MAINTAINERS | 7 +
|
|
drivers/power/supply/Kconfig | 16 +
|
|
drivers/power/supply/Makefile | 1 +
|
|
drivers/power/supply/surface_battery.c | 865 ++++++++++++++++++
|
|
5 files changed, 904 insertions(+)
|
|
create mode 100644 Documentation/ABI/testing/sysfs-class-power-surface
|
|
create mode 100644 drivers/power/supply/surface_battery.c
|
|
|
|
diff --git a/Documentation/ABI/testing/sysfs-class-power-surface b/Documentation/ABI/testing/sysfs-class-power-surface
|
|
new file mode 100644
|
|
index 000000000000..79cde4dcf2f5
|
|
--- /dev/null
|
|
+++ b/Documentation/ABI/testing/sysfs-class-power-surface
|
|
@@ -0,0 +1,15 @@
|
|
+What: /sys/class/power_supply/<supply_name>/alarm
|
|
+Date: April 2021
|
|
+KernelVersion: 5.13
|
|
+Contact: Maximilian Luz <luzmaximilian@gmail.com>
|
|
+Description:
|
|
+ Battery trip point. When the remaining battery capacity crosses this
|
|
+ value in either direction, the system will be notified and if
|
|
+ necessary woken.
|
|
+
|
|
+ Set to zero to clear/disable.
|
|
+
|
|
+ Access: Read, Write
|
|
+
|
|
+ Valid values: In micro-Wh or micro-Ah, depending on the power unit
|
|
+ of the battery
|
|
diff --git a/MAINTAINERS b/MAINTAINERS
|
|
index f54b22333ec6..7ee93b732270 100644
|
|
--- a/MAINTAINERS
|
|
+++ b/MAINTAINERS
|
|
@@ -11868,6 +11868,13 @@ F: drivers/scsi/smartpqi/smartpqi*.[ch]
|
|
F: include/linux/cciss*.h
|
|
F: include/uapi/linux/cciss*.h
|
|
|
|
+MICROSOFT SURFACE BATTERY AND AC DRIVERS
|
|
+M: Maximilian Luz <luzmaximilian@gmail.com>
|
|
+L: linux-pm@vger.kernel.org
|
|
+L: platform-driver-x86@vger.kernel.org
|
|
+S: Maintained
|
|
+F: drivers/power/supply/surface_battery.c
|
|
+
|
|
MICROSOFT SURFACE DTX DRIVER
|
|
M: Maximilian Luz <luzmaximilian@gmail.com>
|
|
L: platform-driver-x86@vger.kernel.org
|
|
diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig
|
|
index 006b95eca673..cebeff10d543 100644
|
|
--- a/drivers/power/supply/Kconfig
|
|
+++ b/drivers/power/supply/Kconfig
|
|
@@ -801,4 +801,20 @@ config BATTERY_ACER_A500
|
|
help
|
|
Say Y to include support for Acer Iconia Tab A500 battery fuel gauge.
|
|
|
|
+config BATTERY_SURFACE
|
|
+ tristate "Battery driver for 7th-generation Microsoft Surface devices"
|
|
+ depends on SURFACE_AGGREGATOR_REGISTRY
|
|
+ help
|
|
+ Driver for battery devices connected via/managed by the Surface System
|
|
+ Aggregator Module (SSAM).
|
|
+
|
|
+ This driver provides battery-information and -status support for
|
|
+ Surface devices where said data is not exposed via the standard ACPI
|
|
+ devices. On those models (7th-generation), battery-information is
|
|
+ instead handled directly via SSAM client devices and this driver.
|
|
+
|
|
+ Say M or Y here to include battery status support for 7th-generation
|
|
+ Microsoft Surface devices, i.e. Surface Pro 7, Surface Laptop 3,
|
|
+ Surface Book 3, and Surface Laptop Go.
|
|
+
|
|
endif # POWER_SUPPLY
|
|
diff --git a/drivers/power/supply/Makefile b/drivers/power/supply/Makefile
|
|
index 5e5fdbbef531..134041538d2c 100644
|
|
--- a/drivers/power/supply/Makefile
|
|
+++ b/drivers/power/supply/Makefile
|
|
@@ -101,3 +101,4 @@ obj-$(CONFIG_CHARGER_BD99954) += bd99954-charger.o
|
|
obj-$(CONFIG_CHARGER_WILCO) += wilco-charger.o
|
|
obj-$(CONFIG_RN5T618_POWER) += rn5t618_power.o
|
|
obj-$(CONFIG_BATTERY_ACER_A500) += acer_a500_battery.o
|
|
+obj-$(CONFIG_BATTERY_SURFACE) += surface_battery.o
|
|
diff --git a/drivers/power/supply/surface_battery.c b/drivers/power/supply/surface_battery.c
|
|
new file mode 100644
|
|
index 000000000000..4116dd839ecd
|
|
--- /dev/null
|
|
+++ b/drivers/power/supply/surface_battery.c
|
|
@@ -0,0 +1,865 @@
|
|
+// SPDX-License-Identifier: GPL-2.0+
|
|
+/*
|
|
+ * Battery driver for 7th-generation Microsoft Surface devices via Surface
|
|
+ * System Aggregator Module (SSAM).
|
|
+ *
|
|
+ * Copyright (C) 2019-2021 Maximilian Luz <luzmaximilian@gmail.com>
|
|
+ */
|
|
+
|
|
+#include <asm/unaligned.h>
|
|
+#include <linux/jiffies.h>
|
|
+#include <linux/kernel.h>
|
|
+#include <linux/module.h>
|
|
+#include <linux/mutex.h>
|
|
+#include <linux/power_supply.h>
|
|
+#include <linux/sysfs.h>
|
|
+#include <linux/types.h>
|
|
+#include <linux/workqueue.h>
|
|
+
|
|
+#include <linux/surface_aggregator/device.h>
|
|
+
|
|
+
|
|
+/* -- SAM interface. -------------------------------------------------------- */
|
|
+
|
|
+enum sam_event_cid_bat {
|
|
+ SAM_EVENT_CID_BAT_BIX = 0x15,
|
|
+ SAM_EVENT_CID_BAT_BST = 0x16,
|
|
+ SAM_EVENT_CID_BAT_ADP = 0x17,
|
|
+ SAM_EVENT_CID_BAT_PROT = 0x18,
|
|
+ SAM_EVENT_CID_BAT_DPTF = 0x53,
|
|
+};
|
|
+
|
|
+enum sam_battery_sta {
|
|
+ SAM_BATTERY_STA_OK = 0x0f,
|
|
+ SAM_BATTERY_STA_PRESENT = 0x10,
|
|
+};
|
|
+
|
|
+enum sam_battery_state {
|
|
+ SAM_BATTERY_STATE_DISCHARGING = BIT(0),
|
|
+ SAM_BATTERY_STATE_CHARGING = BIT(1),
|
|
+ SAM_BATTERY_STATE_CRITICAL = BIT(2),
|
|
+};
|
|
+
|
|
+enum sam_battery_power_unit {
|
|
+ SAM_BATTERY_POWER_UNIT_mW = 0,
|
|
+ SAM_BATTERY_POWER_UNIT_mA = 1,
|
|
+};
|
|
+
|
|
+/* Equivalent to data returned in ACPI _BIX method, revision 0. */
|
|
+struct spwr_bix {
|
|
+ u8 revision;
|
|
+ __le32 power_unit;
|
|
+ __le32 design_cap;
|
|
+ __le32 last_full_charge_cap;
|
|
+ __le32 technology;
|
|
+ __le32 design_voltage;
|
|
+ __le32 design_cap_warn;
|
|
+ __le32 design_cap_low;
|
|
+ __le32 cycle_count;
|
|
+ __le32 measurement_accuracy;
|
|
+ __le32 max_sampling_time;
|
|
+ __le32 min_sampling_time;
|
|
+ __le32 max_avg_interval;
|
|
+ __le32 min_avg_interval;
|
|
+ __le32 bat_cap_granularity_1;
|
|
+ __le32 bat_cap_granularity_2;
|
|
+ __u8 model[21];
|
|
+ __u8 serial[11];
|
|
+ __u8 type[5];
|
|
+ __u8 oem_info[21];
|
|
+} __packed;
|
|
+
|
|
+static_assert(sizeof(struct spwr_bix) == 119);
|
|
+
|
|
+/* Equivalent to data returned in ACPI _BST method. */
|
|
+struct spwr_bst {
|
|
+ __le32 state;
|
|
+ __le32 present_rate;
|
|
+ __le32 remaining_cap;
|
|
+ __le32 present_voltage;
|
|
+} __packed;
|
|
+
|
|
+static_assert(sizeof(struct spwr_bst) == 16);
|
|
+
|
|
+#define SPWR_BIX_REVISION 0
|
|
+#define SPWR_BATTERY_VALUE_UNKNOWN 0xffffffff
|
|
+
|
|
+/* Get battery status (_STA) */
|
|
+SSAM_DEFINE_SYNC_REQUEST_CL_R(ssam_bat_get_sta, __le32, {
|
|
+ .target_category = SSAM_SSH_TC_BAT,
|
|
+ .command_id = 0x01,
|
|
+});
|
|
+
|
|
+/* Get battery static information (_BIX). */
|
|
+SSAM_DEFINE_SYNC_REQUEST_CL_R(ssam_bat_get_bix, struct spwr_bix, {
|
|
+ .target_category = SSAM_SSH_TC_BAT,
|
|
+ .command_id = 0x02,
|
|
+});
|
|
+
|
|
+/* Get battery dynamic information (_BST). */
|
|
+SSAM_DEFINE_SYNC_REQUEST_CL_R(ssam_bat_get_bst, struct spwr_bst, {
|
|
+ .target_category = SSAM_SSH_TC_BAT,
|
|
+ .command_id = 0x03,
|
|
+});
|
|
+
|
|
+/* Set battery trip point (_BTP). */
|
|
+SSAM_DEFINE_SYNC_REQUEST_CL_W(ssam_bat_set_btp, __le32, {
|
|
+ .target_category = SSAM_SSH_TC_BAT,
|
|
+ .command_id = 0x04,
|
|
+});
|
|
+
|
|
+
|
|
+/* -- Device structures. ---------------------------------------------------- */
|
|
+
|
|
+struct spwr_psy_properties {
|
|
+ const char *name;
|
|
+ struct ssam_event_registry registry;
|
|
+};
|
|
+
|
|
+struct spwr_battery_device {
|
|
+ struct ssam_device *sdev;
|
|
+
|
|
+ char name[32];
|
|
+ struct power_supply *psy;
|
|
+ struct power_supply_desc psy_desc;
|
|
+
|
|
+ struct delayed_work update_work;
|
|
+
|
|
+ struct ssam_event_notifier notif;
|
|
+
|
|
+ struct mutex lock; /* Guards access to state data below. */
|
|
+ unsigned long timestamp;
|
|
+
|
|
+ __le32 sta;
|
|
+ struct spwr_bix bix;
|
|
+ struct spwr_bst bst;
|
|
+ u32 alarm;
|
|
+};
|
|
+
|
|
+
|
|
+/* -- Module parameters. ---------------------------------------------------- */
|
|
+
|
|
+static unsigned int cache_time = 1000;
|
|
+module_param(cache_time, uint, 0644);
|
|
+MODULE_PARM_DESC(cache_time, "battery state caching time in milliseconds [default: 1000]");
|
|
+
|
|
+
|
|
+/* -- State management. ----------------------------------------------------- */
|
|
+
|
|
+/*
|
|
+ * Delay for battery update quirk. See spwr_external_power_changed() below
|
|
+ * for more details.
|
|
+ */
|
|
+#define SPWR_AC_BAT_UPDATE_DELAY msecs_to_jiffies(5000)
|
|
+
|
|
+static bool spwr_battery_present(struct spwr_battery_device *bat)
|
|
+{
|
|
+ lockdep_assert_held(&bat->lock);
|
|
+
|
|
+ return le32_to_cpu(bat->sta) & SAM_BATTERY_STA_PRESENT;
|
|
+}
|
|
+
|
|
+static int spwr_battery_load_sta(struct spwr_battery_device *bat)
|
|
+{
|
|
+ lockdep_assert_held(&bat->lock);
|
|
+
|
|
+ return ssam_retry(ssam_bat_get_sta, bat->sdev, &bat->sta);
|
|
+}
|
|
+
|
|
+static int spwr_battery_load_bix(struct spwr_battery_device *bat)
|
|
+{
|
|
+ int status;
|
|
+
|
|
+ lockdep_assert_held(&bat->lock);
|
|
+
|
|
+ if (!spwr_battery_present(bat))
|
|
+ return 0;
|
|
+
|
|
+ status = ssam_retry(ssam_bat_get_bix, bat->sdev, &bat->bix);
|
|
+
|
|
+ /* Enforce NULL terminated strings in case anything goes wrong... */
|
|
+ bat->bix.model[ARRAY_SIZE(bat->bix.model) - 1] = 0;
|
|
+ bat->bix.serial[ARRAY_SIZE(bat->bix.serial) - 1] = 0;
|
|
+ bat->bix.type[ARRAY_SIZE(bat->bix.type) - 1] = 0;
|
|
+ bat->bix.oem_info[ARRAY_SIZE(bat->bix.oem_info) - 1] = 0;
|
|
+
|
|
+ return status;
|
|
+}
|
|
+
|
|
+static int spwr_battery_load_bst(struct spwr_battery_device *bat)
|
|
+{
|
|
+ lockdep_assert_held(&bat->lock);
|
|
+
|
|
+ if (!spwr_battery_present(bat))
|
|
+ return 0;
|
|
+
|
|
+ return ssam_retry(ssam_bat_get_bst, bat->sdev, &bat->bst);
|
|
+}
|
|
+
|
|
+static int spwr_battery_set_alarm_unlocked(struct spwr_battery_device *bat, u32 value)
|
|
+{
|
|
+ __le32 value_le = cpu_to_le32(value);
|
|
+
|
|
+ lockdep_assert_held(&bat->lock);
|
|
+
|
|
+ bat->alarm = value;
|
|
+ return ssam_retry(ssam_bat_set_btp, bat->sdev, &value_le);
|
|
+}
|
|
+
|
|
+static int spwr_battery_update_bst_unlocked(struct spwr_battery_device *bat, bool cached)
|
|
+{
|
|
+ unsigned long cache_deadline = bat->timestamp + msecs_to_jiffies(cache_time);
|
|
+ int status;
|
|
+
|
|
+ lockdep_assert_held(&bat->lock);
|
|
+
|
|
+ if (cached && bat->timestamp && time_is_after_jiffies(cache_deadline))
|
|
+ return 0;
|
|
+
|
|
+ status = spwr_battery_load_sta(bat);
|
|
+ if (status)
|
|
+ return status;
|
|
+
|
|
+ status = spwr_battery_load_bst(bat);
|
|
+ if (status)
|
|
+ return status;
|
|
+
|
|
+ bat->timestamp = jiffies;
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int spwr_battery_update_bst(struct spwr_battery_device *bat, bool cached)
|
|
+{
|
|
+ int status;
|
|
+
|
|
+ mutex_lock(&bat->lock);
|
|
+ status = spwr_battery_update_bst_unlocked(bat, cached);
|
|
+ mutex_unlock(&bat->lock);
|
|
+
|
|
+ return status;
|
|
+}
|
|
+
|
|
+static int spwr_battery_update_bix_unlocked(struct spwr_battery_device *bat)
|
|
+{
|
|
+ int status;
|
|
+
|
|
+ lockdep_assert_held(&bat->lock);
|
|
+
|
|
+ status = spwr_battery_load_sta(bat);
|
|
+ if (status)
|
|
+ return status;
|
|
+
|
|
+ status = spwr_battery_load_bix(bat);
|
|
+ if (status)
|
|
+ return status;
|
|
+
|
|
+ status = spwr_battery_load_bst(bat);
|
|
+ if (status)
|
|
+ return status;
|
|
+
|
|
+ if (bat->bix.revision != SPWR_BIX_REVISION)
|
|
+ dev_warn(&bat->sdev->dev, "unsupported battery revision: %u\n", bat->bix.revision);
|
|
+
|
|
+ bat->timestamp = jiffies;
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static u32 sprw_battery_get_full_cap_safe(struct spwr_battery_device *bat)
|
|
+{
|
|
+ u32 full_cap = get_unaligned_le32(&bat->bix.last_full_charge_cap);
|
|
+
|
|
+ lockdep_assert_held(&bat->lock);
|
|
+
|
|
+ if (full_cap == 0 || full_cap == SPWR_BATTERY_VALUE_UNKNOWN)
|
|
+ full_cap = get_unaligned_le32(&bat->bix.design_cap);
|
|
+
|
|
+ return full_cap;
|
|
+}
|
|
+
|
|
+static bool spwr_battery_is_full(struct spwr_battery_device *bat)
|
|
+{
|
|
+ u32 state = get_unaligned_le32(&bat->bst.state);
|
|
+ u32 full_cap = sprw_battery_get_full_cap_safe(bat);
|
|
+ u32 remaining_cap = get_unaligned_le32(&bat->bst.remaining_cap);
|
|
+
|
|
+ lockdep_assert_held(&bat->lock);
|
|
+
|
|
+ return full_cap != SPWR_BATTERY_VALUE_UNKNOWN && full_cap != 0 &&
|
|
+ remaining_cap != SPWR_BATTERY_VALUE_UNKNOWN &&
|
|
+ remaining_cap >= full_cap &&
|
|
+ state == 0;
|
|
+}
|
|
+
|
|
+static int spwr_battery_recheck_full(struct spwr_battery_device *bat)
|
|
+{
|
|
+ bool present;
|
|
+ u32 unit;
|
|
+ int status;
|
|
+
|
|
+ mutex_lock(&bat->lock);
|
|
+ unit = get_unaligned_le32(&bat->bix.power_unit);
|
|
+ present = spwr_battery_present(bat);
|
|
+
|
|
+ status = spwr_battery_update_bix_unlocked(bat);
|
|
+ if (status)
|
|
+ goto out;
|
|
+
|
|
+ /* If battery has been attached, (re-)initialize alarm. */
|
|
+ if (!present && spwr_battery_present(bat)) {
|
|
+ u32 cap_warn = get_unaligned_le32(&bat->bix.design_cap_warn);
|
|
+
|
|
+ status = spwr_battery_set_alarm_unlocked(bat, cap_warn);
|
|
+ if (status)
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ /*
|
|
+ * Warn if the unit has changed. This is something we genuinely don't
|
|
+ * expect to happen, so make this a big warning. If it does, we'll
|
|
+ * need to add support for it.
|
|
+ */
|
|
+ WARN_ON(unit != get_unaligned_le32(&bat->bix.power_unit));
|
|
+
|
|
+out:
|
|
+ mutex_unlock(&bat->lock);
|
|
+
|
|
+ if (!status)
|
|
+ power_supply_changed(bat->psy);
|
|
+
|
|
+ return status;
|
|
+}
|
|
+
|
|
+static int spwr_battery_recheck_status(struct spwr_battery_device *bat)
|
|
+{
|
|
+ int status;
|
|
+
|
|
+ status = spwr_battery_update_bst(bat, false);
|
|
+ if (!status)
|
|
+ power_supply_changed(bat->psy);
|
|
+
|
|
+ return status;
|
|
+}
|
|
+
|
|
+static u32 spwr_notify_bat(struct ssam_event_notifier *nf, const struct ssam_event *event)
|
|
+{
|
|
+ struct spwr_battery_device *bat = container_of(nf, struct spwr_battery_device, notif);
|
|
+ int status;
|
|
+
|
|
+ dev_dbg(&bat->sdev->dev, "power event (cid = %#04x, iid = %#04x, tid = %#04x)\n",
|
|
+ event->command_id, event->instance_id, event->target_id);
|
|
+
|
|
+ switch (event->command_id) {
|
|
+ case SAM_EVENT_CID_BAT_BIX:
|
|
+ status = spwr_battery_recheck_full(bat);
|
|
+ break;
|
|
+
|
|
+ case SAM_EVENT_CID_BAT_BST:
|
|
+ status = spwr_battery_recheck_status(bat);
|
|
+ break;
|
|
+
|
|
+ case SAM_EVENT_CID_BAT_PROT:
|
|
+ /*
|
|
+ * TODO: Implement support for battery protection status change
|
|
+ * event.
|
|
+ */
|
|
+ status = 0;
|
|
+ break;
|
|
+
|
|
+ case SAM_EVENT_CID_BAT_DPTF:
|
|
+ /*
|
|
+ * TODO: Implement support for DPTF event.
|
|
+ */
|
|
+ status = 0;
|
|
+ break;
|
|
+
|
|
+ default:
|
|
+ return 0;
|
|
+ }
|
|
+
|
|
+ return ssam_notifier_from_errno(status) | SSAM_NOTIF_HANDLED;
|
|
+}
|
|
+
|
|
+static void spwr_battery_update_bst_workfn(struct work_struct *work)
|
|
+{
|
|
+ struct delayed_work *dwork = to_delayed_work(work);
|
|
+ struct spwr_battery_device *bat;
|
|
+ int status;
|
|
+
|
|
+ bat = container_of(dwork, struct spwr_battery_device, update_work);
|
|
+
|
|
+ status = spwr_battery_update_bst(bat, false);
|
|
+ if (status) {
|
|
+ dev_err(&bat->sdev->dev, "failed to update battery state: %d\n", status);
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ power_supply_changed(bat->psy);
|
|
+}
|
|
+
|
|
+static void spwr_external_power_changed(struct power_supply *psy)
|
|
+{
|
|
+ struct spwr_battery_device *bat = power_supply_get_drvdata(psy);
|
|
+
|
|
+ /*
|
|
+ * Handle battery update quirk: When the battery is fully charged (or
|
|
+ * charged up to the limit imposed by the UEFI battery limit) and the
|
|
+ * adapter is plugged in or removed, the EC does not send a separate
|
|
+ * event for the state (charging/discharging) change. Furthermore it
|
|
+ * may take some time until the state is updated on the battery.
|
|
+ * Schedule an update to solve this.
|
|
+ */
|
|
+
|
|
+ schedule_delayed_work(&bat->update_work, SPWR_AC_BAT_UPDATE_DELAY);
|
|
+}
|
|
+
|
|
+
|
|
+/* -- Properties. ----------------------------------------------------------- */
|
|
+
|
|
+static const enum power_supply_property spwr_battery_props_chg[] = {
|
|
+ POWER_SUPPLY_PROP_STATUS,
|
|
+ POWER_SUPPLY_PROP_PRESENT,
|
|
+ POWER_SUPPLY_PROP_TECHNOLOGY,
|
|
+ POWER_SUPPLY_PROP_CYCLE_COUNT,
|
|
+ POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN,
|
|
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
|
|
+ POWER_SUPPLY_PROP_CURRENT_NOW,
|
|
+ POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN,
|
|
+ POWER_SUPPLY_PROP_CHARGE_FULL,
|
|
+ POWER_SUPPLY_PROP_CHARGE_NOW,
|
|
+ POWER_SUPPLY_PROP_CAPACITY,
|
|
+ POWER_SUPPLY_PROP_CAPACITY_LEVEL,
|
|
+ POWER_SUPPLY_PROP_MODEL_NAME,
|
|
+ POWER_SUPPLY_PROP_MANUFACTURER,
|
|
+ POWER_SUPPLY_PROP_SERIAL_NUMBER,
|
|
+};
|
|
+
|
|
+static const enum power_supply_property spwr_battery_props_eng[] = {
|
|
+ POWER_SUPPLY_PROP_STATUS,
|
|
+ POWER_SUPPLY_PROP_PRESENT,
|
|
+ POWER_SUPPLY_PROP_TECHNOLOGY,
|
|
+ POWER_SUPPLY_PROP_CYCLE_COUNT,
|
|
+ POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN,
|
|
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
|
|
+ POWER_SUPPLY_PROP_POWER_NOW,
|
|
+ POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN,
|
|
+ POWER_SUPPLY_PROP_ENERGY_FULL,
|
|
+ POWER_SUPPLY_PROP_ENERGY_NOW,
|
|
+ POWER_SUPPLY_PROP_CAPACITY,
|
|
+ POWER_SUPPLY_PROP_CAPACITY_LEVEL,
|
|
+ POWER_SUPPLY_PROP_MODEL_NAME,
|
|
+ POWER_SUPPLY_PROP_MANUFACTURER,
|
|
+ POWER_SUPPLY_PROP_SERIAL_NUMBER,
|
|
+};
|
|
+
|
|
+static int spwr_battery_prop_status(struct spwr_battery_device *bat)
|
|
+{
|
|
+ u32 state = get_unaligned_le32(&bat->bst.state);
|
|
+ u32 present_rate = get_unaligned_le32(&bat->bst.present_rate);
|
|
+
|
|
+ lockdep_assert_held(&bat->lock);
|
|
+
|
|
+ if (state & SAM_BATTERY_STATE_DISCHARGING)
|
|
+ return POWER_SUPPLY_STATUS_DISCHARGING;
|
|
+
|
|
+ if (state & SAM_BATTERY_STATE_CHARGING)
|
|
+ return POWER_SUPPLY_STATUS_CHARGING;
|
|
+
|
|
+ if (spwr_battery_is_full(bat))
|
|
+ return POWER_SUPPLY_STATUS_FULL;
|
|
+
|
|
+ if (present_rate == 0)
|
|
+ return POWER_SUPPLY_STATUS_NOT_CHARGING;
|
|
+
|
|
+ return POWER_SUPPLY_STATUS_UNKNOWN;
|
|
+}
|
|
+
|
|
+static int spwr_battery_prop_technology(struct spwr_battery_device *bat)
|
|
+{
|
|
+ lockdep_assert_held(&bat->lock);
|
|
+
|
|
+ if (!strcasecmp("NiCd", bat->bix.type))
|
|
+ return POWER_SUPPLY_TECHNOLOGY_NiCd;
|
|
+
|
|
+ if (!strcasecmp("NiMH", bat->bix.type))
|
|
+ return POWER_SUPPLY_TECHNOLOGY_NiMH;
|
|
+
|
|
+ if (!strcasecmp("LION", bat->bix.type))
|
|
+ return POWER_SUPPLY_TECHNOLOGY_LION;
|
|
+
|
|
+ if (!strncasecmp("LI-ION", bat->bix.type, 6))
|
|
+ return POWER_SUPPLY_TECHNOLOGY_LION;
|
|
+
|
|
+ if (!strcasecmp("LiP", bat->bix.type))
|
|
+ return POWER_SUPPLY_TECHNOLOGY_LIPO;
|
|
+
|
|
+ return POWER_SUPPLY_TECHNOLOGY_UNKNOWN;
|
|
+}
|
|
+
|
|
+static int spwr_battery_prop_capacity(struct spwr_battery_device *bat)
|
|
+{
|
|
+ u32 full_cap = sprw_battery_get_full_cap_safe(bat);
|
|
+ u32 remaining_cap = get_unaligned_le32(&bat->bst.remaining_cap);
|
|
+
|
|
+ lockdep_assert_held(&bat->lock);
|
|
+
|
|
+ if (full_cap == 0 || full_cap == SPWR_BATTERY_VALUE_UNKNOWN)
|
|
+ return -ENODATA;
|
|
+
|
|
+ if (remaining_cap == SPWR_BATTERY_VALUE_UNKNOWN)
|
|
+ return -ENODATA;
|
|
+
|
|
+ return remaining_cap * 100 / full_cap;
|
|
+}
|
|
+
|
|
+static int spwr_battery_prop_capacity_level(struct spwr_battery_device *bat)
|
|
+{
|
|
+ u32 state = get_unaligned_le32(&bat->bst.state);
|
|
+ u32 remaining_cap = get_unaligned_le32(&bat->bst.remaining_cap);
|
|
+
|
|
+ lockdep_assert_held(&bat->lock);
|
|
+
|
|
+ if (state & SAM_BATTERY_STATE_CRITICAL)
|
|
+ return POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL;
|
|
+
|
|
+ if (spwr_battery_is_full(bat))
|
|
+ return POWER_SUPPLY_CAPACITY_LEVEL_FULL;
|
|
+
|
|
+ if (remaining_cap <= bat->alarm)
|
|
+ return POWER_SUPPLY_CAPACITY_LEVEL_LOW;
|
|
+
|
|
+ return POWER_SUPPLY_CAPACITY_LEVEL_NORMAL;
|
|
+}
|
|
+
|
|
+static int spwr_battery_get_property(struct power_supply *psy, enum power_supply_property psp,
|
|
+ union power_supply_propval *val)
|
|
+{
|
|
+ struct spwr_battery_device *bat = power_supply_get_drvdata(psy);
|
|
+ u32 value;
|
|
+ int status;
|
|
+
|
|
+ mutex_lock(&bat->lock);
|
|
+
|
|
+ status = spwr_battery_update_bst_unlocked(bat, true);
|
|
+ if (status)
|
|
+ goto out;
|
|
+
|
|
+ /* Abort if battery is not present. */
|
|
+ if (!spwr_battery_present(bat) && psp != POWER_SUPPLY_PROP_PRESENT) {
|
|
+ status = -ENODEV;
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+ switch (psp) {
|
|
+ case POWER_SUPPLY_PROP_STATUS:
|
|
+ val->intval = spwr_battery_prop_status(bat);
|
|
+ break;
|
|
+
|
|
+ case POWER_SUPPLY_PROP_PRESENT:
|
|
+ val->intval = spwr_battery_present(bat);
|
|
+ break;
|
|
+
|
|
+ case POWER_SUPPLY_PROP_TECHNOLOGY:
|
|
+ val->intval = spwr_battery_prop_technology(bat);
|
|
+ break;
|
|
+
|
|
+ case POWER_SUPPLY_PROP_CYCLE_COUNT:
|
|
+ value = get_unaligned_le32(&bat->bix.cycle_count);
|
|
+ if (value != SPWR_BATTERY_VALUE_UNKNOWN)
|
|
+ val->intval = value;
|
|
+ else
|
|
+ status = -ENODATA;
|
|
+ break;
|
|
+
|
|
+ case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN:
|
|
+ value = get_unaligned_le32(&bat->bix.design_voltage);
|
|
+ if (value != SPWR_BATTERY_VALUE_UNKNOWN)
|
|
+ val->intval = value * 1000;
|
|
+ else
|
|
+ status = -ENODATA;
|
|
+ break;
|
|
+
|
|
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
|
|
+ value = get_unaligned_le32(&bat->bst.present_voltage);
|
|
+ if (value != SPWR_BATTERY_VALUE_UNKNOWN)
|
|
+ val->intval = value * 1000;
|
|
+ else
|
|
+ status = -ENODATA;
|
|
+ break;
|
|
+
|
|
+ case POWER_SUPPLY_PROP_CURRENT_NOW:
|
|
+ case POWER_SUPPLY_PROP_POWER_NOW:
|
|
+ value = get_unaligned_le32(&bat->bst.present_rate);
|
|
+ if (value != SPWR_BATTERY_VALUE_UNKNOWN)
|
|
+ val->intval = value * 1000;
|
|
+ else
|
|
+ status = -ENODATA;
|
|
+ break;
|
|
+
|
|
+ case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN:
|
|
+ case POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN:
|
|
+ value = get_unaligned_le32(&bat->bix.design_cap);
|
|
+ if (value != SPWR_BATTERY_VALUE_UNKNOWN)
|
|
+ val->intval = value * 1000;
|
|
+ else
|
|
+ status = -ENODATA;
|
|
+ break;
|
|
+
|
|
+ case POWER_SUPPLY_PROP_CHARGE_FULL:
|
|
+ case POWER_SUPPLY_PROP_ENERGY_FULL:
|
|
+ value = get_unaligned_le32(&bat->bix.last_full_charge_cap);
|
|
+ if (value != SPWR_BATTERY_VALUE_UNKNOWN)
|
|
+ val->intval = value * 1000;
|
|
+ else
|
|
+ status = -ENODATA;
|
|
+ break;
|
|
+
|
|
+ case POWER_SUPPLY_PROP_CHARGE_NOW:
|
|
+ case POWER_SUPPLY_PROP_ENERGY_NOW:
|
|
+ value = get_unaligned_le32(&bat->bst.remaining_cap);
|
|
+ if (value != SPWR_BATTERY_VALUE_UNKNOWN)
|
|
+ val->intval = value * 1000;
|
|
+ else
|
|
+ status = -ENODATA;
|
|
+ break;
|
|
+
|
|
+ case POWER_SUPPLY_PROP_CAPACITY:
|
|
+ val->intval = spwr_battery_prop_capacity(bat);
|
|
+ break;
|
|
+
|
|
+ case POWER_SUPPLY_PROP_CAPACITY_LEVEL:
|
|
+ val->intval = spwr_battery_prop_capacity_level(bat);
|
|
+ break;
|
|
+
|
|
+ case POWER_SUPPLY_PROP_MODEL_NAME:
|
|
+ val->strval = bat->bix.model;
|
|
+ break;
|
|
+
|
|
+ case POWER_SUPPLY_PROP_MANUFACTURER:
|
|
+ val->strval = bat->bix.oem_info;
|
|
+ break;
|
|
+
|
|
+ case POWER_SUPPLY_PROP_SERIAL_NUMBER:
|
|
+ val->strval = bat->bix.serial;
|
|
+ break;
|
|
+
|
|
+ default:
|
|
+ status = -EINVAL;
|
|
+ break;
|
|
+ }
|
|
+
|
|
+out:
|
|
+ mutex_unlock(&bat->lock);
|
|
+ return status;
|
|
+}
|
|
+
|
|
+
|
|
+/* -- Alarm attribute. ------------------------------------------------------ */
|
|
+
|
|
+static ssize_t alarm_show(struct device *dev, struct device_attribute *attr, char *buf)
|
|
+{
|
|
+ struct power_supply *psy = dev_get_drvdata(dev);
|
|
+ struct spwr_battery_device *bat = power_supply_get_drvdata(psy);
|
|
+ int status;
|
|
+
|
|
+ mutex_lock(&bat->lock);
|
|
+ status = sysfs_emit(buf, "%d\n", bat->alarm * 1000);
|
|
+ mutex_unlock(&bat->lock);
|
|
+
|
|
+ return status;
|
|
+}
|
|
+
|
|
+static ssize_t alarm_store(struct device *dev, struct device_attribute *attr, const char *buf,
|
|
+ size_t count)
|
|
+{
|
|
+ struct power_supply *psy = dev_get_drvdata(dev);
|
|
+ struct spwr_battery_device *bat = power_supply_get_drvdata(psy);
|
|
+ unsigned long value;
|
|
+ int status;
|
|
+
|
|
+ status = kstrtoul(buf, 0, &value);
|
|
+ if (status)
|
|
+ return status;
|
|
+
|
|
+ mutex_lock(&bat->lock);
|
|
+
|
|
+ if (!spwr_battery_present(bat)) {
|
|
+ mutex_unlock(&bat->lock);
|
|
+ return -ENODEV;
|
|
+ }
|
|
+
|
|
+ status = spwr_battery_set_alarm_unlocked(bat, value / 1000);
|
|
+ if (status) {
|
|
+ mutex_unlock(&bat->lock);
|
|
+ return status;
|
|
+ }
|
|
+
|
|
+ mutex_unlock(&bat->lock);
|
|
+ return count;
|
|
+}
|
|
+
|
|
+DEVICE_ATTR_RW(alarm);
|
|
+
|
|
+static struct attribute *spwr_battery_attrs[] = {
|
|
+ &dev_attr_alarm.attr,
|
|
+ NULL,
|
|
+};
|
|
+ATTRIBUTE_GROUPS(spwr_battery);
|
|
+
|
|
+
|
|
+/* -- Device setup. --------------------------------------------------------- */
|
|
+
|
|
+static void spwr_battery_init(struct spwr_battery_device *bat, struct ssam_device *sdev,
|
|
+ struct ssam_event_registry registry, const char *name)
|
|
+{
|
|
+ mutex_init(&bat->lock);
|
|
+ strncpy(bat->name, name, ARRAY_SIZE(bat->name) - 1);
|
|
+
|
|
+ bat->sdev = sdev;
|
|
+
|
|
+ bat->notif.base.priority = 1;
|
|
+ bat->notif.base.fn = spwr_notify_bat;
|
|
+ bat->notif.event.reg = registry;
|
|
+ bat->notif.event.id.target_category = sdev->uid.category;
|
|
+ bat->notif.event.id.instance = 0;
|
|
+ bat->notif.event.mask = SSAM_EVENT_MASK_STRICT;
|
|
+ bat->notif.event.flags = SSAM_EVENT_SEQUENCED;
|
|
+
|
|
+ bat->psy_desc.name = bat->name;
|
|
+ bat->psy_desc.type = POWER_SUPPLY_TYPE_BATTERY;
|
|
+ bat->psy_desc.get_property = spwr_battery_get_property;
|
|
+
|
|
+ INIT_DELAYED_WORK(&bat->update_work, spwr_battery_update_bst_workfn);
|
|
+}
|
|
+
|
|
+static int spwr_battery_register(struct spwr_battery_device *bat)
|
|
+{
|
|
+ struct power_supply_config psy_cfg = {};
|
|
+ __le32 sta;
|
|
+ int status;
|
|
+
|
|
+ /* Make sure the device is there and functioning properly. */
|
|
+ status = ssam_retry(ssam_bat_get_sta, bat->sdev, &sta);
|
|
+ if (status)
|
|
+ return status;
|
|
+
|
|
+ if ((le32_to_cpu(sta) & SAM_BATTERY_STA_OK) != SAM_BATTERY_STA_OK)
|
|
+ return -ENODEV;
|
|
+
|
|
+ /* Satisfy lockdep although we are in an exclusive context here. */
|
|
+ mutex_lock(&bat->lock);
|
|
+
|
|
+ status = spwr_battery_update_bix_unlocked(bat);
|
|
+ if (status) {
|
|
+ mutex_unlock(&bat->lock);
|
|
+ return status;
|
|
+ }
|
|
+
|
|
+ if (spwr_battery_present(bat)) {
|
|
+ u32 cap_warn = get_unaligned_le32(&bat->bix.design_cap_warn);
|
|
+
|
|
+ status = spwr_battery_set_alarm_unlocked(bat, cap_warn);
|
|
+ if (status) {
|
|
+ mutex_unlock(&bat->lock);
|
|
+ return status;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ mutex_unlock(&bat->lock);
|
|
+
|
|
+ bat->psy_desc.external_power_changed = spwr_external_power_changed;
|
|
+
|
|
+ switch (get_unaligned_le32(&bat->bix.power_unit)) {
|
|
+ case SAM_BATTERY_POWER_UNIT_mW:
|
|
+ bat->psy_desc.properties = spwr_battery_props_eng;
|
|
+ bat->psy_desc.num_properties = ARRAY_SIZE(spwr_battery_props_eng);
|
|
+ break;
|
|
+
|
|
+ case SAM_BATTERY_POWER_UNIT_mA:
|
|
+ bat->psy_desc.properties = spwr_battery_props_chg;
|
|
+ bat->psy_desc.num_properties = ARRAY_SIZE(spwr_battery_props_chg);
|
|
+ break;
|
|
+
|
|
+ default:
|
|
+ dev_err(&bat->sdev->dev, "unsupported battery power unit: %u\n",
|
|
+ get_unaligned_le32(&bat->bix.power_unit));
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ psy_cfg.drv_data = bat;
|
|
+ psy_cfg.attr_grp = spwr_battery_groups;
|
|
+
|
|
+ bat->psy = devm_power_supply_register(&bat->sdev->dev, &bat->psy_desc, &psy_cfg);
|
|
+ if (IS_ERR(bat->psy))
|
|
+ return PTR_ERR(bat->psy);
|
|
+
|
|
+ return ssam_notifier_register(bat->sdev->ctrl, &bat->notif);
|
|
+}
|
|
+
|
|
+
|
|
+/* -- Driver setup. --------------------------------------------------------- */
|
|
+
|
|
+static int __maybe_unused surface_battery_resume(struct device *dev)
|
|
+{
|
|
+ return spwr_battery_recheck_full(dev_get_drvdata(dev));
|
|
+}
|
|
+SIMPLE_DEV_PM_OPS(surface_battery_pm_ops, NULL, surface_battery_resume);
|
|
+
|
|
+static int surface_battery_probe(struct ssam_device *sdev)
|
|
+{
|
|
+ const struct spwr_psy_properties *p;
|
|
+ struct spwr_battery_device *bat;
|
|
+
|
|
+ p = ssam_device_get_match_data(sdev);
|
|
+ if (!p)
|
|
+ return -ENODEV;
|
|
+
|
|
+ bat = devm_kzalloc(&sdev->dev, sizeof(*bat), GFP_KERNEL);
|
|
+ if (!bat)
|
|
+ return -ENOMEM;
|
|
+
|
|
+ spwr_battery_init(bat, sdev, p->registry, p->name);
|
|
+ ssam_device_set_drvdata(sdev, bat);
|
|
+
|
|
+ return spwr_battery_register(bat);
|
|
+}
|
|
+
|
|
+static void surface_battery_remove(struct ssam_device *sdev)
|
|
+{
|
|
+ struct spwr_battery_device *bat = ssam_device_get_drvdata(sdev);
|
|
+
|
|
+ ssam_notifier_unregister(sdev->ctrl, &bat->notif);
|
|
+ cancel_delayed_work_sync(&bat->update_work);
|
|
+}
|
|
+
|
|
+static const struct spwr_psy_properties spwr_psy_props_bat1 = {
|
|
+ .name = "BAT1",
|
|
+ .registry = SSAM_EVENT_REGISTRY_SAM,
|
|
+};
|
|
+
|
|
+static const struct spwr_psy_properties spwr_psy_props_bat2_sb3 = {
|
|
+ .name = "BAT2",
|
|
+ .registry = SSAM_EVENT_REGISTRY_KIP,
|
|
+};
|
|
+
|
|
+static const struct ssam_device_id surface_battery_match[] = {
|
|
+ { SSAM_SDEV(BAT, 0x01, 0x01, 0x00), (unsigned long)&spwr_psy_props_bat1 },
|
|
+ { SSAM_SDEV(BAT, 0x02, 0x01, 0x00), (unsigned long)&spwr_psy_props_bat2_sb3 },
|
|
+ { },
|
|
+};
|
|
+MODULE_DEVICE_TABLE(ssam, surface_battery_match);
|
|
+
|
|
+static struct ssam_device_driver surface_battery_driver = {
|
|
+ .probe = surface_battery_probe,
|
|
+ .remove = surface_battery_remove,
|
|
+ .match_table = surface_battery_match,
|
|
+ .driver = {
|
|
+ .name = "surface_battery",
|
|
+ .pm = &surface_battery_pm_ops,
|
|
+ .probe_type = PROBE_PREFER_ASYNCHRONOUS,
|
|
+ },
|
|
+};
|
|
+module_ssam_device_driver(surface_battery_driver);
|
|
+
|
|
+MODULE_AUTHOR("Maximilian Luz <luzmaximilian@gmail.com>");
|
|
+MODULE_DESCRIPTION("Battery driver for Surface System Aggregator Module");
|
|
+MODULE_LICENSE("GPL");
|
|
--
|
|
2.32.0
|
|
|
|
From b1345bcac21c00c2e679203a6ffcefc03d066fc9 Mon Sep 17 00:00:00 2001
|
|
From: Maximilian Luz <luzmaximilian@gmail.com>
|
|
Date: Tue, 6 Apr 2021 01:41:26 +0200
|
|
Subject: [PATCH] power: supply: Add AC driver for Surface Aggregator Module
|
|
|
|
On newer Microsoft Surface models (specifically 7th-generation, i.e.
|
|
Surface Pro 7, Surface Book 3, Surface Laptop 3, and Surface Laptop Go),
|
|
battery and AC status/information is no longer handled via standard ACPI
|
|
devices, but instead directly via the Surface System Aggregator Module
|
|
(SSAM), i.e. the embedded controller on those devices.
|
|
|
|
While on previous generation models, AC status is also handled via SSAM,
|
|
an ACPI shim was present to translate the standard ACPI AC interface to
|
|
SSAM requests. The SSAM interface itself, which is modeled closely after
|
|
the ACPI interface, has not changed.
|
|
|
|
This commit introduces a new SSAM client device driver to support AC
|
|
status/information via the aforementioned interface on said Surface
|
|
models.
|
|
|
|
Signed-off-by: Maximilian Luz <luzmaximilian@gmail.com>
|
|
Signed-off-by: Sebastian Reichel <sebastian.reichel@collabora.com>
|
|
Patchset: surface-sam
|
|
---
|
|
MAINTAINERS | 1 +
|
|
drivers/power/supply/Kconfig | 16 ++
|
|
drivers/power/supply/Makefile | 1 +
|
|
drivers/power/supply/surface_charger.c | 282 +++++++++++++++++++++++++
|
|
4 files changed, 300 insertions(+)
|
|
create mode 100644 drivers/power/supply/surface_charger.c
|
|
|
|
diff --git a/MAINTAINERS b/MAINTAINERS
|
|
index 7ee93b732270..710617e26f3e 100644
|
|
--- a/MAINTAINERS
|
|
+++ b/MAINTAINERS
|
|
@@ -11874,6 +11874,7 @@ L: linux-pm@vger.kernel.org
|
|
L: platform-driver-x86@vger.kernel.org
|
|
S: Maintained
|
|
F: drivers/power/supply/surface_battery.c
|
|
+F: drivers/power/supply/surface_charger.c
|
|
|
|
MICROSOFT SURFACE DTX DRIVER
|
|
M: Maximilian Luz <luzmaximilian@gmail.com>
|
|
diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig
|
|
index cebeff10d543..91f7cf425ac9 100644
|
|
--- a/drivers/power/supply/Kconfig
|
|
+++ b/drivers/power/supply/Kconfig
|
|
@@ -817,4 +817,20 @@ config BATTERY_SURFACE
|
|
Microsoft Surface devices, i.e. Surface Pro 7, Surface Laptop 3,
|
|
Surface Book 3, and Surface Laptop Go.
|
|
|
|
+config CHARGER_SURFACE
|
|
+ tristate "AC driver for 7th-generation Microsoft Surface devices"
|
|
+ depends on SURFACE_AGGREGATOR_REGISTRY
|
|
+ help
|
|
+ Driver for AC devices connected via/managed by the Surface System
|
|
+ Aggregator Module (SSAM).
|
|
+
|
|
+ This driver provides AC-information and -status support for Surface
|
|
+ devices where said data is not exposed via the standard ACPI devices.
|
|
+ On those models (7th-generation), AC-information is instead handled
|
|
+ directly via a SSAM client device and this driver.
|
|
+
|
|
+ Say M or Y here to include AC status support for 7th-generation
|
|
+ Microsoft Surface devices, i.e. Surface Pro 7, Surface Laptop 3,
|
|
+ Surface Book 3, and Surface Laptop Go.
|
|
+
|
|
endif # POWER_SUPPLY
|
|
diff --git a/drivers/power/supply/Makefile b/drivers/power/supply/Makefile
|
|
index 134041538d2c..a7309a3d1a47 100644
|
|
--- a/drivers/power/supply/Makefile
|
|
+++ b/drivers/power/supply/Makefile
|
|
@@ -102,3 +102,4 @@ obj-$(CONFIG_CHARGER_WILCO) += wilco-charger.o
|
|
obj-$(CONFIG_RN5T618_POWER) += rn5t618_power.o
|
|
obj-$(CONFIG_BATTERY_ACER_A500) += acer_a500_battery.o
|
|
obj-$(CONFIG_BATTERY_SURFACE) += surface_battery.o
|
|
+obj-$(CONFIG_CHARGER_SURFACE) += surface_charger.o
|
|
diff --git a/drivers/power/supply/surface_charger.c b/drivers/power/supply/surface_charger.c
|
|
new file mode 100644
|
|
index 000000000000..c2dd7e604d14
|
|
--- /dev/null
|
|
+++ b/drivers/power/supply/surface_charger.c
|
|
@@ -0,0 +1,282 @@
|
|
+// SPDX-License-Identifier: GPL-2.0+
|
|
+/*
|
|
+ * AC driver for 7th-generation Microsoft Surface devices via Surface System
|
|
+ * Aggregator Module (SSAM).
|
|
+ *
|
|
+ * Copyright (C) 2019-2021 Maximilian Luz <luzmaximilian@gmail.com>
|
|
+ */
|
|
+
|
|
+#include <asm/unaligned.h>
|
|
+#include <linux/kernel.h>
|
|
+#include <linux/module.h>
|
|
+#include <linux/mutex.h>
|
|
+#include <linux/power_supply.h>
|
|
+#include <linux/types.h>
|
|
+
|
|
+#include <linux/surface_aggregator/device.h>
|
|
+
|
|
+
|
|
+/* -- SAM interface. -------------------------------------------------------- */
|
|
+
|
|
+enum sam_event_cid_bat {
|
|
+ SAM_EVENT_CID_BAT_ADP = 0x17,
|
|
+};
|
|
+
|
|
+enum sam_battery_sta {
|
|
+ SAM_BATTERY_STA_OK = 0x0f,
|
|
+ SAM_BATTERY_STA_PRESENT = 0x10,
|
|
+};
|
|
+
|
|
+/* Get battery status (_STA). */
|
|
+SSAM_DEFINE_SYNC_REQUEST_CL_R(ssam_bat_get_sta, __le32, {
|
|
+ .target_category = SSAM_SSH_TC_BAT,
|
|
+ .command_id = 0x01,
|
|
+});
|
|
+
|
|
+/* Get platform power source for battery (_PSR / DPTF PSRC). */
|
|
+SSAM_DEFINE_SYNC_REQUEST_CL_R(ssam_bat_get_psrc, __le32, {
|
|
+ .target_category = SSAM_SSH_TC_BAT,
|
|
+ .command_id = 0x0d,
|
|
+});
|
|
+
|
|
+
|
|
+/* -- Device structures. ---------------------------------------------------- */
|
|
+
|
|
+struct spwr_psy_properties {
|
|
+ const char *name;
|
|
+ struct ssam_event_registry registry;
|
|
+};
|
|
+
|
|
+struct spwr_ac_device {
|
|
+ struct ssam_device *sdev;
|
|
+
|
|
+ char name[32];
|
|
+ struct power_supply *psy;
|
|
+ struct power_supply_desc psy_desc;
|
|
+
|
|
+ struct ssam_event_notifier notif;
|
|
+
|
|
+ struct mutex lock; /* Guards access to state below. */
|
|
+
|
|
+ __le32 state;
|
|
+};
|
|
+
|
|
+
|
|
+/* -- State management. ----------------------------------------------------- */
|
|
+
|
|
+static int spwr_ac_update_unlocked(struct spwr_ac_device *ac)
|
|
+{
|
|
+ u32 old = ac->state;
|
|
+ int status;
|
|
+
|
|
+ lockdep_assert_held(&ac->lock);
|
|
+
|
|
+ status = ssam_retry(ssam_bat_get_psrc, ac->sdev, &ac->state);
|
|
+ if (status < 0)
|
|
+ return status;
|
|
+
|
|
+ return old != ac->state;
|
|
+}
|
|
+
|
|
+static int spwr_ac_update(struct spwr_ac_device *ac)
|
|
+{
|
|
+ int status;
|
|
+
|
|
+ mutex_lock(&ac->lock);
|
|
+ status = spwr_ac_update_unlocked(ac);
|
|
+ mutex_unlock(&ac->lock);
|
|
+
|
|
+ return status;
|
|
+}
|
|
+
|
|
+static int spwr_ac_recheck(struct spwr_ac_device *ac)
|
|
+{
|
|
+ int status;
|
|
+
|
|
+ status = spwr_ac_update(ac);
|
|
+ if (status > 0)
|
|
+ power_supply_changed(ac->psy);
|
|
+
|
|
+ return status >= 0 ? 0 : status;
|
|
+}
|
|
+
|
|
+static u32 spwr_notify_ac(struct ssam_event_notifier *nf, const struct ssam_event *event)
|
|
+{
|
|
+ struct spwr_ac_device *ac;
|
|
+ int status;
|
|
+
|
|
+ ac = container_of(nf, struct spwr_ac_device, notif);
|
|
+
|
|
+ dev_dbg(&ac->sdev->dev, "power event (cid = %#04x, iid = %#04x, tid = %#04x)\n",
|
|
+ event->command_id, event->instance_id, event->target_id);
|
|
+
|
|
+ /*
|
|
+ * Allow events of all targets/instances here. Global adapter status
|
|
+ * seems to be handled via target=1 and instance=1, but events are
|
|
+ * reported on all targets/instances in use.
|
|
+ *
|
|
+ * While it should be enough to just listen on 1/1, listen everywhere to
|
|
+ * make sure we don't miss anything.
|
|
+ */
|
|
+
|
|
+ switch (event->command_id) {
|
|
+ case SAM_EVENT_CID_BAT_ADP:
|
|
+ status = spwr_ac_recheck(ac);
|
|
+ return ssam_notifier_from_errno(status) | SSAM_NOTIF_HANDLED;
|
|
+
|
|
+ default:
|
|
+ return 0;
|
|
+ }
|
|
+}
|
|
+
|
|
+
|
|
+/* -- Properties. ----------------------------------------------------------- */
|
|
+
|
|
+static const enum power_supply_property spwr_ac_props[] = {
|
|
+ POWER_SUPPLY_PROP_ONLINE,
|
|
+};
|
|
+
|
|
+static int spwr_ac_get_property(struct power_supply *psy, enum power_supply_property psp,
|
|
+ union power_supply_propval *val)
|
|
+{
|
|
+ struct spwr_ac_device *ac = power_supply_get_drvdata(psy);
|
|
+ int status;
|
|
+
|
|
+ mutex_lock(&ac->lock);
|
|
+
|
|
+ status = spwr_ac_update_unlocked(ac);
|
|
+ if (status)
|
|
+ goto out;
|
|
+
|
|
+ switch (psp) {
|
|
+ case POWER_SUPPLY_PROP_ONLINE:
|
|
+ val->intval = !!le32_to_cpu(ac->state);
|
|
+ break;
|
|
+
|
|
+ default:
|
|
+ status = -EINVAL;
|
|
+ goto out;
|
|
+ }
|
|
+
|
|
+out:
|
|
+ mutex_unlock(&ac->lock);
|
|
+ return status;
|
|
+}
|
|
+
|
|
+
|
|
+/* -- Device setup. --------------------------------------------------------- */
|
|
+
|
|
+static char *battery_supplied_to[] = {
|
|
+ "BAT1",
|
|
+ "BAT2",
|
|
+};
|
|
+
|
|
+static void spwr_ac_init(struct spwr_ac_device *ac, struct ssam_device *sdev,
|
|
+ struct ssam_event_registry registry, const char *name)
|
|
+{
|
|
+ mutex_init(&ac->lock);
|
|
+ strncpy(ac->name, name, ARRAY_SIZE(ac->name) - 1);
|
|
+
|
|
+ ac->sdev = sdev;
|
|
+
|
|
+ ac->notif.base.priority = 1;
|
|
+ ac->notif.base.fn = spwr_notify_ac;
|
|
+ ac->notif.event.reg = registry;
|
|
+ ac->notif.event.id.target_category = sdev->uid.category;
|
|
+ ac->notif.event.id.instance = 0;
|
|
+ ac->notif.event.mask = SSAM_EVENT_MASK_NONE;
|
|
+ ac->notif.event.flags = SSAM_EVENT_SEQUENCED;
|
|
+
|
|
+ ac->psy_desc.name = ac->name;
|
|
+ ac->psy_desc.type = POWER_SUPPLY_TYPE_MAINS;
|
|
+ ac->psy_desc.properties = spwr_ac_props;
|
|
+ ac->psy_desc.num_properties = ARRAY_SIZE(spwr_ac_props);
|
|
+ ac->psy_desc.get_property = spwr_ac_get_property;
|
|
+}
|
|
+
|
|
+static int spwr_ac_register(struct spwr_ac_device *ac)
|
|
+{
|
|
+ struct power_supply_config psy_cfg = {};
|
|
+ __le32 sta;
|
|
+ int status;
|
|
+
|
|
+ /* Make sure the device is there and functioning properly. */
|
|
+ status = ssam_retry(ssam_bat_get_sta, ac->sdev, &sta);
|
|
+ if (status)
|
|
+ return status;
|
|
+
|
|
+ if ((le32_to_cpu(sta) & SAM_BATTERY_STA_OK) != SAM_BATTERY_STA_OK)
|
|
+ return -ENODEV;
|
|
+
|
|
+ psy_cfg.drv_data = ac;
|
|
+ psy_cfg.supplied_to = battery_supplied_to;
|
|
+ psy_cfg.num_supplicants = ARRAY_SIZE(battery_supplied_to);
|
|
+
|
|
+ ac->psy = devm_power_supply_register(&ac->sdev->dev, &ac->psy_desc, &psy_cfg);
|
|
+ if (IS_ERR(ac->psy))
|
|
+ return PTR_ERR(ac->psy);
|
|
+
|
|
+ return ssam_notifier_register(ac->sdev->ctrl, &ac->notif);
|
|
+}
|
|
+
|
|
+
|
|
+/* -- Driver setup. --------------------------------------------------------- */
|
|
+
|
|
+static int __maybe_unused surface_ac_resume(struct device *dev)
|
|
+{
|
|
+ return spwr_ac_recheck(dev_get_drvdata(dev));
|
|
+}
|
|
+SIMPLE_DEV_PM_OPS(surface_ac_pm_ops, NULL, surface_ac_resume);
|
|
+
|
|
+static int surface_ac_probe(struct ssam_device *sdev)
|
|
+{
|
|
+ const struct spwr_psy_properties *p;
|
|
+ struct spwr_ac_device *ac;
|
|
+
|
|
+ p = ssam_device_get_match_data(sdev);
|
|
+ if (!p)
|
|
+ return -ENODEV;
|
|
+
|
|
+ ac = devm_kzalloc(&sdev->dev, sizeof(*ac), GFP_KERNEL);
|
|
+ if (!ac)
|
|
+ return -ENOMEM;
|
|
+
|
|
+ spwr_ac_init(ac, sdev, p->registry, p->name);
|
|
+ ssam_device_set_drvdata(sdev, ac);
|
|
+
|
|
+ return spwr_ac_register(ac);
|
|
+}
|
|
+
|
|
+static void surface_ac_remove(struct ssam_device *sdev)
|
|
+{
|
|
+ struct spwr_ac_device *ac = ssam_device_get_drvdata(sdev);
|
|
+
|
|
+ ssam_notifier_unregister(sdev->ctrl, &ac->notif);
|
|
+}
|
|
+
|
|
+static const struct spwr_psy_properties spwr_psy_props_adp1 = {
|
|
+ .name = "ADP1",
|
|
+ .registry = SSAM_EVENT_REGISTRY_SAM,
|
|
+};
|
|
+
|
|
+static const struct ssam_device_id surface_ac_match[] = {
|
|
+ { SSAM_SDEV(BAT, 0x01, 0x01, 0x01), (unsigned long)&spwr_psy_props_adp1 },
|
|
+ { },
|
|
+};
|
|
+MODULE_DEVICE_TABLE(ssam, surface_ac_match);
|
|
+
|
|
+static struct ssam_device_driver surface_ac_driver = {
|
|
+ .probe = surface_ac_probe,
|
|
+ .remove = surface_ac_remove,
|
|
+ .match_table = surface_ac_match,
|
|
+ .driver = {
|
|
+ .name = "surface_ac",
|
|
+ .pm = &surface_ac_pm_ops,
|
|
+ .probe_type = PROBE_PREFER_ASYNCHRONOUS,
|
|
+ },
|
|
+};
|
|
+module_ssam_device_driver(surface_ac_driver);
|
|
+
|
|
+MODULE_AUTHOR("Maximilian Luz <luzmaximilian@gmail.com>");
|
|
+MODULE_DESCRIPTION("AC driver for Surface System Aggregator Module");
|
|
+MODULE_LICENSE("GPL");
|
|
--
|
|
2.32.0
|
|
|
|
From e32231d4ac303912945147b70d733d21ea1b1b18 Mon Sep 17 00:00:00 2001
|
|
From: Qiheng Lin <linqiheng@huawei.com>
|
|
Date: Sat, 10 Apr 2021 12:12:46 +0800
|
|
Subject: [PATCH] power: supply: surface-battery: Make some symbols static
|
|
|
|
The sparse tool complains as follows:
|
|
|
|
drivers/power/supply/surface_battery.c:700:1: warning:
|
|
symbol 'dev_attr_alarm' was not declared. Should it be static?
|
|
drivers/power/supply/surface_battery.c:805:1: warning:
|
|
symbol 'surface_battery_pm_ops' was not declared. Should it be static?
|
|
|
|
This symbol is not used outside of surface_battery.c, so this
|
|
commit marks it static.
|
|
|
|
Reported-by: Hulk Robot <hulkci@huawei.com>
|
|
Signed-off-by: Qiheng Lin <linqiheng@huawei.com>
|
|
Acked-by: Maximilian Luz <luzmaximilian@gmail.com>
|
|
Signed-off-by: Sebastian Reichel <sebastian.reichel@collabora.com>
|
|
Patchset: surface-sam
|
|
---
|
|
drivers/power/supply/surface_battery.c | 4 ++--
|
|
1 file changed, 2 insertions(+), 2 deletions(-)
|
|
|
|
diff --git a/drivers/power/supply/surface_battery.c b/drivers/power/supply/surface_battery.c
|
|
index 4116dd839ecd..7efa431a62b2 100644
|
|
--- a/drivers/power/supply/surface_battery.c
|
|
+++ b/drivers/power/supply/surface_battery.c
|
|
@@ -697,7 +697,7 @@ static ssize_t alarm_store(struct device *dev, struct device_attribute *attr, co
|
|
return count;
|
|
}
|
|
|
|
-DEVICE_ATTR_RW(alarm);
|
|
+static DEVICE_ATTR_RW(alarm);
|
|
|
|
static struct attribute *spwr_battery_attrs[] = {
|
|
&dev_attr_alarm.attr,
|
|
@@ -802,7 +802,7 @@ static int __maybe_unused surface_battery_resume(struct device *dev)
|
|
{
|
|
return spwr_battery_recheck_full(dev_get_drvdata(dev));
|
|
}
|
|
-SIMPLE_DEV_PM_OPS(surface_battery_pm_ops, NULL, surface_battery_resume);
|
|
+static SIMPLE_DEV_PM_OPS(surface_battery_pm_ops, NULL, surface_battery_resume);
|
|
|
|
static int surface_battery_probe(struct ssam_device *sdev)
|
|
{
|
|
--
|
|
2.32.0
|
|
|
|
From ae4baf9def6a94953b826b6cacb00b4ff72457d4 Mon Sep 17 00:00:00 2001
|
|
From: Qiheng Lin <linqiheng@huawei.com>
|
|
Date: Sat, 10 Apr 2021 12:12:49 +0800
|
|
Subject: [PATCH] power: supply: surface-charger: Make symbol
|
|
'surface_ac_pm_ops' static
|
|
|
|
The sparse tool complains as follows:
|
|
|
|
drivers/power/supply/surface_charger.c:229:1: warning:
|
|
symbol 'surface_ac_pm_ops' was not declared. Should it be static?
|
|
|
|
This symbol is not used outside of surface_charger.c, so this
|
|
commit marks it static.
|
|
|
|
Reported-by: Hulk Robot <hulkci@huawei.com>
|
|
Signed-off-by: Qiheng Lin <linqiheng@huawei.com>
|
|
Acked-by: Maximilian Luz <luzmaximilian@gmail.com>
|
|
Signed-off-by: Sebastian Reichel <sebastian.reichel@collabora.com>
|
|
Patchset: surface-sam
|
|
---
|
|
drivers/power/supply/surface_charger.c | 2 +-
|
|
1 file changed, 1 insertion(+), 1 deletion(-)
|
|
|
|
diff --git a/drivers/power/supply/surface_charger.c b/drivers/power/supply/surface_charger.c
|
|
index c2dd7e604d14..81a5b79822c9 100644
|
|
--- a/drivers/power/supply/surface_charger.c
|
|
+++ b/drivers/power/supply/surface_charger.c
|
|
@@ -226,7 +226,7 @@ static int __maybe_unused surface_ac_resume(struct device *dev)
|
|
{
|
|
return spwr_ac_recheck(dev_get_drvdata(dev));
|
|
}
|
|
-SIMPLE_DEV_PM_OPS(surface_ac_pm_ops, NULL, surface_ac_resume);
|
|
+static SIMPLE_DEV_PM_OPS(surface_ac_pm_ops, NULL, surface_ac_resume);
|
|
|
|
static int surface_ac_probe(struct ssam_device *sdev)
|
|
{
|
|
--
|
|
2.32.0
|
|
|
|
From cf2597e96e97d4eb9dfb19ec4e6e3fe8c4226102 Mon Sep 17 00:00:00 2001
|
|
From: Maximilian Luz <luzmaximilian@gmail.com>
|
|
Date: Tue, 4 May 2021 20:00:46 +0200
|
|
Subject: [PATCH] power: supply: surface_battery: Fix battery event handling
|
|
|
|
The battery subsystem of the Surface Aggregator Module EC requires us to
|
|
register the battery notifier with instance ID 0. However, battery
|
|
events are actually sent with the instance ID corresponding to the
|
|
device, which is nonzero. Thus, the strict-matching approach doesn't
|
|
work here and will discard events that the driver is expected to handle.
|
|
|
|
To fix this we have to fall back on notifier matching by target-category
|
|
only and have to manually check the instance ID in the notifier
|
|
callback.
|
|
|
|
Fixes: 167f77f7d0b3 ("power: supply: Add battery driver for Surface Aggregator Module")
|
|
Signed-off-by: Maximilian Luz <luzmaximilian@gmail.com>
|
|
Patchset: surface-sam
|
|
---
|
|
drivers/power/supply/surface_battery.c | 14 ++++++++++++--
|
|
1 file changed, 12 insertions(+), 2 deletions(-)
|
|
|
|
diff --git a/drivers/power/supply/surface_battery.c b/drivers/power/supply/surface_battery.c
|
|
index 7efa431a62b2..5ec2e6bb2465 100644
|
|
--- a/drivers/power/supply/surface_battery.c
|
|
+++ b/drivers/power/supply/surface_battery.c
|
|
@@ -345,6 +345,16 @@ static u32 spwr_notify_bat(struct ssam_event_notifier *nf, const struct ssam_eve
|
|
struct spwr_battery_device *bat = container_of(nf, struct spwr_battery_device, notif);
|
|
int status;
|
|
|
|
+ /*
|
|
+ * We cannot use strict matching when registering the notifier as the
|
|
+ * EC expects us to register it against instance ID 0. Strict matching
|
|
+ * would thus drop events, as those may have non-zero instance IDs in
|
|
+ * this subsystem. So we need to check the instance ID of the event
|
|
+ * here manually.
|
|
+ */
|
|
+ if (event->instance_id != bat->sdev->uid.instance)
|
|
+ return 0;
|
|
+
|
|
dev_dbg(&bat->sdev->dev, "power event (cid = %#04x, iid = %#04x, tid = %#04x)\n",
|
|
event->command_id, event->instance_id, event->target_id);
|
|
|
|
@@ -720,8 +730,8 @@ static void spwr_battery_init(struct spwr_battery_device *bat, struct ssam_devic
|
|
bat->notif.base.fn = spwr_notify_bat;
|
|
bat->notif.event.reg = registry;
|
|
bat->notif.event.id.target_category = sdev->uid.category;
|
|
- bat->notif.event.id.instance = 0;
|
|
- bat->notif.event.mask = SSAM_EVENT_MASK_STRICT;
|
|
+ bat->notif.event.id.instance = 0; /* need to register with instance 0 */
|
|
+ bat->notif.event.mask = SSAM_EVENT_MASK_TARGET;
|
|
bat->notif.event.flags = SSAM_EVENT_SEQUENCED;
|
|
|
|
bat->psy_desc.name = bat->name;
|
|
--
|
|
2.32.0
|
|
|
|
From 12c6919dc79bbd4d6782dd089460276b0072e4ba Mon Sep 17 00:00:00 2001
|
|
From: Maximilian Luz <luzmaximilian@gmail.com>
|
|
Date: Tue, 11 May 2021 11:24:21 +0200
|
|
Subject: [PATCH] power: supply: surface-charger: Fix type of integer variable
|
|
|
|
The ac->state field is __le32, not u32. So change the variable we're
|
|
temporarily storing it in to __le32 as well.
|
|
|
|
Reported-by: kernel test robot <lkp@intel.com>
|
|
Fixes: e61ffb344591 ("power: supply: Add AC driver for Surface Aggregator Module")
|
|
Signed-off-by: Maximilian Luz <luzmaximilian@gmail.com>
|
|
Patchset: surface-sam
|
|
---
|
|
drivers/power/supply/surface_charger.c | 2 +-
|
|
1 file changed, 1 insertion(+), 1 deletion(-)
|
|
|
|
diff --git a/drivers/power/supply/surface_charger.c b/drivers/power/supply/surface_charger.c
|
|
index 81a5b79822c9..a060c36c7766 100644
|
|
--- a/drivers/power/supply/surface_charger.c
|
|
+++ b/drivers/power/supply/surface_charger.c
|
|
@@ -66,7 +66,7 @@ struct spwr_ac_device {
|
|
|
|
static int spwr_ac_update_unlocked(struct spwr_ac_device *ac)
|
|
{
|
|
- u32 old = ac->state;
|
|
+ __le32 old = ac->state;
|
|
int status;
|
|
|
|
lockdep_assert_held(&ac->lock);
|
|
--
|
|
2.32.0
|
|
|
|
From fcab68d61af80d7a72ac2c920968f13d6ea9af16 Mon Sep 17 00:00:00 2001
|
|
From: Maximilian Luz <luzmaximilian@gmail.com>
|
|
Date: Wed, 2 Jun 2021 18:27:21 +0200
|
|
Subject: [PATCH] platform/surface: aggregator: Allow registering notifiers
|
|
without enabling events
|
|
|
|
Currently, each SSAM event notifier is directly tied to one group of
|
|
events. This makes sense as registering a notifier will automatically
|
|
take care of enabling the corresponding event group and normally drivers
|
|
only need notifications for a very limited number of events, associated
|
|
with different callbacks for each group.
|
|
|
|
However, there are rare cases, especially for debugging, when we want to
|
|
get notifications for a whole event target category instead of just a
|
|
single group of events in that category. Registering multiple notifiers,
|
|
i.e. one per group, may be infeasible due to two issues: a) we might not
|
|
know every event enable/disable specification as some events are
|
|
auto-enabled by the EC and b) forwarding this to the same callback will
|
|
lead to duplicate events as we might not know the full event
|
|
specification to perform the appropriate filtering.
|
|
|
|
This commit introduces observer-notifiers, which are notifiers that are
|
|
not tied to a specific event group and do not attempt to manage any
|
|
events. In other words, they can be registered without enabling any
|
|
event group or incrementing the corresponding reference count and just
|
|
act as silent observers, listening to all currently/previously enabled
|
|
events based on their match-specification.
|
|
|
|
Essentially, this allows us to register one single notifier for a full
|
|
event target category, meaning that we can process all events of that
|
|
target category in a single callback without duplication. Specifically,
|
|
this will be used in the cdev debug interface to forward events to
|
|
user-space via a device file from which the events can be read.
|
|
|
|
Signed-off-by: Maximilian Luz <luzmaximilian@gmail.com>
|
|
Patchset: surface-sam
|
|
---
|
|
.../platform/surface/aggregator/controller.c | 69 +++++++++++--------
|
|
include/linux/surface_aggregator/controller.h | 17 +++++
|
|
2 files changed, 58 insertions(+), 28 deletions(-)
|
|
|
|
diff --git a/drivers/platform/surface/aggregator/controller.c b/drivers/platform/surface/aggregator/controller.c
|
|
index a06964aa96e7..cd3a6b77f48d 100644
|
|
--- a/drivers/platform/surface/aggregator/controller.c
|
|
+++ b/drivers/platform/surface/aggregator/controller.c
|
|
@@ -2127,9 +2127,15 @@ int ssam_ctrl_notif_d0_entry(struct ssam_controller *ctrl)
|
|
* @ctrl: The controller to register the notifier on.
|
|
* @n: The event notifier to register.
|
|
*
|
|
- * Register an event notifier and increment the usage counter of the
|
|
- * associated SAM event. If the event was previously not enabled, it will be
|
|
- * enabled during this call.
|
|
+ * Register an event notifier. Increment the usage counter of the associated
|
|
+ * SAM event if the notifier is not marked as an observer. If the event is not
|
|
+ * marked as an observer and is currently not enabled, it will be enabled
|
|
+ * during this call. If the notifier is marked as an observer, no attempt will
|
|
+ * be made at enabling any event and no reference count will be modified.
|
|
+ *
|
|
+ * Notifiers marked as observers do not need to be associated with one specific
|
|
+ * event, i.e. as long as no event matching is performed, only the event target
|
|
+ * category needs to be set.
|
|
*
|
|
* Return: Returns zero on success, %-ENOSPC if there have already been
|
|
* %INT_MAX notifiers for the event ID/type associated with the notifier block
|
|
@@ -2138,11 +2144,10 @@ int ssam_ctrl_notif_d0_entry(struct ssam_controller *ctrl)
|
|
* for the specific associated event, returns the status of the event-enable
|
|
* EC-command.
|
|
*/
|
|
-int ssam_notifier_register(struct ssam_controller *ctrl,
|
|
- struct ssam_event_notifier *n)
|
|
+int ssam_notifier_register(struct ssam_controller *ctrl, struct ssam_event_notifier *n)
|
|
{
|
|
u16 rqid = ssh_tc_to_rqid(n->event.id.target_category);
|
|
- struct ssam_nf_refcount_entry *entry;
|
|
+ struct ssam_nf_refcount_entry *entry = NULL;
|
|
struct ssam_nf_head *nf_head;
|
|
struct ssam_nf *nf;
|
|
int status;
|
|
@@ -2155,29 +2160,32 @@ int ssam_notifier_register(struct ssam_controller *ctrl,
|
|
|
|
mutex_lock(&nf->lock);
|
|
|
|
- entry = ssam_nf_refcount_inc(nf, n->event.reg, n->event.id);
|
|
- if (IS_ERR(entry)) {
|
|
- mutex_unlock(&nf->lock);
|
|
- return PTR_ERR(entry);
|
|
- }
|
|
+ if (!(n->flags & SSAM_EVENT_NOTIFIER_OBSERVER)) {
|
|
+ entry = ssam_nf_refcount_inc(nf, n->event.reg, n->event.id);
|
|
+ if (IS_ERR(entry)) {
|
|
+ mutex_unlock(&nf->lock);
|
|
+ return PTR_ERR(entry);
|
|
+ }
|
|
|
|
- ssam_dbg(ctrl, "enabling event (reg: %#04x, tc: %#04x, iid: %#04x, rc: %d)\n",
|
|
- n->event.reg.target_category, n->event.id.target_category,
|
|
- n->event.id.instance, entry->refcount);
|
|
+ ssam_dbg(ctrl, "enabling event (reg: %#04x, tc: %#04x, iid: %#04x, rc: %d)\n",
|
|
+ n->event.reg.target_category, n->event.id.target_category,
|
|
+ n->event.id.instance, entry->refcount);
|
|
+ }
|
|
|
|
status = ssam_nfblk_insert(nf_head, &n->base);
|
|
if (status) {
|
|
- entry = ssam_nf_refcount_dec(nf, n->event.reg, n->event.id);
|
|
- if (entry->refcount == 0)
|
|
- kfree(entry);
|
|
+ if (entry) {
|
|
+ entry = ssam_nf_refcount_dec(nf, n->event.reg, n->event.id);
|
|
+ if (entry->refcount == 0)
|
|
+ kfree(entry);
|
|
+ }
|
|
|
|
mutex_unlock(&nf->lock);
|
|
return status;
|
|
}
|
|
|
|
- if (entry->refcount == 1) {
|
|
- status = ssam_ssh_event_enable(ctrl, n->event.reg, n->event.id,
|
|
- n->event.flags);
|
|
+ if (entry && entry->refcount == 1) {
|
|
+ status = ssam_ssh_event_enable(ctrl, n->event.reg, n->event.id, n->event.flags);
|
|
if (status) {
|
|
ssam_nfblk_remove(&n->base);
|
|
kfree(ssam_nf_refcount_dec(nf, n->event.reg, n->event.id));
|
|
@@ -2188,7 +2196,7 @@ int ssam_notifier_register(struct ssam_controller *ctrl,
|
|
|
|
entry->flags = n->event.flags;
|
|
|
|
- } else if (entry->flags != n->event.flags) {
|
|
+ } else if (entry && entry->flags != n->event.flags) {
|
|
ssam_warn(ctrl,
|
|
"inconsistent flags when enabling event: got %#04x, expected %#04x (reg: %#04x, tc: %#04x, iid: %#04x)\n",
|
|
n->event.flags, entry->flags, n->event.reg.target_category,
|
|
@@ -2205,17 +2213,16 @@ EXPORT_SYMBOL_GPL(ssam_notifier_register);
|
|
* @ctrl: The controller the notifier has been registered on.
|
|
* @n: The event notifier to unregister.
|
|
*
|
|
- * Unregister an event notifier and decrement the usage counter of the
|
|
- * associated SAM event. If the usage counter reaches zero, the event will be
|
|
- * disabled.
|
|
+ * Unregister an event notifier. Decrement the usage counter of the associated
|
|
+ * SAM event if the notifier is not marked as an observer. If the usage counter
|
|
+ * reaches zero, the event will be disabled.
|
|
*
|
|
* Return: Returns zero on success, %-ENOENT if the given notifier block has
|
|
* not been registered on the controller. If the given notifier block was the
|
|
* last one associated with its specific event, returns the status of the
|
|
* event-disable EC-command.
|
|
*/
|
|
-int ssam_notifier_unregister(struct ssam_controller *ctrl,
|
|
- struct ssam_event_notifier *n)
|
|
+int ssam_notifier_unregister(struct ssam_controller *ctrl, struct ssam_event_notifier *n)
|
|
{
|
|
u16 rqid = ssh_tc_to_rqid(n->event.id.target_category);
|
|
struct ssam_nf_refcount_entry *entry;
|
|
@@ -2236,6 +2243,13 @@ int ssam_notifier_unregister(struct ssam_controller *ctrl,
|
|
return -ENOENT;
|
|
}
|
|
|
|
+ /*
|
|
+ * If this is an observer notifier, do not attempt to disable the
|
|
+ * event, just remove it.
|
|
+ */
|
|
+ if (n->flags & SSAM_EVENT_NOTIFIER_OBSERVER)
|
|
+ goto remove;
|
|
+
|
|
entry = ssam_nf_refcount_dec(nf, n->event.reg, n->event.id);
|
|
if (WARN_ON(!entry)) {
|
|
/*
|
|
@@ -2260,8 +2274,7 @@ int ssam_notifier_unregister(struct ssam_controller *ctrl,
|
|
}
|
|
|
|
if (entry->refcount == 0) {
|
|
- status = ssam_ssh_event_disable(ctrl, n->event.reg, n->event.id,
|
|
- n->event.flags);
|
|
+ status = ssam_ssh_event_disable(ctrl, n->event.reg, n->event.id, n->event.flags);
|
|
kfree(entry);
|
|
}
|
|
|
|
diff --git a/include/linux/surface_aggregator/controller.h b/include/linux/surface_aggregator/controller.h
|
|
index 0806796eabcb..cf4bb48a850e 100644
|
|
--- a/include/linux/surface_aggregator/controller.h
|
|
+++ b/include/linux/surface_aggregator/controller.h
|
|
@@ -795,6 +795,20 @@ enum ssam_event_mask {
|
|
#define SSAM_EVENT_REGISTRY_REG \
|
|
SSAM_EVENT_REGISTRY(SSAM_SSH_TC_REG, 0x02, 0x01, 0x02)
|
|
|
|
+/**
|
|
+ * enum ssam_event_notifier_flags - Flags for event notifiers.
|
|
+ * @SSAM_EVENT_NOTIFIER_OBSERVER:
|
|
+ * The corresponding notifier acts as observer. Registering a notifier
|
|
+ * with this flag set will not attempt to enable any event. Equally,
|
|
+ * unregistering will not attempt to disable any event. Note that a
|
|
+ * notifier with this flag may not even correspond to a certain event at
|
|
+ * all, only to a specific event target category. Event matching will not
|
|
+ * be influenced by this flag.
|
|
+ */
|
|
+enum ssam_event_notifier_flags {
|
|
+ SSAM_EVENT_NOTIFIER_OBSERVER = BIT(0),
|
|
+};
|
|
+
|
|
/**
|
|
* struct ssam_event_notifier - Notifier block for SSAM events.
|
|
* @base: The base notifier block with callback function and priority.
|
|
@@ -803,6 +817,7 @@ enum ssam_event_mask {
|
|
* @event.id: ID specifying the event.
|
|
* @event.mask: Flags determining how events are matched to the notifier.
|
|
* @event.flags: Flags used for enabling the event.
|
|
+ * @flags: Notifier flags (see &enum ssam_event_notifier_flags).
|
|
*/
|
|
struct ssam_event_notifier {
|
|
struct ssam_notifier_block base;
|
|
@@ -813,6 +828,8 @@ struct ssam_event_notifier {
|
|
enum ssam_event_mask mask;
|
|
u8 flags;
|
|
} event;
|
|
+
|
|
+ unsigned long flags;
|
|
};
|
|
|
|
int ssam_notifier_register(struct ssam_controller *ctrl,
|
|
--
|
|
2.32.0
|
|
|
|
From 674b886f8e962823be0e849b33ca9f842319a429 Mon Sep 17 00:00:00 2001
|
|
From: Maximilian Luz <luzmaximilian@gmail.com>
|
|
Date: Wed, 2 Jun 2021 18:34:48 +0200
|
|
Subject: [PATCH] platform/surface: aggregator: Allow enabling of events
|
|
without notifiers
|
|
|
|
We can already enable and disable SAM events via one of two ways: either
|
|
via a (non-observer) notifier tied to a specific event group, or a
|
|
generic event enable/disable request. In some instances, however,
|
|
neither method may be desirable.
|
|
|
|
The first method will tie the event enable request to a specific
|
|
notifier, however, when we want to receive notifications for multiple
|
|
event groups of the same target category and forward this to the same
|
|
notifier callback, we may receive duplicate events, i.e. one event per
|
|
registered notifier. The second method will bypass the internal
|
|
reference counting mechanism, meaning that a disable request will
|
|
disable the event regardless of any other client driver using it, which
|
|
may break the functionality of that driver.
|
|
|
|
To address this problem, add new functions that allow enabling and
|
|
disabling of events via the event reference counting mechanism built
|
|
into the controller, without needing to register a notifier.
|
|
|
|
This can then be used in combination with observer notifiers to process
|
|
multiple events of the same target category without duplication in the
|
|
same callback function.
|
|
|
|
Signed-off-by: Maximilian Luz <luzmaximilian@gmail.com>
|
|
Patchset: surface-sam
|
|
---
|
|
.../platform/surface/aggregator/controller.c | 135 ++++++++++++++++++
|
|
include/linux/surface_aggregator/controller.h | 8 ++
|
|
2 files changed, 143 insertions(+)
|
|
|
|
diff --git a/drivers/platform/surface/aggregator/controller.c b/drivers/platform/surface/aggregator/controller.c
|
|
index cd3a6b77f48d..49edddea417e 100644
|
|
--- a/drivers/platform/surface/aggregator/controller.c
|
|
+++ b/drivers/platform/surface/aggregator/controller.c
|
|
@@ -2287,6 +2287,141 @@ int ssam_notifier_unregister(struct ssam_controller *ctrl, struct ssam_event_not
|
|
}
|
|
EXPORT_SYMBOL_GPL(ssam_notifier_unregister);
|
|
|
|
+/**
|
|
+ * ssam_controller_event_enable() - Enable the specified event.
|
|
+ * @ctrl: The controller to enable the event for.
|
|
+ * @reg: The event registry to use for enabling the event.
|
|
+ * @id: The event ID specifying the event to be enabled.
|
|
+ * @flags: The SAM event flags used for enabling the event.
|
|
+ *
|
|
+ * Increment the event reference count of the specified event. If the event has
|
|
+ * not been enabled previously, it will be enabled by this call.
|
|
+ *
|
|
+ * Note: In general, ssam_notifier_register() with a non-observer notifier
|
|
+ * should be preferred for enabling/disabling events, as this will guarantee
|
|
+ * proper ordering and event forwarding in case of errors during event
|
|
+ * enabling/disabling.
|
|
+ *
|
|
+ * Return: Returns zero on success, %-ENOSPC if the reference count for the
|
|
+ * specified event has reached its maximum, %-ENOMEM if the corresponding event
|
|
+ * entry could not be allocated. If this is the first time that this event has
|
|
+ * been enabled (i.e. the reference count was incremented from zero to one by
|
|
+ * this call), returns the status of the event-enable EC-command.
|
|
+ */
|
|
+int ssam_controller_event_enable(struct ssam_controller *ctrl,
|
|
+ struct ssam_event_registry reg,
|
|
+ struct ssam_event_id id, u8 flags)
|
|
+{
|
|
+ u16 rqid = ssh_tc_to_rqid(id.target_category);
|
|
+ struct ssam_nf_refcount_entry *entry;
|
|
+ struct ssam_nf_head *nf_head;
|
|
+ struct ssam_nf *nf;
|
|
+ int status;
|
|
+
|
|
+ if (!ssh_rqid_is_event(rqid))
|
|
+ return -EINVAL;
|
|
+
|
|
+ nf = &ctrl->cplt.event.notif;
|
|
+ nf_head = &nf->head[ssh_rqid_to_event(rqid)];
|
|
+
|
|
+ mutex_lock(&nf->lock);
|
|
+
|
|
+ entry = ssam_nf_refcount_inc(nf, reg, id);
|
|
+ if (IS_ERR(entry)) {
|
|
+ mutex_unlock(&nf->lock);
|
|
+ return PTR_ERR(entry);
|
|
+ }
|
|
+
|
|
+ ssam_dbg(ctrl, "enabling event (reg: %#04x, tc: %#04x, iid: %#04x, rc: %d)\n",
|
|
+ reg.target_category, id.target_category, id.instance,
|
|
+ entry->refcount);
|
|
+
|
|
+ if (entry->refcount == 1) {
|
|
+ status = ssam_ssh_event_enable(ctrl, reg, id, flags);
|
|
+ if (status) {
|
|
+ kfree(ssam_nf_refcount_dec(nf, reg, id));
|
|
+ mutex_unlock(&nf->lock);
|
|
+ return status;
|
|
+ }
|
|
+
|
|
+ entry->flags = flags;
|
|
+
|
|
+ } else if (entry->flags != flags) {
|
|
+ ssam_warn(ctrl,
|
|
+ "inconsistent flags when enabling event: got %#04x, expected %#04x (reg: %#04x, tc: %#04x, iid: %#04x)\n",
|
|
+ flags, entry->flags, reg.target_category,
|
|
+ id.target_category, id.instance);
|
|
+ }
|
|
+
|
|
+ mutex_unlock(&nf->lock);
|
|
+ return 0;
|
|
+}
|
|
+EXPORT_SYMBOL_GPL(ssam_controller_event_enable);
|
|
+
|
|
+/**
|
|
+ * ssam_controller_event_disable() - Disable the specified event.
|
|
+ * @ctrl: The controller to disable the event for.
|
|
+ * @reg: The event registry to use for disabling the event.
|
|
+ * @id: The event ID specifying the event to be disabled.
|
|
+ * @flags: The flags used when enabling the event.
|
|
+ *
|
|
+ * Decrement the reference count of the specified event. If the reference count
|
|
+ * reaches zero, the event will be disabled.
|
|
+ *
|
|
+ * Note: In general, ssam_notifier_register()/ssam_notifier_unregister() with a
|
|
+ * non-observer notifier should be preferred for enabling/disabling events, as
|
|
+ * this will guarantee proper ordering and event forwarding in case of errors
|
|
+ * during event enabling/disabling.
|
|
+ *
|
|
+ * Return: Returns zero on success, %-ENOENT if the given event has not been
|
|
+ * enabled on the controller. If the reference count of the event reaches zero
|
|
+ * during this call, returns the status of the event-disable EC-command.
|
|
+ */
|
|
+int ssam_controller_event_disable(struct ssam_controller *ctrl,
|
|
+ struct ssam_event_registry reg,
|
|
+ struct ssam_event_id id, u8 flags)
|
|
+{
|
|
+ u16 rqid = ssh_tc_to_rqid(id.target_category);
|
|
+ struct ssam_nf_refcount_entry *entry;
|
|
+ struct ssam_nf_head *nf_head;
|
|
+ struct ssam_nf *nf;
|
|
+ int status = 0;
|
|
+
|
|
+ if (!ssh_rqid_is_event(rqid))
|
|
+ return -EINVAL;
|
|
+
|
|
+ nf = &ctrl->cplt.event.notif;
|
|
+ nf_head = &nf->head[ssh_rqid_to_event(rqid)];
|
|
+
|
|
+ mutex_lock(&nf->lock);
|
|
+
|
|
+ entry = ssam_nf_refcount_dec(nf, reg, id);
|
|
+ if (WARN_ON(!entry)) {
|
|
+ mutex_unlock(&nf->lock);
|
|
+ return -ENOENT;
|
|
+ }
|
|
+
|
|
+ ssam_dbg(ctrl, "disabling event (reg: %#04x, tc: %#04x, iid: %#04x, rc: %d)\n",
|
|
+ reg.target_category, id.target_category, id.instance,
|
|
+ entry->refcount);
|
|
+
|
|
+ if (entry->flags != flags) {
|
|
+ ssam_warn(ctrl,
|
|
+ "inconsistent flags when disabling event: got %#04x, expected %#04x (reg: %#04x, tc: %#04x, iid: %#04x)\n",
|
|
+ flags, entry->flags, reg.target_category,
|
|
+ id.target_category, id.instance);
|
|
+ }
|
|
+
|
|
+ if (entry->refcount == 0) {
|
|
+ status = ssam_ssh_event_disable(ctrl, reg, id, flags);
|
|
+ kfree(entry);
|
|
+ }
|
|
+
|
|
+ mutex_unlock(&nf->lock);
|
|
+ return status;
|
|
+}
|
|
+EXPORT_SYMBOL_GPL(ssam_controller_event_disable);
|
|
+
|
|
/**
|
|
* ssam_notifier_disable_registered() - Disable events for all registered
|
|
* notifiers.
|
|
diff --git a/include/linux/surface_aggregator/controller.h b/include/linux/surface_aggregator/controller.h
|
|
index cf4bb48a850e..7965bdc669c5 100644
|
|
--- a/include/linux/surface_aggregator/controller.h
|
|
+++ b/include/linux/surface_aggregator/controller.h
|
|
@@ -838,4 +838,12 @@ int ssam_notifier_register(struct ssam_controller *ctrl,
|
|
int ssam_notifier_unregister(struct ssam_controller *ctrl,
|
|
struct ssam_event_notifier *n);
|
|
|
|
+int ssam_controller_event_enable(struct ssam_controller *ctrl,
|
|
+ struct ssam_event_registry reg,
|
|
+ struct ssam_event_id id, u8 flags);
|
|
+
|
|
+int ssam_controller_event_disable(struct ssam_controller *ctrl,
|
|
+ struct ssam_event_registry reg,
|
|
+ struct ssam_event_id id, u8 flags);
|
|
+
|
|
#endif /* _LINUX_SURFACE_AGGREGATOR_CONTROLLER_H */
|
|
--
|
|
2.32.0
|
|
|
|
From 4f63f260aaa1b2f61d407fd32e9ecc1b108e5472 Mon Sep 17 00:00:00 2001
|
|
From: Maximilian Luz <luzmaximilian@gmail.com>
|
|
Date: Thu, 3 Jun 2021 00:10:38 +0200
|
|
Subject: [PATCH] platform/surface: aggregator: Update copyright
|
|
|
|
It's 2021, update the copyright accordingly.
|
|
|
|
Signed-off-by: Maximilian Luz <luzmaximilian@gmail.com>
|
|
Patchset: surface-sam
|
|
---
|
|
drivers/platform/surface/aggregator/Kconfig | 2 +-
|
|
drivers/platform/surface/aggregator/Makefile | 2 +-
|
|
drivers/platform/surface/aggregator/bus.c | 2 +-
|
|
drivers/platform/surface/aggregator/bus.h | 2 +-
|
|
drivers/platform/surface/aggregator/controller.c | 2 +-
|
|
drivers/platform/surface/aggregator/controller.h | 2 +-
|
|
drivers/platform/surface/aggregator/core.c | 2 +-
|
|
drivers/platform/surface/aggregator/ssh_msgb.h | 2 +-
|
|
drivers/platform/surface/aggregator/ssh_packet_layer.c | 2 +-
|
|
drivers/platform/surface/aggregator/ssh_packet_layer.h | 2 +-
|
|
drivers/platform/surface/aggregator/ssh_parser.c | 2 +-
|
|
drivers/platform/surface/aggregator/ssh_parser.h | 2 +-
|
|
drivers/platform/surface/aggregator/ssh_request_layer.c | 2 +-
|
|
drivers/platform/surface/aggregator/ssh_request_layer.h | 2 +-
|
|
drivers/platform/surface/aggregator/trace.h | 2 +-
|
|
include/linux/surface_aggregator/controller.h | 2 +-
|
|
include/linux/surface_aggregator/device.h | 2 +-
|
|
include/linux/surface_aggregator/serial_hub.h | 2 +-
|
|
18 files changed, 18 insertions(+), 18 deletions(-)
|
|
|
|
diff --git a/drivers/platform/surface/aggregator/Kconfig b/drivers/platform/surface/aggregator/Kconfig
|
|
index 3aaeea9f0433..fd6dc452f3e8 100644
|
|
--- a/drivers/platform/surface/aggregator/Kconfig
|
|
+++ b/drivers/platform/surface/aggregator/Kconfig
|
|
@@ -1,5 +1,5 @@
|
|
# SPDX-License-Identifier: GPL-2.0+
|
|
-# Copyright (C) 2019-2020 Maximilian Luz <luzmaximilian@gmail.com>
|
|
+# Copyright (C) 2019-2021 Maximilian Luz <luzmaximilian@gmail.com>
|
|
|
|
menuconfig SURFACE_AGGREGATOR
|
|
tristate "Microsoft Surface System Aggregator Module Subsystem and Drivers"
|
|
diff --git a/drivers/platform/surface/aggregator/Makefile b/drivers/platform/surface/aggregator/Makefile
|
|
index c112e2c7112b..c8498c41e758 100644
|
|
--- a/drivers/platform/surface/aggregator/Makefile
|
|
+++ b/drivers/platform/surface/aggregator/Makefile
|
|
@@ -1,5 +1,5 @@
|
|
# SPDX-License-Identifier: GPL-2.0+
|
|
-# Copyright (C) 2019-2020 Maximilian Luz <luzmaximilian@gmail.com>
|
|
+# Copyright (C) 2019-2021 Maximilian Luz <luzmaximilian@gmail.com>
|
|
|
|
# For include/trace/define_trace.h to include trace.h
|
|
CFLAGS_core.o = -I$(src)
|
|
diff --git a/drivers/platform/surface/aggregator/bus.c b/drivers/platform/surface/aggregator/bus.c
|
|
index a9b660af0917..0169677c243e 100644
|
|
--- a/drivers/platform/surface/aggregator/bus.c
|
|
+++ b/drivers/platform/surface/aggregator/bus.c
|
|
@@ -2,7 +2,7 @@
|
|
/*
|
|
* Surface System Aggregator Module bus and device integration.
|
|
*
|
|
- * Copyright (C) 2019-2020 Maximilian Luz <luzmaximilian@gmail.com>
|
|
+ * Copyright (C) 2019-2021 Maximilian Luz <luzmaximilian@gmail.com>
|
|
*/
|
|
|
|
#include <linux/device.h>
|
|
diff --git a/drivers/platform/surface/aggregator/bus.h b/drivers/platform/surface/aggregator/bus.h
|
|
index 7712baaed6a5..ed032c2cbdb2 100644
|
|
--- a/drivers/platform/surface/aggregator/bus.h
|
|
+++ b/drivers/platform/surface/aggregator/bus.h
|
|
@@ -2,7 +2,7 @@
|
|
/*
|
|
* Surface System Aggregator Module bus and device integration.
|
|
*
|
|
- * Copyright (C) 2019-2020 Maximilian Luz <luzmaximilian@gmail.com>
|
|
+ * Copyright (C) 2019-2021 Maximilian Luz <luzmaximilian@gmail.com>
|
|
*/
|
|
|
|
#ifndef _SURFACE_AGGREGATOR_BUS_H
|
|
diff --git a/drivers/platform/surface/aggregator/controller.c b/drivers/platform/surface/aggregator/controller.c
|
|
index 49edddea417e..e91ee7e72c14 100644
|
|
--- a/drivers/platform/surface/aggregator/controller.c
|
|
+++ b/drivers/platform/surface/aggregator/controller.c
|
|
@@ -2,7 +2,7 @@
|
|
/*
|
|
* Main SSAM/SSH controller structure and functionality.
|
|
*
|
|
- * Copyright (C) 2019-2020 Maximilian Luz <luzmaximilian@gmail.com>
|
|
+ * Copyright (C) 2019-2021 Maximilian Luz <luzmaximilian@gmail.com>
|
|
*/
|
|
|
|
#include <linux/acpi.h>
|
|
diff --git a/drivers/platform/surface/aggregator/controller.h b/drivers/platform/surface/aggregator/controller.h
|
|
index 8297d34e7489..a0963c3562ff 100644
|
|
--- a/drivers/platform/surface/aggregator/controller.h
|
|
+++ b/drivers/platform/surface/aggregator/controller.h
|
|
@@ -2,7 +2,7 @@
|
|
/*
|
|
* Main SSAM/SSH controller structure and functionality.
|
|
*
|
|
- * Copyright (C) 2019-2020 Maximilian Luz <luzmaximilian@gmail.com>
|
|
+ * Copyright (C) 2019-2021 Maximilian Luz <luzmaximilian@gmail.com>
|
|
*/
|
|
|
|
#ifndef _SURFACE_AGGREGATOR_CONTROLLER_H
|
|
diff --git a/drivers/platform/surface/aggregator/core.c b/drivers/platform/surface/aggregator/core.c
|
|
index 8dc2c267bcd6..5d780e55f4a1 100644
|
|
--- a/drivers/platform/surface/aggregator/core.c
|
|
+++ b/drivers/platform/surface/aggregator/core.c
|
|
@@ -7,7 +7,7 @@
|
|
* Handles communication via requests as well as enabling, disabling, and
|
|
* relaying of events.
|
|
*
|
|
- * Copyright (C) 2019-2020 Maximilian Luz <luzmaximilian@gmail.com>
|
|
+ * Copyright (C) 2019-2021 Maximilian Luz <luzmaximilian@gmail.com>
|
|
*/
|
|
|
|
#include <linux/acpi.h>
|
|
diff --git a/drivers/platform/surface/aggregator/ssh_msgb.h b/drivers/platform/surface/aggregator/ssh_msgb.h
|
|
index 1221f642dda1..e562958ffdf0 100644
|
|
--- a/drivers/platform/surface/aggregator/ssh_msgb.h
|
|
+++ b/drivers/platform/surface/aggregator/ssh_msgb.h
|
|
@@ -2,7 +2,7 @@
|
|
/*
|
|
* SSH message builder functions.
|
|
*
|
|
- * Copyright (C) 2019-2020 Maximilian Luz <luzmaximilian@gmail.com>
|
|
+ * Copyright (C) 2019-2021 Maximilian Luz <luzmaximilian@gmail.com>
|
|
*/
|
|
|
|
#ifndef _SURFACE_AGGREGATOR_SSH_MSGB_H
|
|
diff --git a/drivers/platform/surface/aggregator/ssh_packet_layer.c b/drivers/platform/surface/aggregator/ssh_packet_layer.c
|
|
index 15d96eac6811..5e08049fc3ac 100644
|
|
--- a/drivers/platform/surface/aggregator/ssh_packet_layer.c
|
|
+++ b/drivers/platform/surface/aggregator/ssh_packet_layer.c
|
|
@@ -2,7 +2,7 @@
|
|
/*
|
|
* SSH packet transport layer.
|
|
*
|
|
- * Copyright (C) 2019-2020 Maximilian Luz <luzmaximilian@gmail.com>
|
|
+ * Copyright (C) 2019-2021 Maximilian Luz <luzmaximilian@gmail.com>
|
|
*/
|
|
|
|
#include <asm/unaligned.h>
|
|
diff --git a/drivers/platform/surface/aggregator/ssh_packet_layer.h b/drivers/platform/surface/aggregator/ssh_packet_layer.h
|
|
index e8757d03f279..2eb329f0b91a 100644
|
|
--- a/drivers/platform/surface/aggregator/ssh_packet_layer.h
|
|
+++ b/drivers/platform/surface/aggregator/ssh_packet_layer.h
|
|
@@ -2,7 +2,7 @@
|
|
/*
|
|
* SSH packet transport layer.
|
|
*
|
|
- * Copyright (C) 2019-2020 Maximilian Luz <luzmaximilian@gmail.com>
|
|
+ * Copyright (C) 2019-2021 Maximilian Luz <luzmaximilian@gmail.com>
|
|
*/
|
|
|
|
#ifndef _SURFACE_AGGREGATOR_SSH_PACKET_LAYER_H
|
|
diff --git a/drivers/platform/surface/aggregator/ssh_parser.c b/drivers/platform/surface/aggregator/ssh_parser.c
|
|
index e2dead8de94a..b77912f8f13b 100644
|
|
--- a/drivers/platform/surface/aggregator/ssh_parser.c
|
|
+++ b/drivers/platform/surface/aggregator/ssh_parser.c
|
|
@@ -2,7 +2,7 @@
|
|
/*
|
|
* SSH message parser.
|
|
*
|
|
- * Copyright (C) 2019-2020 Maximilian Luz <luzmaximilian@gmail.com>
|
|
+ * Copyright (C) 2019-2021 Maximilian Luz <luzmaximilian@gmail.com>
|
|
*/
|
|
|
|
#include <asm/unaligned.h>
|
|
diff --git a/drivers/platform/surface/aggregator/ssh_parser.h b/drivers/platform/surface/aggregator/ssh_parser.h
|
|
index 63c38d350988..3bd6e180fd16 100644
|
|
--- a/drivers/platform/surface/aggregator/ssh_parser.h
|
|
+++ b/drivers/platform/surface/aggregator/ssh_parser.h
|
|
@@ -2,7 +2,7 @@
|
|
/*
|
|
* SSH message parser.
|
|
*
|
|
- * Copyright (C) 2019-2020 Maximilian Luz <luzmaximilian@gmail.com>
|
|
+ * Copyright (C) 2019-2021 Maximilian Luz <luzmaximilian@gmail.com>
|
|
*/
|
|
|
|
#ifndef _SURFACE_AGGREGATOR_SSH_PARSER_H
|
|
diff --git a/drivers/platform/surface/aggregator/ssh_request_layer.c b/drivers/platform/surface/aggregator/ssh_request_layer.c
|
|
index 52a83a8fcf82..bfe1aaf38065 100644
|
|
--- a/drivers/platform/surface/aggregator/ssh_request_layer.c
|
|
+++ b/drivers/platform/surface/aggregator/ssh_request_layer.c
|
|
@@ -2,7 +2,7 @@
|
|
/*
|
|
* SSH request transport layer.
|
|
*
|
|
- * Copyright (C) 2019-2020 Maximilian Luz <luzmaximilian@gmail.com>
|
|
+ * Copyright (C) 2019-2021 Maximilian Luz <luzmaximilian@gmail.com>
|
|
*/
|
|
|
|
#include <asm/unaligned.h>
|
|
diff --git a/drivers/platform/surface/aggregator/ssh_request_layer.h b/drivers/platform/surface/aggregator/ssh_request_layer.h
|
|
index cb35815858d1..9c3cbae2d4bd 100644
|
|
--- a/drivers/platform/surface/aggregator/ssh_request_layer.h
|
|
+++ b/drivers/platform/surface/aggregator/ssh_request_layer.h
|
|
@@ -2,7 +2,7 @@
|
|
/*
|
|
* SSH request transport layer.
|
|
*
|
|
- * Copyright (C) 2019-2020 Maximilian Luz <luzmaximilian@gmail.com>
|
|
+ * Copyright (C) 2019-2021 Maximilian Luz <luzmaximilian@gmail.com>
|
|
*/
|
|
|
|
#ifndef _SURFACE_AGGREGATOR_SSH_REQUEST_LAYER_H
|
|
diff --git a/drivers/platform/surface/aggregator/trace.h b/drivers/platform/surface/aggregator/trace.h
|
|
index eb332bb53ae4..de64cf169060 100644
|
|
--- a/drivers/platform/surface/aggregator/trace.h
|
|
+++ b/drivers/platform/surface/aggregator/trace.h
|
|
@@ -2,7 +2,7 @@
|
|
/*
|
|
* Trace points for SSAM/SSH.
|
|
*
|
|
- * Copyright (C) 2020 Maximilian Luz <luzmaximilian@gmail.com>
|
|
+ * Copyright (C) 2020-2021 Maximilian Luz <luzmaximilian@gmail.com>
|
|
*/
|
|
|
|
#undef TRACE_SYSTEM
|
|
diff --git a/include/linux/surface_aggregator/controller.h b/include/linux/surface_aggregator/controller.h
|
|
index 7965bdc669c5..068e1982ad37 100644
|
|
--- a/include/linux/surface_aggregator/controller.h
|
|
+++ b/include/linux/surface_aggregator/controller.h
|
|
@@ -6,7 +6,7 @@
|
|
* managing access and communication to and from the SSAM EC, as well as main
|
|
* communication structures and definitions.
|
|
*
|
|
- * Copyright (C) 2019-2020 Maximilian Luz <luzmaximilian@gmail.com>
|
|
+ * Copyright (C) 2019-2021 Maximilian Luz <luzmaximilian@gmail.com>
|
|
*/
|
|
|
|
#ifndef _LINUX_SURFACE_AGGREGATOR_CONTROLLER_H
|
|
diff --git a/include/linux/surface_aggregator/device.h b/include/linux/surface_aggregator/device.h
|
|
index 6ff9c58b3e17..f636c5310321 100644
|
|
--- a/include/linux/surface_aggregator/device.h
|
|
+++ b/include/linux/surface_aggregator/device.h
|
|
@@ -7,7 +7,7 @@
|
|
* Provides support for non-platform/non-ACPI SSAM clients via dedicated
|
|
* subsystem.
|
|
*
|
|
- * Copyright (C) 2019-2020 Maximilian Luz <luzmaximilian@gmail.com>
|
|
+ * Copyright (C) 2019-2021 Maximilian Luz <luzmaximilian@gmail.com>
|
|
*/
|
|
|
|
#ifndef _LINUX_SURFACE_AGGREGATOR_DEVICE_H
|
|
diff --git a/include/linux/surface_aggregator/serial_hub.h b/include/linux/surface_aggregator/serial_hub.h
|
|
index 64276fbfa1d5..c3de43edcffa 100644
|
|
--- a/include/linux/surface_aggregator/serial_hub.h
|
|
+++ b/include/linux/surface_aggregator/serial_hub.h
|
|
@@ -6,7 +6,7 @@
|
|
* Surface System Aggregator Module (SSAM). Provides the interface for basic
|
|
* packet- and request-based communication with the SSAM EC via SSH.
|
|
*
|
|
- * Copyright (C) 2019-2020 Maximilian Luz <luzmaximilian@gmail.com>
|
|
+ * Copyright (C) 2019-2021 Maximilian Luz <luzmaximilian@gmail.com>
|
|
*/
|
|
|
|
#ifndef _LINUX_SURFACE_AGGREGATOR_SERIAL_HUB_H
|
|
--
|
|
2.32.0
|
|
|
|
From e8076a85fafbd1ede0edea60727eda1f2b83ad40 Mon Sep 17 00:00:00 2001
|
|
From: Maximilian Luz <luzmaximilian@gmail.com>
|
|
Date: Wed, 2 Jun 2021 19:29:16 +0200
|
|
Subject: [PATCH] platform/surface: aggregator_cdev: Add support for forwarding
|
|
events to user-space
|
|
|
|
Currently, debugging unknown events requires writing a custom driver.
|
|
This is somewhat difficult, slow to adapt, and not entirely
|
|
user-friendly for quickly trying to figure out things on devices of some
|
|
third-party user. We can do better. We already have a user-space
|
|
interface intended for debugging SAM EC requests, so let's add support
|
|
for receiving events to that.
|
|
|
|
This commit provides support for receiving events by reading from the
|
|
controller file. It additionally introduces two new IOCTLs to control
|
|
which event categories will be forwarded. Specifically, a user-space
|
|
client can specify which target categories it wants to receive events
|
|
from by registering the corresponding notifier(s) via the IOCTLs and
|
|
after that, read the received events by reading from the controller
|
|
device.
|
|
|
|
Signed-off-by: Maximilian Luz <luzmaximilian@gmail.com>
|
|
Patchset: surface-sam
|
|
---
|
|
.../userspace-api/ioctl/ioctl-number.rst | 2 +-
|
|
.../surface/surface_aggregator_cdev.c | 454 +++++++++++++++++-
|
|
include/uapi/linux/surface_aggregator/cdev.h | 41 +-
|
|
3 files changed, 471 insertions(+), 26 deletions(-)
|
|
|
|
diff --git a/Documentation/userspace-api/ioctl/ioctl-number.rst b/Documentation/userspace-api/ioctl/ioctl-number.rst
|
|
index 1c28b8ef6677..cfd1610e5e95 100644
|
|
--- a/Documentation/userspace-api/ioctl/ioctl-number.rst
|
|
+++ b/Documentation/userspace-api/ioctl/ioctl-number.rst
|
|
@@ -325,7 +325,7 @@ Code Seq# Include File Comments
|
|
0xA3 90-9F linux/dtlk.h
|
|
0xA4 00-1F uapi/linux/tee.h Generic TEE subsystem
|
|
0xA4 00-1F uapi/asm/sgx.h <mailto:linux-sgx@vger.kernel.org>
|
|
-0xA5 01 linux/surface_aggregator/cdev.h Microsoft Surface Platform System Aggregator
|
|
+0xA5 01-05 linux/surface_aggregator/cdev.h Microsoft Surface Platform System Aggregator
|
|
<mailto:luzmaximilian@gmail.com>
|
|
0xA5 20-2F linux/surface_aggregator/dtx.h Microsoft Surface DTX driver
|
|
<mailto:luzmaximilian@gmail.com>
|
|
diff --git a/drivers/platform/surface/surface_aggregator_cdev.c b/drivers/platform/surface/surface_aggregator_cdev.c
|
|
index 79e28fab7e40..7b27c8ca38a5 100644
|
|
--- a/drivers/platform/surface/surface_aggregator_cdev.c
|
|
+++ b/drivers/platform/surface/surface_aggregator_cdev.c
|
|
@@ -3,29 +3,68 @@
|
|
* Provides user-space access to the SSAM EC via the /dev/surface/aggregator
|
|
* misc device. Intended for debugging and development.
|
|
*
|
|
- * Copyright (C) 2020 Maximilian Luz <luzmaximilian@gmail.com>
|
|
+ * Copyright (C) 2020-2021 Maximilian Luz <luzmaximilian@gmail.com>
|
|
*/
|
|
|
|
#include <linux/fs.h>
|
|
+#include <linux/ioctl.h>
|
|
#include <linux/kernel.h>
|
|
+#include <linux/kfifo.h>
|
|
#include <linux/kref.h>
|
|
#include <linux/miscdevice.h>
|
|
#include <linux/module.h>
|
|
#include <linux/platform_device.h>
|
|
+#include <linux/poll.h>
|
|
#include <linux/rwsem.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/uaccess.h>
|
|
+#include <linux/vmalloc.h>
|
|
|
|
#include <linux/surface_aggregator/cdev.h>
|
|
#include <linux/surface_aggregator/controller.h>
|
|
|
|
#define SSAM_CDEV_DEVICE_NAME "surface_aggregator_cdev"
|
|
|
|
+
|
|
+/* -- Main structures. ------------------------------------------------------ */
|
|
+
|
|
+enum ssam_cdev_device_state {
|
|
+ SSAM_CDEV_DEVICE_SHUTDOWN_BIT = BIT(0),
|
|
+};
|
|
+
|
|
struct ssam_cdev {
|
|
struct kref kref;
|
|
struct rw_semaphore lock;
|
|
+
|
|
+ struct device *dev;
|
|
struct ssam_controller *ctrl;
|
|
struct miscdevice mdev;
|
|
+ unsigned long flags;
|
|
+
|
|
+ struct rw_semaphore client_lock; /* Guards client list. */
|
|
+ struct list_head client_list;
|
|
+};
|
|
+
|
|
+struct ssam_cdev_client;
|
|
+
|
|
+struct ssam_cdev_notifier {
|
|
+ struct ssam_cdev_client *client;
|
|
+ struct ssam_event_notifier nf;
|
|
+};
|
|
+
|
|
+struct ssam_cdev_client {
|
|
+ struct ssam_cdev *cdev;
|
|
+ struct list_head node;
|
|
+
|
|
+ struct mutex notifier_lock; /* Guards notifier access for registration */
|
|
+ struct ssam_cdev_notifier *notifier[SSH_NUM_EVENTS];
|
|
+
|
|
+ struct mutex read_lock; /* Guards FIFO buffer read access */
|
|
+ struct mutex write_lock; /* Guards FIFO buffer write access */
|
|
+ DECLARE_KFIFO(buffer, u8, 4096);
|
|
+
|
|
+ wait_queue_head_t waitq;
|
|
+ struct fasync_struct *fasync;
|
|
};
|
|
|
|
static void __ssam_cdev_release(struct kref *kref)
|
|
@@ -47,24 +86,167 @@ static void ssam_cdev_put(struct ssam_cdev *cdev)
|
|
kref_put(&cdev->kref, __ssam_cdev_release);
|
|
}
|
|
|
|
-static int ssam_cdev_device_open(struct inode *inode, struct file *filp)
|
|
+
|
|
+/* -- Notifier handling. ---------------------------------------------------- */
|
|
+
|
|
+static u32 ssam_cdev_notifier(struct ssam_event_notifier *nf, const struct ssam_event *in)
|
|
{
|
|
- struct miscdevice *mdev = filp->private_data;
|
|
- struct ssam_cdev *cdev = container_of(mdev, struct ssam_cdev, mdev);
|
|
+ struct ssam_cdev_notifier *cdev_nf = container_of(nf, struct ssam_cdev_notifier, nf);
|
|
+ struct ssam_cdev_client *client = cdev_nf->client;
|
|
+ struct ssam_cdev_event event;
|
|
+ size_t n = struct_size(&event, data, in->length);
|
|
+
|
|
+ /* Translate event. */
|
|
+ event.target_category = in->target_category;
|
|
+ event.target_id = in->target_id;
|
|
+ event.command_id = in->command_id;
|
|
+ event.instance_id = in->instance_id;
|
|
+ event.length = in->length;
|
|
+
|
|
+ mutex_lock(&client->write_lock);
|
|
+
|
|
+ /* Make sure we have enough space. */
|
|
+ if (kfifo_avail(&client->buffer) < n) {
|
|
+ dev_warn(client->cdev->dev,
|
|
+ "buffer full, dropping event (tc: %#04x, tid: %#04x, cid: %#04x, iid: %#04x)\n",
|
|
+ in->target_category, in->target_id, in->command_id, in->instance_id);
|
|
+ mutex_unlock(&client->write_lock);
|
|
+ return 0;
|
|
+ }
|
|
|
|
- filp->private_data = ssam_cdev_get(cdev);
|
|
- return stream_open(inode, filp);
|
|
+ /* Copy event header and payload. */
|
|
+ kfifo_in(&client->buffer, (const u8 *)&event, struct_size(&event, data, 0));
|
|
+ kfifo_in(&client->buffer, &in->data[0], in->length);
|
|
+
|
|
+ mutex_unlock(&client->write_lock);
|
|
+
|
|
+ /* Notify waiting readers. */
|
|
+ kill_fasync(&client->fasync, SIGIO, POLL_IN);
|
|
+ wake_up_interruptible(&client->waitq);
|
|
+
|
|
+ /*
|
|
+ * Don't mark events as handled, this is the job of a proper driver and
|
|
+ * not the debugging interface.
|
|
+ */
|
|
+ return 0;
|
|
}
|
|
|
|
-static int ssam_cdev_device_release(struct inode *inode, struct file *filp)
|
|
+static int ssam_cdev_notifier_register(struct ssam_cdev_client *client, u8 category, int priority)
|
|
{
|
|
- ssam_cdev_put(filp->private_data);
|
|
- return 0;
|
|
+ struct ssam_cdev_notifier *nf;
|
|
+ int index = ((int)category) - 1;
|
|
+ int status;
|
|
+
|
|
+ /* Validate notifier target category. */
|
|
+ if (index < 0 || index >= SSH_NUM_EVENTS)
|
|
+ return -EINVAL;
|
|
+
|
|
+ mutex_lock(&client->notifier_lock);
|
|
+
|
|
+ /* Check if the notifier has already been registered. */
|
|
+ if (client->notifier[index]) {
|
|
+ mutex_unlock(&client->notifier_lock);
|
|
+ return -EEXIST;
|
|
+ }
|
|
+
|
|
+ /* Allocate new notifier. */
|
|
+ nf = kzalloc(sizeof(*nf), GFP_KERNEL);
|
|
+ if (!nf) {
|
|
+ mutex_unlock(&client->notifier_lock);
|
|
+ return -ENOMEM;
|
|
+ }
|
|
+
|
|
+ /*
|
|
+ * Create a dummy notifier with the minimal required fields for
|
|
+ * observer registration. Note that we can skip fully specifying event
|
|
+ * and registry here as we do not need any matching and use silent
|
|
+ * registration, which does not enable the corresponding event.
|
|
+ */
|
|
+ nf->client = client;
|
|
+ nf->nf.base.fn = ssam_cdev_notifier;
|
|
+ nf->nf.base.priority = priority;
|
|
+ nf->nf.event.id.target_category = category;
|
|
+ nf->nf.event.mask = 0; /* Do not do any matching. */
|
|
+ nf->nf.flags = SSAM_EVENT_NOTIFIER_OBSERVER;
|
|
+
|
|
+ /* Register notifier. */
|
|
+ status = ssam_notifier_register(client->cdev->ctrl, &nf->nf);
|
|
+ if (status)
|
|
+ kfree(nf);
|
|
+ else
|
|
+ client->notifier[index] = nf;
|
|
+
|
|
+ mutex_unlock(&client->notifier_lock);
|
|
+ return status;
|
|
}
|
|
|
|
-static long ssam_cdev_request(struct ssam_cdev *cdev, unsigned long arg)
|
|
+static int ssam_cdev_notifier_unregister(struct ssam_cdev_client *client, u8 category)
|
|
+{
|
|
+ int index = ((int)category) - 1;
|
|
+ int status;
|
|
+
|
|
+ /* Validate notifier target category. */
|
|
+ if (index < 0 || index >= SSH_NUM_EVENTS)
|
|
+ return -EINVAL;
|
|
+
|
|
+ mutex_lock(&client->notifier_lock);
|
|
+
|
|
+ /* Check if the notifier is currently registered. */
|
|
+ if (!client->notifier[index]) {
|
|
+ mutex_unlock(&client->notifier_lock);
|
|
+ return -ENOENT;
|
|
+ }
|
|
+
|
|
+ /* Unregister and free notifier. */
|
|
+ status = ssam_notifier_unregister(client->cdev->ctrl, &client->notifier[index]->nf);
|
|
+ kfree(client->notifier[index]);
|
|
+ client->notifier[index] = NULL;
|
|
+
|
|
+ mutex_unlock(&client->notifier_lock);
|
|
+ return status;
|
|
+}
|
|
+
|
|
+static void ssam_cdev_notifier_unregister_all(struct ssam_cdev_client *client)
|
|
+{
|
|
+ int i;
|
|
+
|
|
+ down_read(&client->cdev->lock);
|
|
+
|
|
+ /*
|
|
+ * This function may be used during shutdown, thus we need to test for
|
|
+ * cdev->ctrl instead of the SSAM_CDEV_DEVICE_SHUTDOWN_BIT bit.
|
|
+ */
|
|
+ if (client->cdev->ctrl) {
|
|
+ for (i = 0; i < SSH_NUM_EVENTS; i++)
|
|
+ ssam_cdev_notifier_unregister(client, i + 1);
|
|
+
|
|
+ } else {
|
|
+ int count = 0;
|
|
+
|
|
+ /*
|
|
+ * Device has been shut down. Any notifier remaining is a bug,
|
|
+ * so warn about that as this would otherwise hardly be
|
|
+ * noticeable. Nevertheless, free them as well.
|
|
+ */
|
|
+ mutex_lock(&client->notifier_lock);
|
|
+ for (i = 0; i < SSH_NUM_EVENTS; i++) {
|
|
+ count += !!(client->notifier[i]);
|
|
+ kfree(client->notifier[i]);
|
|
+ client->notifier[i] = NULL;
|
|
+ }
|
|
+ mutex_unlock(&client->notifier_lock);
|
|
+
|
|
+ WARN_ON(count > 0);
|
|
+ }
|
|
+
|
|
+ up_read(&client->cdev->lock);
|
|
+}
|
|
+
|
|
+
|
|
+/* -- IOCTL functions. ------------------------------------------------------ */
|
|
+
|
|
+static long ssam_cdev_request(struct ssam_cdev_client *client, struct ssam_cdev_request __user *r)
|
|
{
|
|
- struct ssam_cdev_request __user *r;
|
|
struct ssam_cdev_request rqst;
|
|
struct ssam_request spec = {};
|
|
struct ssam_response rsp = {};
|
|
@@ -72,7 +254,6 @@ static long ssam_cdev_request(struct ssam_cdev *cdev, unsigned long arg)
|
|
void __user *rspdata;
|
|
int status = 0, ret = 0, tmp;
|
|
|
|
- r = (struct ssam_cdev_request __user *)arg;
|
|
ret = copy_struct_from_user(&rqst, sizeof(rqst), r, sizeof(*r));
|
|
if (ret)
|
|
goto out;
|
|
@@ -152,7 +333,7 @@ static long ssam_cdev_request(struct ssam_cdev *cdev, unsigned long arg)
|
|
}
|
|
|
|
/* Perform request. */
|
|
- status = ssam_request_sync(cdev->ctrl, &spec, &rsp);
|
|
+ status = ssam_request_sync(client->cdev->ctrl, &spec, &rsp);
|
|
if (status)
|
|
goto out;
|
|
|
|
@@ -177,48 +358,244 @@ static long ssam_cdev_request(struct ssam_cdev *cdev, unsigned long arg)
|
|
return ret;
|
|
}
|
|
|
|
-static long __ssam_cdev_device_ioctl(struct ssam_cdev *cdev, unsigned int cmd,
|
|
+static long ssam_cdev_notif_register(struct ssam_cdev_client *client,
|
|
+ const struct ssam_cdev_notifier_desc __user *d)
|
|
+{
|
|
+ struct ssam_cdev_notifier_desc desc;
|
|
+ long ret;
|
|
+
|
|
+ ret = copy_struct_from_user(&desc, sizeof(desc), d, sizeof(*d));
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ return ssam_cdev_notifier_register(client, desc.target_category, desc.priority);
|
|
+}
|
|
+
|
|
+static long ssam_cdev_notif_unregister(struct ssam_cdev_client *client,
|
|
+ const struct ssam_cdev_notifier_desc __user *d)
|
|
+{
|
|
+ struct ssam_cdev_notifier_desc desc;
|
|
+ long ret;
|
|
+
|
|
+ ret = copy_struct_from_user(&desc, sizeof(desc), d, sizeof(*d));
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ return ssam_cdev_notifier_unregister(client, desc.target_category);
|
|
+}
|
|
+
|
|
+
|
|
+/* -- File operations. ------------------------------------------------------ */
|
|
+
|
|
+static int ssam_cdev_device_open(struct inode *inode, struct file *filp)
|
|
+{
|
|
+ struct miscdevice *mdev = filp->private_data;
|
|
+ struct ssam_cdev_client *client;
|
|
+ struct ssam_cdev *cdev = container_of(mdev, struct ssam_cdev, mdev);
|
|
+
|
|
+ /* Initialize client */
|
|
+ client = vzalloc(sizeof(*client));
|
|
+ if (!client)
|
|
+ return -ENOMEM;
|
|
+
|
|
+ client->cdev = ssam_cdev_get(cdev);
|
|
+
|
|
+ INIT_LIST_HEAD(&client->node);
|
|
+
|
|
+ mutex_init(&client->notifier_lock);
|
|
+
|
|
+ mutex_init(&client->read_lock);
|
|
+ mutex_init(&client->write_lock);
|
|
+ INIT_KFIFO(client->buffer);
|
|
+ init_waitqueue_head(&client->waitq);
|
|
+
|
|
+ filp->private_data = client;
|
|
+
|
|
+ /* Attach client. */
|
|
+ down_write(&cdev->client_lock);
|
|
+
|
|
+ if (test_bit(SSAM_CDEV_DEVICE_SHUTDOWN_BIT, &cdev->flags)) {
|
|
+ up_write(&cdev->client_lock);
|
|
+ ssam_cdev_put(client->cdev);
|
|
+ vfree(client);
|
|
+ return -ENODEV;
|
|
+ }
|
|
+ list_add_tail(&client->node, &cdev->client_list);
|
|
+
|
|
+ up_write(&cdev->client_lock);
|
|
+
|
|
+ stream_open(inode, filp);
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int ssam_cdev_device_release(struct inode *inode, struct file *filp)
|
|
+{
|
|
+ struct ssam_cdev_client *client = filp->private_data;
|
|
+
|
|
+ /* Force-unregister all remaining notifiers of this client. */
|
|
+ ssam_cdev_notifier_unregister_all(client);
|
|
+
|
|
+ /* Detach client. */
|
|
+ down_write(&client->cdev->client_lock);
|
|
+ list_del(&client->node);
|
|
+ up_write(&client->cdev->client_lock);
|
|
+
|
|
+ /* Free client. */
|
|
+ mutex_destroy(&client->write_lock);
|
|
+ mutex_destroy(&client->read_lock);
|
|
+
|
|
+ mutex_destroy(&client->notifier_lock);
|
|
+
|
|
+ ssam_cdev_put(client->cdev);
|
|
+ vfree(client);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static long __ssam_cdev_device_ioctl(struct ssam_cdev_client *client, unsigned int cmd,
|
|
unsigned long arg)
|
|
{
|
|
switch (cmd) {
|
|
case SSAM_CDEV_REQUEST:
|
|
- return ssam_cdev_request(cdev, arg);
|
|
+ return ssam_cdev_request(client, (struct ssam_cdev_request __user *)arg);
|
|
+
|
|
+ case SSAM_CDEV_NOTIF_REGISTER:
|
|
+ return ssam_cdev_notif_register(client,
|
|
+ (struct ssam_cdev_notifier_desc __user *)arg);
|
|
+
|
|
+ case SSAM_CDEV_NOTIF_UNREGISTER:
|
|
+ return ssam_cdev_notif_unregister(client,
|
|
+ (struct ssam_cdev_notifier_desc __user *)arg);
|
|
|
|
default:
|
|
return -ENOTTY;
|
|
}
|
|
}
|
|
|
|
-static long ssam_cdev_device_ioctl(struct file *file, unsigned int cmd,
|
|
- unsigned long arg)
|
|
+static long ssam_cdev_device_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
|
|
{
|
|
- struct ssam_cdev *cdev = file->private_data;
|
|
+ struct ssam_cdev_client *client = file->private_data;
|
|
long status;
|
|
|
|
/* Ensure that controller is valid for as long as we need it. */
|
|
+ if (down_read_killable(&client->cdev->lock))
|
|
+ return -ERESTARTSYS;
|
|
+
|
|
+ if (test_bit(SSAM_CDEV_DEVICE_SHUTDOWN_BIT, &client->cdev->flags)) {
|
|
+ up_read(&client->cdev->lock);
|
|
+ return -ENODEV;
|
|
+ }
|
|
+
|
|
+ status = __ssam_cdev_device_ioctl(client, cmd, arg);
|
|
+
|
|
+ up_read(&client->cdev->lock);
|
|
+ return status;
|
|
+}
|
|
+
|
|
+static ssize_t ssam_cdev_read(struct file *file, char __user *buf, size_t count, loff_t *offs)
|
|
+{
|
|
+ struct ssam_cdev_client *client = file->private_data;
|
|
+ struct ssam_cdev *cdev = client->cdev;
|
|
+ unsigned int copied;
|
|
+ int status = 0;
|
|
+
|
|
if (down_read_killable(&cdev->lock))
|
|
return -ERESTARTSYS;
|
|
|
|
- if (!cdev->ctrl) {
|
|
+ /* Make sure we're not shut down. */
|
|
+ if (test_bit(SSAM_CDEV_DEVICE_SHUTDOWN_BIT, &cdev->flags)) {
|
|
up_read(&cdev->lock);
|
|
return -ENODEV;
|
|
}
|
|
|
|
- status = __ssam_cdev_device_ioctl(cdev, cmd, arg);
|
|
+ do {
|
|
+ /* Check availability, wait if necessary. */
|
|
+ if (kfifo_is_empty(&client->buffer)) {
|
|
+ up_read(&cdev->lock);
|
|
+
|
|
+ if (file->f_flags & O_NONBLOCK)
|
|
+ return -EAGAIN;
|
|
+
|
|
+ status = wait_event_interruptible(client->waitq,
|
|
+ !kfifo_is_empty(&client->buffer) ||
|
|
+ test_bit(SSAM_CDEV_DEVICE_SHUTDOWN_BIT,
|
|
+ &cdev->flags));
|
|
+ if (status < 0)
|
|
+ return status;
|
|
+
|
|
+ if (down_read_killable(&cdev->lock))
|
|
+ return -ERESTARTSYS;
|
|
+
|
|
+ /* Need to check that we're not shut down again. */
|
|
+ if (test_bit(SSAM_CDEV_DEVICE_SHUTDOWN_BIT, &cdev->flags)) {
|
|
+ up_read(&cdev->lock);
|
|
+ return -ENODEV;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ /* Try to read from FIFO. */
|
|
+ if (mutex_lock_interruptible(&client->read_lock)) {
|
|
+ up_read(&cdev->lock);
|
|
+ return -ERESTARTSYS;
|
|
+ }
|
|
+
|
|
+ status = kfifo_to_user(&client->buffer, buf, count, &copied);
|
|
+ mutex_unlock(&client->read_lock);
|
|
+
|
|
+ if (status < 0) {
|
|
+ up_read(&cdev->lock);
|
|
+ return status;
|
|
+ }
|
|
+
|
|
+ /* We might not have gotten anything, check this here. */
|
|
+ if (copied == 0 && (file->f_flags & O_NONBLOCK)) {
|
|
+ up_read(&cdev->lock);
|
|
+ return -EAGAIN;
|
|
+ }
|
|
+ } while (copied == 0);
|
|
|
|
up_read(&cdev->lock);
|
|
- return status;
|
|
+ return copied;
|
|
+}
|
|
+
|
|
+static __poll_t ssam_cdev_poll(struct file *file, struct poll_table_struct *pt)
|
|
+{
|
|
+ struct ssam_cdev_client *client = file->private_data;
|
|
+ __poll_t events = 0;
|
|
+
|
|
+ if (test_bit(SSAM_CDEV_DEVICE_SHUTDOWN_BIT, &client->cdev->flags))
|
|
+ return EPOLLHUP | EPOLLERR;
|
|
+
|
|
+ poll_wait(file, &client->waitq, pt);
|
|
+
|
|
+ if (!kfifo_is_empty(&client->buffer))
|
|
+ events |= EPOLLIN | EPOLLRDNORM;
|
|
+
|
|
+ return events;
|
|
+}
|
|
+
|
|
+static int ssam_cdev_fasync(int fd, struct file *file, int on)
|
|
+{
|
|
+ struct ssam_cdev_client *client = file->private_data;
|
|
+
|
|
+ return fasync_helper(fd, file, on, &client->fasync);
|
|
}
|
|
|
|
static const struct file_operations ssam_controller_fops = {
|
|
.owner = THIS_MODULE,
|
|
.open = ssam_cdev_device_open,
|
|
.release = ssam_cdev_device_release,
|
|
+ .read = ssam_cdev_read,
|
|
+ .poll = ssam_cdev_poll,
|
|
+ .fasync = ssam_cdev_fasync,
|
|
.unlocked_ioctl = ssam_cdev_device_ioctl,
|
|
.compat_ioctl = ssam_cdev_device_ioctl,
|
|
- .llseek = noop_llseek,
|
|
+ .llseek = no_llseek,
|
|
};
|
|
|
|
+
|
|
+/* -- Device and driver setup ----------------------------------------------- */
|
|
+
|
|
static int ssam_dbg_device_probe(struct platform_device *pdev)
|
|
{
|
|
struct ssam_controller *ctrl;
|
|
@@ -236,6 +613,7 @@ static int ssam_dbg_device_probe(struct platform_device *pdev)
|
|
kref_init(&cdev->kref);
|
|
init_rwsem(&cdev->lock);
|
|
cdev->ctrl = ctrl;
|
|
+ cdev->dev = &pdev->dev;
|
|
|
|
cdev->mdev.parent = &pdev->dev;
|
|
cdev->mdev.minor = MISC_DYNAMIC_MINOR;
|
|
@@ -243,6 +621,9 @@ static int ssam_dbg_device_probe(struct platform_device *pdev)
|
|
cdev->mdev.nodename = "surface/aggregator";
|
|
cdev->mdev.fops = &ssam_controller_fops;
|
|
|
|
+ init_rwsem(&cdev->client_lock);
|
|
+ INIT_LIST_HEAD(&cdev->client_list);
|
|
+
|
|
status = misc_register(&cdev->mdev);
|
|
if (status) {
|
|
kfree(cdev);
|
|
@@ -256,8 +637,32 @@ static int ssam_dbg_device_probe(struct platform_device *pdev)
|
|
static int ssam_dbg_device_remove(struct platform_device *pdev)
|
|
{
|
|
struct ssam_cdev *cdev = platform_get_drvdata(pdev);
|
|
+ struct ssam_cdev_client *client;
|
|
|
|
- misc_deregister(&cdev->mdev);
|
|
+ /*
|
|
+ * Mark device as shut-down. Prevent new clients from being added and
|
|
+ * new operations from being executed.
|
|
+ */
|
|
+ set_bit(SSAM_CDEV_DEVICE_SHUTDOWN_BIT, &cdev->flags);
|
|
+
|
|
+ down_write(&cdev->client_lock);
|
|
+
|
|
+ /* Remove all notifiers registered by us. */
|
|
+ list_for_each_entry(client, &cdev->client_list, node) {
|
|
+ ssam_cdev_notifier_unregister_all(client);
|
|
+ }
|
|
+
|
|
+ /* Wake up async clients. */
|
|
+ list_for_each_entry(client, &cdev->client_list, node) {
|
|
+ kill_fasync(&client->fasync, SIGIO, POLL_HUP);
|
|
+ }
|
|
+
|
|
+ /* Wake up blocking clients. */
|
|
+ list_for_each_entry(client, &cdev->client_list, node) {
|
|
+ wake_up_interruptible(&client->waitq);
|
|
+ }
|
|
+
|
|
+ up_write(&cdev->client_lock);
|
|
|
|
/*
|
|
* The controller is only guaranteed to be valid for as long as the
|
|
@@ -266,8 +671,11 @@ static int ssam_dbg_device_remove(struct platform_device *pdev)
|
|
*/
|
|
down_write(&cdev->lock);
|
|
cdev->ctrl = NULL;
|
|
+ cdev->dev = NULL;
|
|
up_write(&cdev->lock);
|
|
|
|
+ misc_deregister(&cdev->mdev);
|
|
+
|
|
ssam_cdev_put(cdev);
|
|
return 0;
|
|
}
|
|
diff --git a/include/uapi/linux/surface_aggregator/cdev.h b/include/uapi/linux/surface_aggregator/cdev.h
|
|
index fbcce04abfe9..4f393fafc235 100644
|
|
--- a/include/uapi/linux/surface_aggregator/cdev.h
|
|
+++ b/include/uapi/linux/surface_aggregator/cdev.h
|
|
@@ -6,7 +6,7 @@
|
|
* device. This device provides direct user-space access to the SSAM EC.
|
|
* Intended for debugging and development.
|
|
*
|
|
- * Copyright (C) 2020 Maximilian Luz <luzmaximilian@gmail.com>
|
|
+ * Copyright (C) 2020-2021 Maximilian Luz <luzmaximilian@gmail.com>
|
|
*/
|
|
|
|
#ifndef _UAPI_LINUX_SURFACE_AGGREGATOR_CDEV_H
|
|
@@ -73,6 +73,43 @@ struct ssam_cdev_request {
|
|
} response;
|
|
} __attribute__((__packed__));
|
|
|
|
-#define SSAM_CDEV_REQUEST _IOWR(0xA5, 1, struct ssam_cdev_request)
|
|
+/**
|
|
+ * struct ssam_cdev_notifier_desc - Notifier descriptor.
|
|
+ * @priority: Priority value determining the order in which notifier
|
|
+ * callbacks will be called. A higher value means higher
|
|
+ * priority, i.e. the associated callback will be executed
|
|
+ * earlier than other (lower priority) callbacks.
|
|
+ * @target_category: The event target category for which this notifier should
|
|
+ * receive events.
|
|
+ *
|
|
+ * Specifies the notifier that should be registered or unregistered,
|
|
+ * specifically with which priority and for which target category of events.
|
|
+ */
|
|
+struct ssam_cdev_notifier_desc {
|
|
+ __s32 priority;
|
|
+ __u8 target_category;
|
|
+} __attribute__((__packed__));
|
|
+
|
|
+/**
|
|
+ * struct ssam_cdev_event - SSAM event sent by the EC.
|
|
+ * @target_category: Target category of the event source. See &enum ssam_ssh_tc.
|
|
+ * @target_id: Target ID of the event source.
|
|
+ * @command_id: Command ID of the event.
|
|
+ * @instance_id: Instance ID of the event source.
|
|
+ * @length: Length of the event payload in bytes.
|
|
+ * @data: Event payload data.
|
|
+ */
|
|
+struct ssam_cdev_event {
|
|
+ __u8 target_category;
|
|
+ __u8 target_id;
|
|
+ __u8 command_id;
|
|
+ __u8 instance_id;
|
|
+ __u16 length;
|
|
+ __u8 data[];
|
|
+} __attribute__((__packed__));
|
|
+
|
|
+#define SSAM_CDEV_REQUEST _IOWR(0xA5, 1, struct ssam_cdev_request)
|
|
+#define SSAM_CDEV_NOTIF_REGISTER _IOW(0xA5, 2, struct ssam_cdev_notifier_desc)
|
|
+#define SSAM_CDEV_NOTIF_UNREGISTER _IOW(0xA5, 3, struct ssam_cdev_notifier_desc)
|
|
|
|
#endif /* _UAPI_LINUX_SURFACE_AGGREGATOR_CDEV_H */
|
|
--
|
|
2.32.0
|
|
|
|
From 6c1af5eaf46d1462ebf82ec40fee70cdc5d37405 Mon Sep 17 00:00:00 2001
|
|
From: Maximilian Luz <luzmaximilian@gmail.com>
|
|
Date: Wed, 2 Jun 2021 19:30:42 +0200
|
|
Subject: [PATCH] platform/surface: aggregator_cdev: Allow enabling of events
|
|
from user-space
|
|
|
|
While events can already be enabled and disabled via the generic request
|
|
IOCTL, this bypasses the internal reference counting mechanism of the
|
|
controller. Due to that, disabling an event will turn it off regardless
|
|
of any other client having requested said event, which may break
|
|
functionality of that client.
|
|
|
|
To solve this, add IOCTLs wrapping the ssam_controller_event_enable()
|
|
and ssam_controller_event_disable() functions, which have been
|
|
previously introduced for this specific purpose.
|
|
|
|
Signed-off-by: Maximilian Luz <luzmaximilian@gmail.com>
|
|
Patchset: surface-sam
|
|
---
|
|
.../surface/surface_aggregator_cdev.c | 58 +++++++++++++++++++
|
|
include/uapi/linux/surface_aggregator/cdev.h | 32 ++++++++++
|
|
2 files changed, 90 insertions(+)
|
|
|
|
diff --git a/drivers/platform/surface/surface_aggregator_cdev.c b/drivers/platform/surface/surface_aggregator_cdev.c
|
|
index 7b27c8ca38a5..55bf55c93624 100644
|
|
--- a/drivers/platform/surface/surface_aggregator_cdev.c
|
|
+++ b/drivers/platform/surface/surface_aggregator_cdev.c
|
|
@@ -384,6 +384,58 @@ static long ssam_cdev_notif_unregister(struct ssam_cdev_client *client,
|
|
return ssam_cdev_notifier_unregister(client, desc.target_category);
|
|
}
|
|
|
|
+static long ssam_cdev_event_enable(struct ssam_cdev_client *client,
|
|
+ const struct ssam_cdev_event_desc __user *d)
|
|
+{
|
|
+ struct ssam_cdev_event_desc desc;
|
|
+ struct ssam_event_registry reg;
|
|
+ struct ssam_event_id id;
|
|
+ long ret;
|
|
+
|
|
+ /* Read descriptor from user-space. */
|
|
+ ret = copy_struct_from_user(&desc, sizeof(desc), d, sizeof(*d));
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ /* Translate descriptor. */
|
|
+ reg.target_category = desc.reg.target_category;
|
|
+ reg.target_id = desc.reg.target_id;
|
|
+ reg.cid_enable = desc.reg.cid_enable;
|
|
+ reg.cid_disable = desc.reg.cid_disable;
|
|
+
|
|
+ id.target_category = desc.id.target_category;
|
|
+ id.instance = desc.id.instance;
|
|
+
|
|
+ /* Disable event. */
|
|
+ return ssam_controller_event_enable(client->cdev->ctrl, reg, id, desc.flags);
|
|
+}
|
|
+
|
|
+static long ssam_cdev_event_disable(struct ssam_cdev_client *client,
|
|
+ const struct ssam_cdev_event_desc __user *d)
|
|
+{
|
|
+ struct ssam_cdev_event_desc desc;
|
|
+ struct ssam_event_registry reg;
|
|
+ struct ssam_event_id id;
|
|
+ long ret;
|
|
+
|
|
+ /* Read descriptor from user-space. */
|
|
+ ret = copy_struct_from_user(&desc, sizeof(desc), d, sizeof(*d));
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ /* Translate descriptor. */
|
|
+ reg.target_category = desc.reg.target_category;
|
|
+ reg.target_id = desc.reg.target_id;
|
|
+ reg.cid_enable = desc.reg.cid_enable;
|
|
+ reg.cid_disable = desc.reg.cid_disable;
|
|
+
|
|
+ id.target_category = desc.id.target_category;
|
|
+ id.instance = desc.id.instance;
|
|
+
|
|
+ /* Disable event. */
|
|
+ return ssam_controller_event_disable(client->cdev->ctrl, reg, id, desc.flags);
|
|
+}
|
|
+
|
|
|
|
/* -- File operations. ------------------------------------------------------ */
|
|
|
|
@@ -467,6 +519,12 @@ static long __ssam_cdev_device_ioctl(struct ssam_cdev_client *client, unsigned i
|
|
return ssam_cdev_notif_unregister(client,
|
|
(struct ssam_cdev_notifier_desc __user *)arg);
|
|
|
|
+ case SSAM_CDEV_EVENT_ENABLE:
|
|
+ return ssam_cdev_event_enable(client, (struct ssam_cdev_event_desc __user *)arg);
|
|
+
|
|
+ case SSAM_CDEV_EVENT_DISABLE:
|
|
+ return ssam_cdev_event_disable(client, (struct ssam_cdev_event_desc __user *)arg);
|
|
+
|
|
default:
|
|
return -ENOTTY;
|
|
}
|
|
diff --git a/include/uapi/linux/surface_aggregator/cdev.h b/include/uapi/linux/surface_aggregator/cdev.h
|
|
index 4f393fafc235..08f46b60b151 100644
|
|
--- a/include/uapi/linux/surface_aggregator/cdev.h
|
|
+++ b/include/uapi/linux/surface_aggregator/cdev.h
|
|
@@ -90,6 +90,36 @@ struct ssam_cdev_notifier_desc {
|
|
__u8 target_category;
|
|
} __attribute__((__packed__));
|
|
|
|
+/**
|
|
+ * struct ssam_cdev_event_desc - Event descriptor.
|
|
+ * @reg: Registry via which the event will be enabled/disabled.
|
|
+ * @reg.target_category: Target category for the event registry requests.
|
|
+ * @reg.target_id: Target ID for the event registry requests.
|
|
+ * @reg.cid_enable: Command ID for the event-enable request.
|
|
+ * @reg.cid_disable: Command ID for the event-disable request.
|
|
+ * @id: ID specifying the event.
|
|
+ * @id.target_category: Target category of the event source.
|
|
+ * @id.instance: Instance ID of the event source.
|
|
+ * @flags: Flags used for enabling the event.
|
|
+ *
|
|
+ * Specifies which event should be enabled/disabled and how to do that.
|
|
+ */
|
|
+struct ssam_cdev_event_desc {
|
|
+ struct {
|
|
+ __u8 target_category;
|
|
+ __u8 target_id;
|
|
+ __u8 cid_enable;
|
|
+ __u8 cid_disable;
|
|
+ } reg;
|
|
+
|
|
+ struct {
|
|
+ __u8 target_category;
|
|
+ __u8 instance;
|
|
+ } id;
|
|
+
|
|
+ __u8 flags;
|
|
+} __attribute__((__packed__));
|
|
+
|
|
/**
|
|
* struct ssam_cdev_event - SSAM event sent by the EC.
|
|
* @target_category: Target category of the event source. See &enum ssam_ssh_tc.
|
|
@@ -111,5 +141,7 @@ struct ssam_cdev_event {
|
|
#define SSAM_CDEV_REQUEST _IOWR(0xA5, 1, struct ssam_cdev_request)
|
|
#define SSAM_CDEV_NOTIF_REGISTER _IOW(0xA5, 2, struct ssam_cdev_notifier_desc)
|
|
#define SSAM_CDEV_NOTIF_UNREGISTER _IOW(0xA5, 3, struct ssam_cdev_notifier_desc)
|
|
+#define SSAM_CDEV_EVENT_ENABLE _IOW(0xA5, 4, struct ssam_cdev_event_desc)
|
|
+#define SSAM_CDEV_EVENT_DISABLE _IOW(0xA5, 5, struct ssam_cdev_event_desc)
|
|
|
|
#endif /* _UAPI_LINUX_SURFACE_AGGREGATOR_CDEV_H */
|
|
--
|
|
2.32.0
|
|
|
|
From 38fd84ffd2930a1800f8bbb308536096394b31dc Mon Sep 17 00:00:00 2001
|
|
From: Maximilian Luz <luzmaximilian@gmail.com>
|
|
Date: Wed, 2 Jun 2021 19:38:07 +0200
|
|
Subject: [PATCH] platform/surface: aggregator_cdev: Add lockdep support
|
|
|
|
Mark functions with locking requirements via the corresponding lockdep
|
|
calls for debugging and documentary purposes.
|
|
|
|
Signed-off-by: Maximilian Luz <luzmaximilian@gmail.com>
|
|
Patchset: surface-sam
|
|
---
|
|
.../platform/surface/surface_aggregator_cdev.c | 16 ++++++++++++++++
|
|
1 file changed, 16 insertions(+)
|
|
|
|
diff --git a/drivers/platform/surface/surface_aggregator_cdev.c b/drivers/platform/surface/surface_aggregator_cdev.c
|
|
index 55bf55c93624..2cad4147645c 100644
|
|
--- a/drivers/platform/surface/surface_aggregator_cdev.c
|
|
+++ b/drivers/platform/surface/surface_aggregator_cdev.c
|
|
@@ -137,6 +137,8 @@ static int ssam_cdev_notifier_register(struct ssam_cdev_client *client, u8 categ
|
|
int index = ((int)category) - 1;
|
|
int status;
|
|
|
|
+ lockdep_assert_held_read(&client->cdev->lock);
|
|
+
|
|
/* Validate notifier target category. */
|
|
if (index < 0 || index >= SSH_NUM_EVENTS)
|
|
return -EINVAL;
|
|
@@ -185,6 +187,8 @@ static int ssam_cdev_notifier_unregister(struct ssam_cdev_client *client, u8 cat
|
|
int index = ((int)category) - 1;
|
|
int status;
|
|
|
|
+ lockdep_assert_held_read(&client->cdev->lock);
|
|
+
|
|
/* Validate notifier target category. */
|
|
if (index < 0 || index >= SSH_NUM_EVENTS)
|
|
return -EINVAL;
|
|
@@ -254,6 +258,8 @@ static long ssam_cdev_request(struct ssam_cdev_client *client, struct ssam_cdev_
|
|
void __user *rspdata;
|
|
int status = 0, ret = 0, tmp;
|
|
|
|
+ lockdep_assert_held_read(&client->cdev->lock);
|
|
+
|
|
ret = copy_struct_from_user(&rqst, sizeof(rqst), r, sizeof(*r));
|
|
if (ret)
|
|
goto out;
|
|
@@ -364,6 +370,8 @@ static long ssam_cdev_notif_register(struct ssam_cdev_client *client,
|
|
struct ssam_cdev_notifier_desc desc;
|
|
long ret;
|
|
|
|
+ lockdep_assert_held_read(&client->cdev->lock);
|
|
+
|
|
ret = copy_struct_from_user(&desc, sizeof(desc), d, sizeof(*d));
|
|
if (ret)
|
|
return ret;
|
|
@@ -377,6 +385,8 @@ static long ssam_cdev_notif_unregister(struct ssam_cdev_client *client,
|
|
struct ssam_cdev_notifier_desc desc;
|
|
long ret;
|
|
|
|
+ lockdep_assert_held_read(&client->cdev->lock);
|
|
+
|
|
ret = copy_struct_from_user(&desc, sizeof(desc), d, sizeof(*d));
|
|
if (ret)
|
|
return ret;
|
|
@@ -392,6 +402,8 @@ static long ssam_cdev_event_enable(struct ssam_cdev_client *client,
|
|
struct ssam_event_id id;
|
|
long ret;
|
|
|
|
+ lockdep_assert_held_read(&client->cdev->lock);
|
|
+
|
|
/* Read descriptor from user-space. */
|
|
ret = copy_struct_from_user(&desc, sizeof(desc), d, sizeof(*d));
|
|
if (ret)
|
|
@@ -418,6 +430,8 @@ static long ssam_cdev_event_disable(struct ssam_cdev_client *client,
|
|
struct ssam_event_id id;
|
|
long ret;
|
|
|
|
+ lockdep_assert_held_read(&client->cdev->lock);
|
|
+
|
|
/* Read descriptor from user-space. */
|
|
ret = copy_struct_from_user(&desc, sizeof(desc), d, sizeof(*d));
|
|
if (ret)
|
|
@@ -507,6 +521,8 @@ static int ssam_cdev_device_release(struct inode *inode, struct file *filp)
|
|
static long __ssam_cdev_device_ioctl(struct ssam_cdev_client *client, unsigned int cmd,
|
|
unsigned long arg)
|
|
{
|
|
+ lockdep_assert_held_read(&client->cdev->lock);
|
|
+
|
|
switch (cmd) {
|
|
case SSAM_CDEV_REQUEST:
|
|
return ssam_cdev_request(client, (struct ssam_cdev_request __user *)arg);
|
|
--
|
|
2.32.0
|
|
|
|
From f8a8ca2b134d58d8f253acb92e83ae0d20cbedca Mon Sep 17 00:00:00 2001
|
|
From: Maximilian Luz <luzmaximilian@gmail.com>
|
|
Date: Fri, 4 Jun 2021 22:28:41 +0200
|
|
Subject: [PATCH] platform/surface: aggregator: Fixups for user-space event
|
|
forwarding series
|
|
|
|
Patchset: surface-sam
|
|
---
|
|
.../platform/surface/aggregator/controller.c | 248 +++++++++++-------
|
|
.../surface/surface_aggregator_cdev.c | 32 ++-
|
|
2 files changed, 174 insertions(+), 106 deletions(-)
|
|
|
|
diff --git a/drivers/platform/surface/aggregator/controller.c b/drivers/platform/surface/aggregator/controller.c
|
|
index e91ee7e72c14..6646f4d6e10d 100644
|
|
--- a/drivers/platform/surface/aggregator/controller.c
|
|
+++ b/drivers/platform/surface/aggregator/controller.c
|
|
@@ -407,6 +407,31 @@ ssam_nf_refcount_dec(struct ssam_nf *nf, struct ssam_event_registry reg,
|
|
return NULL;
|
|
}
|
|
|
|
+/**
|
|
+ * ssam_nf_refcount_dec_free() - Decrement reference-/activation-count of the
|
|
+ * given event and free its entry if the reference count reaches zero.
|
|
+ * @nf: The notifier system reference.
|
|
+ * @reg: The registry used to enable/disable the event.
|
|
+ * @id: The event ID.
|
|
+ *
|
|
+ * Decrements the reference-/activation-count of the specified event, freeing
|
|
+ * its entry if it reaches zero.
|
|
+ *
|
|
+ * Note: ``nf->lock`` must be held when calling this function.
|
|
+ */
|
|
+static void ssam_nf_refcount_dec_free(struct ssam_nf *nf,
|
|
+ struct ssam_event_registry reg,
|
|
+ struct ssam_event_id id)
|
|
+{
|
|
+ struct ssam_nf_refcount_entry *entry;
|
|
+
|
|
+ lockdep_assert_held(&nf->lock);
|
|
+
|
|
+ entry = ssam_nf_refcount_dec(nf, reg, id);
|
|
+ if (entry && entry->refcount == 0)
|
|
+ kfree(entry);
|
|
+}
|
|
+
|
|
/**
|
|
* ssam_nf_refcount_empty() - Test if the notification system has any
|
|
* enabled/active events.
|
|
@@ -2122,6 +2147,109 @@ int ssam_ctrl_notif_d0_entry(struct ssam_controller *ctrl)
|
|
|
|
/* -- Top-level event registry interface. ----------------------------------- */
|
|
|
|
+/**
|
|
+ * ssam_nf_refcount_enable() - Enable event for reference count entry if it has
|
|
+ * not already been enabled.
|
|
+ * @ctrl: The controller to enable the event on.
|
|
+ * @entry: The reference count entry for the event to be enabled.
|
|
+ * @flags: The flags used for enabling the event on the EC.
|
|
+ *
|
|
+ * Enable the event associated with the given reference count entry if the
|
|
+ * reference count equals one, i.e. the event has not previously been enabled.
|
|
+ * If the event has already been enabled (i.e. reference count not equal to
|
|
+ * one), check that the flags used for enabling match and warn about this if
|
|
+ * they do not.
|
|
+ *
|
|
+ * This does not modify the reference count itself, which is done with
|
|
+ * ssam_nf_refcount_inc() / ssam_nf_refcount_dec().
|
|
+ *
|
|
+ * Note: ``nf->lock`` must be held when calling this function.
|
|
+ *
|
|
+ * Return: Returns zero on success. If the event is enabled by this call,
|
|
+ * returns the status of the event-enable EC command.
|
|
+ */
|
|
+static int ssam_nf_refcount_enable(struct ssam_controller *ctrl,
|
|
+ struct ssam_nf_refcount_entry *entry, u8 flags)
|
|
+{
|
|
+ const struct ssam_event_registry reg = entry->key.reg;
|
|
+ const struct ssam_event_id id = entry->key.id;
|
|
+ struct ssam_nf *nf = &ctrl->cplt.event.notif;
|
|
+ int status;
|
|
+
|
|
+ lockdep_assert_held(&nf->lock);
|
|
+
|
|
+ ssam_dbg(ctrl, "enabling event (reg: %#04x, tc: %#04x, iid: %#04x, rc: %d)\n",
|
|
+ reg.target_category, id.target_category, id.instance, entry->refcount);
|
|
+
|
|
+ if (entry->refcount == 1) {
|
|
+ status = ssam_ssh_event_enable(ctrl, reg, id, flags);
|
|
+ if (status)
|
|
+ return status;
|
|
+
|
|
+ entry->flags = flags;
|
|
+
|
|
+ } else if (entry->flags != flags) {
|
|
+ ssam_warn(ctrl,
|
|
+ "inconsistent flags when enabling event: got %#04x, expected %#04x (reg: %#04x, tc: %#04x, iid: %#04x)\n",
|
|
+ flags, entry->flags, reg.target_category, id.target_category,
|
|
+ id.instance);
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+/**
|
|
+ * ssam_nf_refcount_disable_free() - Disable event for reference count entry if it is
|
|
+ * no longer in use and free the corresponding entry.
|
|
+ * @ctrl: The controller to disable the event on.
|
|
+ * @entry: The reference count entry for the event to be disabled.
|
|
+ * @flags: The flags used for enabling the event on the EC.
|
|
+ *
|
|
+ * If the reference count equals zero, i.e. the event is no longer requested by
|
|
+ * any client, the event will be disabled and the corresponding reference count
|
|
+ * entry freed. The reference count entry must not be used any more after a
|
|
+ * call to this function.
|
|
+ *
|
|
+ * Also checks if the flags used for disabling the event match the flags used
|
|
+ * for enabling the event and warns if they do not (regardless of reference
|
|
+ * count).
|
|
+ *
|
|
+ * This does not modify the reference count itself, which is done with
|
|
+ * ssam_nf_refcount_inc() / ssam_nf_refcount_dec().
|
|
+ *
|
|
+ * Note: ``nf->lock`` must be held when calling this function.
|
|
+ *
|
|
+ * Return: Returns zero on success. If the event is disabled by this call,
|
|
+ * returns the status of the event-enable EC command.
|
|
+ */
|
|
+static int ssam_nf_refcount_disable_free(struct ssam_controller *ctrl,
|
|
+ struct ssam_nf_refcount_entry *entry, u8 flags)
|
|
+{
|
|
+ const struct ssam_event_registry reg = entry->key.reg;
|
|
+ const struct ssam_event_id id = entry->key.id;
|
|
+ struct ssam_nf *nf = &ctrl->cplt.event.notif;
|
|
+ int status;
|
|
+
|
|
+ lockdep_assert_held(&nf->lock);
|
|
+
|
|
+ ssam_dbg(ctrl, "disabling event (reg: %#04x, tc: %#04x, iid: %#04x, rc: %d)\n",
|
|
+ reg.target_category, id.target_category, id.instance, entry->refcount);
|
|
+
|
|
+ if (entry->flags != flags) {
|
|
+ ssam_warn(ctrl,
|
|
+ "inconsistent flags when disabling event: got %#04x, expected %#04x (reg: %#04x, tc: %#04x, iid: %#04x)\n",
|
|
+ flags, entry->flags, reg.target_category, id.target_category,
|
|
+ id.instance);
|
|
+ }
|
|
+
|
|
+ if (entry->refcount == 0) {
|
|
+ status = ssam_ssh_event_disable(ctrl, reg, id, flags);
|
|
+ kfree(entry);
|
|
+ }
|
|
+
|
|
+ return status;
|
|
+}
|
|
+
|
|
/**
|
|
* ssam_notifier_register() - Register an event notifier.
|
|
* @ctrl: The controller to register the notifier on.
|
|
@@ -2166,41 +2294,26 @@ int ssam_notifier_register(struct ssam_controller *ctrl, struct ssam_event_notif
|
|
mutex_unlock(&nf->lock);
|
|
return PTR_ERR(entry);
|
|
}
|
|
-
|
|
- ssam_dbg(ctrl, "enabling event (reg: %#04x, tc: %#04x, iid: %#04x, rc: %d)\n",
|
|
- n->event.reg.target_category, n->event.id.target_category,
|
|
- n->event.id.instance, entry->refcount);
|
|
}
|
|
|
|
status = ssam_nfblk_insert(nf_head, &n->base);
|
|
if (status) {
|
|
- if (entry) {
|
|
- entry = ssam_nf_refcount_dec(nf, n->event.reg, n->event.id);
|
|
- if (entry->refcount == 0)
|
|
- kfree(entry);
|
|
- }
|
|
+ if (entry)
|
|
+ ssam_nf_refcount_dec_free(nf, n->event.reg, n->event.id);
|
|
|
|
mutex_unlock(&nf->lock);
|
|
return status;
|
|
}
|
|
|
|
- if (entry && entry->refcount == 1) {
|
|
- status = ssam_ssh_event_enable(ctrl, n->event.reg, n->event.id, n->event.flags);
|
|
+ if (entry) {
|
|
+ status = ssam_nf_refcount_enable(ctrl, entry, n->event.flags);
|
|
if (status) {
|
|
ssam_nfblk_remove(&n->base);
|
|
- kfree(ssam_nf_refcount_dec(nf, n->event.reg, n->event.id));
|
|
+ ssam_nf_refcount_dec_free(nf, n->event.reg, n->event.id);
|
|
mutex_unlock(&nf->lock);
|
|
synchronize_srcu(&nf_head->srcu);
|
|
return status;
|
|
}
|
|
-
|
|
- entry->flags = n->event.flags;
|
|
-
|
|
- } else if (entry && entry->flags != n->event.flags) {
|
|
- ssam_warn(ctrl,
|
|
- "inconsistent flags when enabling event: got %#04x, expected %#04x (reg: %#04x, tc: %#04x, iid: %#04x)\n",
|
|
- n->event.flags, entry->flags, n->event.reg.target_category,
|
|
- n->event.id.target_category, n->event.id.instance);
|
|
}
|
|
|
|
mutex_unlock(&nf->lock);
|
|
@@ -2247,35 +2360,20 @@ int ssam_notifier_unregister(struct ssam_controller *ctrl, struct ssam_event_not
|
|
* If this is an observer notifier, do not attempt to disable the
|
|
* event, just remove it.
|
|
*/
|
|
- if (n->flags & SSAM_EVENT_NOTIFIER_OBSERVER)
|
|
- goto remove;
|
|
-
|
|
- entry = ssam_nf_refcount_dec(nf, n->event.reg, n->event.id);
|
|
- if (WARN_ON(!entry)) {
|
|
- /*
|
|
- * If this does not return an entry, there's a logic error
|
|
- * somewhere: The notifier block is registered, but the event
|
|
- * refcount entry is not there. Remove the notifier block
|
|
- * anyways.
|
|
- */
|
|
- status = -ENOENT;
|
|
- goto remove;
|
|
- }
|
|
-
|
|
- ssam_dbg(ctrl, "disabling event (reg: %#04x, tc: %#04x, iid: %#04x, rc: %d)\n",
|
|
- n->event.reg.target_category, n->event.id.target_category,
|
|
- n->event.id.instance, entry->refcount);
|
|
+ if (!(n->flags & SSAM_EVENT_NOTIFIER_OBSERVER)) {
|
|
+ entry = ssam_nf_refcount_dec(nf, n->event.reg, n->event.id);
|
|
+ if (WARN_ON(!entry)) {
|
|
+ /*
|
|
+ * If this does not return an entry, there's a logic
|
|
+ * error somewhere: The notifier block is registered,
|
|
+ * but the event refcount entry is not there. Remove
|
|
+ * the notifier block anyways.
|
|
+ */
|
|
+ status = -ENOENT;
|
|
+ goto remove;
|
|
+ }
|
|
|
|
- if (entry->flags != n->event.flags) {
|
|
- ssam_warn(ctrl,
|
|
- "inconsistent flags when disabling event: got %#04x, expected %#04x (reg: %#04x, tc: %#04x, iid: %#04x)\n",
|
|
- n->event.flags, entry->flags, n->event.reg.target_category,
|
|
- n->event.id.target_category, n->event.id.instance);
|
|
- }
|
|
-
|
|
- if (entry->refcount == 0) {
|
|
- status = ssam_ssh_event_disable(ctrl, n->event.reg, n->event.id, n->event.flags);
|
|
- kfree(entry);
|
|
+ status = ssam_nf_refcount_disable_free(ctrl, entry, n->event.flags);
|
|
}
|
|
|
|
remove:
|
|
@@ -2313,17 +2411,13 @@ int ssam_controller_event_enable(struct ssam_controller *ctrl,
|
|
struct ssam_event_id id, u8 flags)
|
|
{
|
|
u16 rqid = ssh_tc_to_rqid(id.target_category);
|
|
+ struct ssam_nf *nf = &ctrl->cplt.event.notif;
|
|
struct ssam_nf_refcount_entry *entry;
|
|
- struct ssam_nf_head *nf_head;
|
|
- struct ssam_nf *nf;
|
|
int status;
|
|
|
|
if (!ssh_rqid_is_event(rqid))
|
|
return -EINVAL;
|
|
|
|
- nf = &ctrl->cplt.event.notif;
|
|
- nf_head = &nf->head[ssh_rqid_to_event(rqid)];
|
|
-
|
|
mutex_lock(&nf->lock);
|
|
|
|
entry = ssam_nf_refcount_inc(nf, reg, id);
|
|
@@ -2332,25 +2426,11 @@ int ssam_controller_event_enable(struct ssam_controller *ctrl,
|
|
return PTR_ERR(entry);
|
|
}
|
|
|
|
- ssam_dbg(ctrl, "enabling event (reg: %#04x, tc: %#04x, iid: %#04x, rc: %d)\n",
|
|
- reg.target_category, id.target_category, id.instance,
|
|
- entry->refcount);
|
|
-
|
|
- if (entry->refcount == 1) {
|
|
- status = ssam_ssh_event_enable(ctrl, reg, id, flags);
|
|
- if (status) {
|
|
- kfree(ssam_nf_refcount_dec(nf, reg, id));
|
|
- mutex_unlock(&nf->lock);
|
|
- return status;
|
|
- }
|
|
-
|
|
- entry->flags = flags;
|
|
-
|
|
- } else if (entry->flags != flags) {
|
|
- ssam_warn(ctrl,
|
|
- "inconsistent flags when enabling event: got %#04x, expected %#04x (reg: %#04x, tc: %#04x, iid: %#04x)\n",
|
|
- flags, entry->flags, reg.target_category,
|
|
- id.target_category, id.instance);
|
|
+ status = ssam_nf_refcount_enable(ctrl, entry, flags);
|
|
+ if (status) {
|
|
+ ssam_nf_refcount_dec_free(nf, reg, id);
|
|
+ mutex_unlock(&nf->lock);
|
|
+ return status;
|
|
}
|
|
|
|
mutex_unlock(&nf->lock);
|
|
@@ -2382,40 +2462,22 @@ int ssam_controller_event_disable(struct ssam_controller *ctrl,
|
|
struct ssam_event_id id, u8 flags)
|
|
{
|
|
u16 rqid = ssh_tc_to_rqid(id.target_category);
|
|
+ struct ssam_nf *nf = &ctrl->cplt.event.notif;
|
|
struct ssam_nf_refcount_entry *entry;
|
|
- struct ssam_nf_head *nf_head;
|
|
- struct ssam_nf *nf;
|
|
int status = 0;
|
|
|
|
if (!ssh_rqid_is_event(rqid))
|
|
return -EINVAL;
|
|
|
|
- nf = &ctrl->cplt.event.notif;
|
|
- nf_head = &nf->head[ssh_rqid_to_event(rqid)];
|
|
-
|
|
mutex_lock(&nf->lock);
|
|
|
|
entry = ssam_nf_refcount_dec(nf, reg, id);
|
|
- if (WARN_ON(!entry)) {
|
|
+ if (!entry) {
|
|
mutex_unlock(&nf->lock);
|
|
return -ENOENT;
|
|
}
|
|
|
|
- ssam_dbg(ctrl, "disabling event (reg: %#04x, tc: %#04x, iid: %#04x, rc: %d)\n",
|
|
- reg.target_category, id.target_category, id.instance,
|
|
- entry->refcount);
|
|
-
|
|
- if (entry->flags != flags) {
|
|
- ssam_warn(ctrl,
|
|
- "inconsistent flags when disabling event: got %#04x, expected %#04x (reg: %#04x, tc: %#04x, iid: %#04x)\n",
|
|
- flags, entry->flags, reg.target_category,
|
|
- id.target_category, id.instance);
|
|
- }
|
|
-
|
|
- if (entry->refcount == 0) {
|
|
- status = ssam_ssh_event_disable(ctrl, reg, id, flags);
|
|
- kfree(entry);
|
|
- }
|
|
+ status = ssam_nf_refcount_disable_free(ctrl, entry, flags);
|
|
|
|
mutex_unlock(&nf->lock);
|
|
return status;
|
|
diff --git a/drivers/platform/surface/surface_aggregator_cdev.c b/drivers/platform/surface/surface_aggregator_cdev.c
|
|
index 2cad4147645c..30fb50fde450 100644
|
|
--- a/drivers/platform/surface/surface_aggregator_cdev.c
|
|
+++ b/drivers/platform/surface/surface_aggregator_cdev.c
|
|
@@ -22,6 +22,7 @@
|
|
|
|
#include <linux/surface_aggregator/cdev.h>
|
|
#include <linux/surface_aggregator/controller.h>
|
|
+#include <linux/surface_aggregator/serial_hub.h>
|
|
|
|
#define SSAM_CDEV_DEVICE_NAME "surface_aggregator_cdev"
|
|
|
|
@@ -131,22 +132,23 @@ static u32 ssam_cdev_notifier(struct ssam_event_notifier *nf, const struct ssam_
|
|
return 0;
|
|
}
|
|
|
|
-static int ssam_cdev_notifier_register(struct ssam_cdev_client *client, u8 category, int priority)
|
|
+static int ssam_cdev_notifier_register(struct ssam_cdev_client *client, u8 tc, int priority)
|
|
{
|
|
+ const u16 rqid = ssh_tc_to_rqid(tc);
|
|
+ const u16 event = ssh_rqid_to_event(rqid);
|
|
struct ssam_cdev_notifier *nf;
|
|
- int index = ((int)category) - 1;
|
|
int status;
|
|
|
|
lockdep_assert_held_read(&client->cdev->lock);
|
|
|
|
/* Validate notifier target category. */
|
|
- if (index < 0 || index >= SSH_NUM_EVENTS)
|
|
+ if (!ssh_rqid_is_event(rqid))
|
|
return -EINVAL;
|
|
|
|
mutex_lock(&client->notifier_lock);
|
|
|
|
/* Check if the notifier has already been registered. */
|
|
- if (client->notifier[index]) {
|
|
+ if (client->notifier[event]) {
|
|
mutex_unlock(&client->notifier_lock);
|
|
return -EEXIST;
|
|
}
|
|
@@ -167,7 +169,7 @@ static int ssam_cdev_notifier_register(struct ssam_cdev_client *client, u8 categ
|
|
nf->client = client;
|
|
nf->nf.base.fn = ssam_cdev_notifier;
|
|
nf->nf.base.priority = priority;
|
|
- nf->nf.event.id.target_category = category;
|
|
+ nf->nf.event.id.target_category = tc;
|
|
nf->nf.event.mask = 0; /* Do not do any matching. */
|
|
nf->nf.flags = SSAM_EVENT_NOTIFIER_OBSERVER;
|
|
|
|
@@ -176,35 +178,36 @@ static int ssam_cdev_notifier_register(struct ssam_cdev_client *client, u8 categ
|
|
if (status)
|
|
kfree(nf);
|
|
else
|
|
- client->notifier[index] = nf;
|
|
+ client->notifier[event] = nf;
|
|
|
|
mutex_unlock(&client->notifier_lock);
|
|
return status;
|
|
}
|
|
|
|
-static int ssam_cdev_notifier_unregister(struct ssam_cdev_client *client, u8 category)
|
|
+static int ssam_cdev_notifier_unregister(struct ssam_cdev_client *client, u8 tc)
|
|
{
|
|
- int index = ((int)category) - 1;
|
|
+ const u16 rqid = ssh_tc_to_rqid(tc);
|
|
+ const u16 event = ssh_rqid_to_event(rqid);
|
|
int status;
|
|
|
|
lockdep_assert_held_read(&client->cdev->lock);
|
|
|
|
/* Validate notifier target category. */
|
|
- if (index < 0 || index >= SSH_NUM_EVENTS)
|
|
+ if (!ssh_rqid_is_event(rqid))
|
|
return -EINVAL;
|
|
|
|
mutex_lock(&client->notifier_lock);
|
|
|
|
/* Check if the notifier is currently registered. */
|
|
- if (!client->notifier[index]) {
|
|
+ if (!client->notifier[event]) {
|
|
mutex_unlock(&client->notifier_lock);
|
|
return -ENOENT;
|
|
}
|
|
|
|
/* Unregister and free notifier. */
|
|
- status = ssam_notifier_unregister(client->cdev->ctrl, &client->notifier[index]->nf);
|
|
- kfree(client->notifier[index]);
|
|
- client->notifier[index] = NULL;
|
|
+ status = ssam_notifier_unregister(client->cdev->ctrl, &client->notifier[event]->nf);
|
|
+ kfree(client->notifier[event]);
|
|
+ client->notifier[event] = NULL;
|
|
|
|
mutex_unlock(&client->notifier_lock);
|
|
return status;
|
|
@@ -482,6 +485,9 @@ static int ssam_cdev_device_open(struct inode *inode, struct file *filp)
|
|
|
|
if (test_bit(SSAM_CDEV_DEVICE_SHUTDOWN_BIT, &cdev->flags)) {
|
|
up_write(&cdev->client_lock);
|
|
+ mutex_destroy(&client->write_lock);
|
|
+ mutex_destroy(&client->read_lock);
|
|
+ mutex_destroy(&client->notifier_lock);
|
|
ssam_cdev_put(client->cdev);
|
|
vfree(client);
|
|
return -ENODEV;
|
|
--
|
|
2.32.0
|
|
|
|
From fe4a216fc5f8e021db1d78df71c4f99ad5110bfd Mon Sep 17 00:00:00 2001
|
|
From: Maximilian Luz <luzmaximilian@gmail.com>
|
|
Date: Fri, 4 Jun 2021 22:56:38 +0200
|
|
Subject: [PATCH] platform/surface: aggregator: Do not return uninitialized
|
|
value
|
|
|
|
The status variable in ssam_nf_refcount_disable_free() is only set when
|
|
the reference count equals zero. Otherwise, it is returned
|
|
uninitialized. Fix this by always initializing status to zero.
|
|
|
|
Reported-by: kernel test robot <lkp@intel.com>
|
|
Fixes: 640ee17199e4 ("platform/surface: aggregator: Allow enabling of events without notifiers")
|
|
Signed-off-by: Maximilian Luz <luzmaximilian@gmail.com>
|
|
Patchset: surface-sam
|
|
---
|
|
drivers/platform/surface/aggregator/controller.c | 2 +-
|
|
1 file changed, 1 insertion(+), 1 deletion(-)
|
|
|
|
diff --git a/drivers/platform/surface/aggregator/controller.c b/drivers/platform/surface/aggregator/controller.c
|
|
index 6646f4d6e10d..634399387d76 100644
|
|
--- a/drivers/platform/surface/aggregator/controller.c
|
|
+++ b/drivers/platform/surface/aggregator/controller.c
|
|
@@ -2228,7 +2228,7 @@ static int ssam_nf_refcount_disable_free(struct ssam_controller *ctrl,
|
|
const struct ssam_event_registry reg = entry->key.reg;
|
|
const struct ssam_event_id id = entry->key.id;
|
|
struct ssam_nf *nf = &ctrl->cplt.event.notif;
|
|
- int status;
|
|
+ int status = 0;
|
|
|
|
lockdep_assert_held(&nf->lock);
|
|
|
|
--
|
|
2.32.0
|
|
|
|
From 79c87e7add447e1021595ff6a56c34ded0734926 Mon Sep 17 00:00:00 2001
|
|
From: Maximilian Luz <luzmaximilian@gmail.com>
|
|
Date: Fri, 4 Jun 2021 23:00:47 +0200
|
|
Subject: [PATCH] platform/surface: aggregator: Drop unnecessary variable
|
|
initialization
|
|
|
|
The status variable in ssam_controller_event_disable() is always set, no
|
|
need to initialize it.
|
|
|
|
Signed-off-by: Maximilian Luz <luzmaximilian@gmail.com>
|
|
Patchset: surface-sam
|
|
---
|
|
drivers/platform/surface/aggregator/controller.c | 2 +-
|
|
1 file changed, 1 insertion(+), 1 deletion(-)
|
|
|
|
diff --git a/drivers/platform/surface/aggregator/controller.c b/drivers/platform/surface/aggregator/controller.c
|
|
index 634399387d76..b8c377b3f932 100644
|
|
--- a/drivers/platform/surface/aggregator/controller.c
|
|
+++ b/drivers/platform/surface/aggregator/controller.c
|
|
@@ -2464,7 +2464,7 @@ int ssam_controller_event_disable(struct ssam_controller *ctrl,
|
|
u16 rqid = ssh_tc_to_rqid(id.target_category);
|
|
struct ssam_nf *nf = &ctrl->cplt.event.notif;
|
|
struct ssam_nf_refcount_entry *entry;
|
|
- int status = 0;
|
|
+ int status;
|
|
|
|
if (!ssh_rqid_is_event(rqid))
|
|
return -EINVAL;
|
|
--
|
|
2.32.0
|
|
|
|
From 220729198b2a72ada278f36112f6c064d82c7a0e Mon Sep 17 00:00:00 2001
|
|
From: Maximilian Luz <luzmaximilian@gmail.com>
|
|
Date: Wed, 2 Jun 2021 20:07:47 +0200
|
|
Subject: [PATCH] docs: driver-api: Update Surface Aggregator user-space
|
|
interface documentation
|
|
|
|
Update the controller-device user-space interface (cdev) documentation
|
|
for the newly introduced IOCTLs and event interface.
|
|
|
|
Signed-off-by: Maximilian Luz <luzmaximilian@gmail.com>
|
|
Patchset: surface-sam
|
|
---
|
|
.../surface_aggregator/clients/cdev.rst | 127 +++++++++++++++++-
|
|
1 file changed, 122 insertions(+), 5 deletions(-)
|
|
|
|
diff --git a/Documentation/driver-api/surface_aggregator/clients/cdev.rst b/Documentation/driver-api/surface_aggregator/clients/cdev.rst
|
|
index 248c1372d879..0134a841a079 100644
|
|
--- a/Documentation/driver-api/surface_aggregator/clients/cdev.rst
|
|
+++ b/Documentation/driver-api/surface_aggregator/clients/cdev.rst
|
|
@@ -1,9 +1,8 @@
|
|
.. SPDX-License-Identifier: GPL-2.0+
|
|
|
|
-.. |u8| replace:: :c:type:`u8 <u8>`
|
|
-.. |u16| replace:: :c:type:`u16 <u16>`
|
|
.. |ssam_cdev_request| replace:: :c:type:`struct ssam_cdev_request <ssam_cdev_request>`
|
|
.. |ssam_cdev_request_flags| replace:: :c:type:`enum ssam_cdev_request_flags <ssam_cdev_request_flags>`
|
|
+.. |ssam_cdev_event| replace:: :c:type:`struct ssam_cdev_event <ssam_cdev_event>`
|
|
|
|
==============================
|
|
User-Space EC Interface (cdev)
|
|
@@ -23,6 +22,40 @@ These IOCTLs and their respective input/output parameter structs are defined in
|
|
A small python library and scripts for accessing this interface can be found
|
|
at https://github.com/linux-surface/surface-aggregator-module/tree/master/scripts/ssam.
|
|
|
|
+.. contents::
|
|
+
|
|
+
|
|
+Receiving Events
|
|
+================
|
|
+
|
|
+Events can be received by reading from the device-file. The are represented by
|
|
+the |ssam_cdev_event| datatype.
|
|
+
|
|
+Before events are available to be read, however, the desired notifiers must be
|
|
+registered via the ``SSAM_CDEV_NOTIF_REGISTER`` IOCTL. Notifiers are, in
|
|
+essence, callbacks, called when the EC sends an event. They are, in this
|
|
+interface, associated with a specific target category and device-file-instance.
|
|
+They forward any event of this category to the buffer of the corresponding
|
|
+instance, from which it can then be read.
|
|
+
|
|
+Notifiers themselves do not enable events on the EC. Thus, it may additionally
|
|
+be necessary to enable events via the ``SSAM_CDEV_EVENT_ENABLE`` IOCTL. While
|
|
+notifiers work per-client (i.e. per-device-file-instance), events are enabled
|
|
+globally, for the EC and all of its clients (regardless of userspace or
|
|
+non-userspace). The ``SSAM_CDEV_EVENT_ENABLE`` and ``SSAM_CDEV_EVENT_DISABLE``
|
|
+IOCTLs take care of reference counting the events, such that an event is
|
|
+enabled as long as there is a client that has requested it.
|
|
+
|
|
+Note that enabled events are not automatically disabled once the client
|
|
+instance is closed. Therefore any client process (or group of processes) should
|
|
+balance their event enable calls with the corresponding event disable calls. It
|
|
+is, however, perfectly valid to enable and disable events on different client
|
|
+instances. For example, it is valid to set up notifiers and read events on
|
|
+client instance ``A``, enable those events on instance ``B`` (note that these
|
|
+will also be received by A since events are enabled/disabled globally), and
|
|
+after no more events are desired, disable the previously enabled events via
|
|
+instance ``C``.
|
|
+
|
|
|
|
Controller IOCTLs
|
|
=================
|
|
@@ -45,9 +78,33 @@ The following IOCTLs are provided:
|
|
- ``REQUEST``
|
|
- Perform synchronous SAM request.
|
|
|
|
+ * - ``0xA5``
|
|
+ - ``2``
|
|
+ - ``W``
|
|
+ - ``NOTIF_REGISTER``
|
|
+ - Register event notifier.
|
|
|
|
-``REQUEST``
|
|
------------
|
|
+ * - ``0xA5``
|
|
+ - ``3``
|
|
+ - ``W``
|
|
+ - ``NOTIF_UNREGISTER``
|
|
+ - Unregister event notifier.
|
|
+
|
|
+ * - ``0xA5``
|
|
+ - ``4``
|
|
+ - ``W``
|
|
+ - ``EVENT_ENABLE``
|
|
+ - Enable event source.
|
|
+
|
|
+ * - ``0xA5``
|
|
+ - ``5``
|
|
+ - ``W``
|
|
+ - ``EVENT_DISABLE``
|
|
+ - Disable event source.
|
|
+
|
|
+
|
|
+``SSAM_CDEV_REQUEST``
|
|
+---------------------
|
|
|
|
Defined as ``_IOWR(0xA5, 1, struct ssam_cdev_request)``.
|
|
|
|
@@ -82,6 +139,66 @@ submitted, and completed (i.e. handed back to user-space) successfully from
|
|
inside the IOCTL, but the request ``status`` member may still be negative in
|
|
case the actual execution of the request failed after it has been submitted.
|
|
|
|
-A full definition of the argument struct is provided below:
|
|
+A full definition of the argument struct is provided below.
|
|
+
|
|
+``SSAM_CDEV_NOTIF_REGISTER``
|
|
+----------------------------
|
|
+
|
|
+Defined as ``_IOW(0xA5, 2, struct ssam_cdev_notifier_desc)``.
|
|
+
|
|
+Register a notifier for the event target category specified in the given
|
|
+notifier description with the specified priority. Notifiers registration is
|
|
+required to receive events, but does not enable events themselves. After a
|
|
+notifier for a specific target category has been registered, all events of that
|
|
+category will be forwarded to the userspace client and can then be read from
|
|
+the device file instance. Note that events may have to be enabled, e.g. via the
|
|
+``SSAM_CDEV_EVENT_ENABLE`` IOCTL, before the EC will send them.
|
|
+
|
|
+Only one notifier can be registered per target category and client instance. If
|
|
+a notifier has already been registered, this IOCTL will fail with ``-EEXIST``.
|
|
+
|
|
+Notifiers will automatically be removed when the device file instance is
|
|
+closed.
|
|
+
|
|
+``SSAM_CDEV_NOTIF_UNREGISTER``
|
|
+------------------------------
|
|
+
|
|
+Defined as ``_IOW(0xA5, 3, struct ssam_cdev_notifier_desc)``.
|
|
+
|
|
+Unregisters the notifier associated with the specified target category. The
|
|
+priority field will be ignored by this IOCTL. If no notifier has been
|
|
+registered for this client instance and the given category, this IOCTL will
|
|
+fail with ``-ENOENT``.
|
|
+
|
|
+``SSAM_CDEV_EVENT_ENABLE``
|
|
+--------------------------
|
|
+
|
|
+Defined as ``_IOW(0xA5, 4, struct ssam_cdev_event_desc)``.
|
|
+
|
|
+Enable the event associated with the given event descriptor.
|
|
+
|
|
+Note that this call will not register a notifier itself, it will only enable
|
|
+events on the controller. If you want to receive events by reading from the
|
|
+device file, you will need to register the corresponding notifier(s) on that
|
|
+instance.
|
|
+
|
|
+Events are not automatically disabled when the device file is closed. This must
|
|
+be done manually, via a call to the ``SSAM_CDEV_EVENT_DISABLE`` IOCTL.
|
|
+
|
|
+``SSAM_CDEV_EVENT_DISABLE``
|
|
+---------------------------
|
|
+
|
|
+Defined as ``_IOW(0xA5, 5, struct ssam_cdev_event_desc)``.
|
|
+
|
|
+Disable the event associated with the given event descriptor.
|
|
+
|
|
+Note that this will not unregister any notifiers. Events may still be received
|
|
+and forwarded to user-space after this call. The only safe way of stopping
|
|
+events from being received is unregistering all previously registered
|
|
+notifiers.
|
|
+
|
|
+
|
|
+Structures and Enums
|
|
+====================
|
|
|
|
.. kernel-doc:: include/uapi/linux/surface_aggregator/cdev.h
|
|
--
|
|
2.32.0
|
|
|
|
From a12ef238262023ecb08d5fd7ac5b589c5cb41524 Mon Sep 17 00:00:00 2001
|
|
From: Maximilian Luz <luzmaximilian@gmail.com>
|
|
Date: Wed, 5 May 2021 18:22:04 +0200
|
|
Subject: [PATCH] pinctrl/amd: Add device HID for new AMD GPIO controller
|
|
|
|
Add device HID AMDI0031 to the AMD GPIO controller driver match table.
|
|
This controller can be found on Microsoft Surface Laptop 4 devices and
|
|
seems similar enough that we can just copy the existing AMDI0030 entry.
|
|
|
|
Cc: <stable@vger.kernel.org> # 5.10+
|
|
Tested-by: Sachi King <nakato@nakato.io>
|
|
Signed-off-by: Maximilian Luz <luzmaximilian@gmail.com>
|
|
Patchset: surface-sam
|
|
---
|
|
drivers/pinctrl/pinctrl-amd.c | 1 +
|
|
1 file changed, 1 insertion(+)
|
|
|
|
diff --git a/drivers/pinctrl/pinctrl-amd.c b/drivers/pinctrl/pinctrl-amd.c
|
|
index 2d4acf21117c..c5950a3b4e4c 100644
|
|
--- a/drivers/pinctrl/pinctrl-amd.c
|
|
+++ b/drivers/pinctrl/pinctrl-amd.c
|
|
@@ -991,6 +991,7 @@ static int amd_gpio_remove(struct platform_device *pdev)
|
|
static const struct acpi_device_id amd_gpio_acpi_match[] = {
|
|
{ "AMD0030", 0 },
|
|
{ "AMDI0030", 0},
|
|
+ { "AMDI0031", 0},
|
|
{ },
|
|
};
|
|
MODULE_DEVICE_TABLE(acpi, amd_gpio_acpi_match);
|
|
--
|
|
2.32.0
|
|
|
|
From 5500228a79c6136a40035f557bd62160b2c7b4eb Mon Sep 17 00:00:00 2001
|
|
From: Sachi King <nakato@nakato.io>
|
|
Date: Sat, 29 May 2021 17:47:38 +1000
|
|
Subject: [PATCH] ACPI: Add quirk for Surface Laptop 4 AMD missing irq 7
|
|
override
|
|
|
|
This patch is the work of Thomas Gleixner <tglx@linutronix.de> and is
|
|
copied from:
|
|
https://lore.kernel.org/lkml/87lf8ddjqx.ffs@nanos.tec.linutronix.de/
|
|
|
|
This patch adds a quirk to the ACPI setup to patch in the the irq 7 pin
|
|
setup that is missing in the laptops ACPI table.
|
|
|
|
This patch was used for validation of the issue, and is not a proper
|
|
fix, but is probably a better temporary hack than continuing to probe
|
|
the Legacy PIC and run with the PIC in an unknown state.
|
|
|
|
Patchset: surface-sam
|
|
---
|
|
arch/x86/kernel/acpi/boot.c | 17 +++++++++++++++++
|
|
1 file changed, 17 insertions(+)
|
|
|
|
diff --git a/arch/x86/kernel/acpi/boot.c b/arch/x86/kernel/acpi/boot.c
|
|
index 14cd3186dc77..ab3ba60cb6da 100644
|
|
--- a/arch/x86/kernel/acpi/boot.c
|
|
+++ b/arch/x86/kernel/acpi/boot.c
|
|
@@ -21,6 +21,7 @@
|
|
#include <linux/efi-bgrt.h>
|
|
#include <linux/serial_core.h>
|
|
#include <linux/pgtable.h>
|
|
+#include <linux/dmi.h>
|
|
|
|
#include <asm/e820/api.h>
|
|
#include <asm/irqdomain.h>
|
|
@@ -1155,6 +1156,17 @@ static void __init mp_config_acpi_legacy_irqs(void)
|
|
}
|
|
}
|
|
|
|
+static const struct dmi_system_id surface_quirk[] __initconst = {
|
|
+ {
|
|
+ .ident = "Microsoft Surface Laptop 4 (AMD)",
|
|
+ .matches = {
|
|
+ DMI_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),
|
|
+ DMI_MATCH(DMI_PRODUCT_SKU, "Surface_Laptop_4_1952:1953")
|
|
+ },
|
|
+ },
|
|
+ {}
|
|
+};
|
|
+
|
|
/*
|
|
* Parse IOAPIC related entries in MADT
|
|
* returns 0 on success, < 0 on error
|
|
@@ -1212,6 +1224,11 @@ static int __init acpi_parse_madt_ioapic_entries(void)
|
|
acpi_sci_ioapic_setup(acpi_gbl_FADT.sci_interrupt, 0, 0,
|
|
acpi_gbl_FADT.sci_interrupt);
|
|
|
|
+ if (dmi_check_system(surface_quirk)) {
|
|
+ pr_warn("Surface hack: Override irq 7\n");
|
|
+ mp_override_legacy_irq(7, 3, 3, 7);
|
|
+ }
|
|
+
|
|
/* Fill in identity legacy mappings where no override */
|
|
mp_config_acpi_legacy_irqs();
|
|
|
|
--
|
|
2.32.0
|
|
|
|
From 14310a79f4542660ae09e7ee337547e1aa7a37bf Mon Sep 17 00:00:00 2001
|
|
From: Maximilian Luz <luzmaximilian@gmail.com>
|
|
Date: Thu, 3 Jun 2021 14:04:26 +0200
|
|
Subject: [PATCH] ACPI: Add AMD 13" Surface Laptop 4 model to irq 7 override
|
|
quirk
|
|
|
|
The 13" version of the Surface Laptop 4 has the same problem as the 15"
|
|
version, but uses a different SKU. Add that SKU to the quirk as well.
|
|
|
|
Patchset: surface-sam
|
|
---
|
|
arch/x86/kernel/acpi/boot.c | 9 ++++++++-
|
|
1 file changed, 8 insertions(+), 1 deletion(-)
|
|
|
|
diff --git a/arch/x86/kernel/acpi/boot.c b/arch/x86/kernel/acpi/boot.c
|
|
index ab3ba60cb6da..fa1dcdd119e5 100644
|
|
--- a/arch/x86/kernel/acpi/boot.c
|
|
+++ b/arch/x86/kernel/acpi/boot.c
|
|
@@ -1158,12 +1158,19 @@ static void __init mp_config_acpi_legacy_irqs(void)
|
|
|
|
static const struct dmi_system_id surface_quirk[] __initconst = {
|
|
{
|
|
- .ident = "Microsoft Surface Laptop 4 (AMD)",
|
|
+ .ident = "Microsoft Surface Laptop 4 (AMD 15\")",
|
|
.matches = {
|
|
DMI_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),
|
|
DMI_MATCH(DMI_PRODUCT_SKU, "Surface_Laptop_4_1952:1953")
|
|
},
|
|
},
|
|
+ {
|
|
+ .ident = "Microsoft Surface Laptop 4 (AMD 13\")",
|
|
+ .matches = {
|
|
+ DMI_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"),
|
|
+ DMI_MATCH(DMI_PRODUCT_SKU, "Surface_Laptop_4_1958:1959")
|
|
+ },
|
|
+ },
|
|
{}
|
|
};
|
|
|
|
--
|
|
2.32.0
|
|
|
|
From 418f3b0405c0134e8ce5fb53d857435b4865fa2e Mon Sep 17 00:00:00 2001
|
|
From: Sachi King <nakato@nakato.io>
|
|
Date: Sat, 29 May 2021 22:27:25 +1000
|
|
Subject: [PATCH] platform/x86: amd-pmc: Add device HID for AMD PMC
|
|
|
|
The Surface Laptop 4 appears to have used AMD0005 for the PMC instead of
|
|
the AMDI0005 which would match the ACPI ID Registry.
|
|
|
|
AMD appears to have previously used "AMD" in a number of IDs in the past,
|
|
and AMD is not allocated to any other entity as an ID, so adding this ID
|
|
should not cause any harm.
|
|
|
|
Signed-off-by: Sachi King <nakato@nakato.io>
|
|
Patchset: surface-sam
|
|
---
|
|
drivers/platform/x86/amd-pmc.c | 1 +
|
|
1 file changed, 1 insertion(+)
|
|
|
|
diff --git a/drivers/platform/x86/amd-pmc.c b/drivers/platform/x86/amd-pmc.c
|
|
index b9da58ee9b1e..0b5578a8a449 100644
|
|
--- a/drivers/platform/x86/amd-pmc.c
|
|
+++ b/drivers/platform/x86/amd-pmc.c
|
|
@@ -275,6 +275,7 @@ static int amd_pmc_remove(struct platform_device *pdev)
|
|
static const struct acpi_device_id amd_pmc_acpi_ids[] = {
|
|
{"AMDI0005", 0},
|
|
{"AMD0004", 0},
|
|
+ {"AMD0005", 0},
|
|
{ }
|
|
};
|
|
MODULE_DEVICE_TABLE(acpi, amd_pmc_acpi_ids);
|
|
--
|
|
2.32.0
|
|
|