diff --git a/configs/surface-4.19.config b/configs/surface-4.19.config index b43028638..a074b83bc 100644 --- a/configs/surface-4.19.config +++ b/configs/surface-4.19.config @@ -12,15 +12,14 @@ CONFIG_SURFACE_SAM=m CONFIG_SURFACE_SAM_SSH=m CONFIG_SURFACE_SAM_SSH_ERROR_INJECTION=n CONFIG_SURFACE_SAM_DEBUGFS=m +CONFIG_SURFACE_SAM_DEVICE_HUB=m CONFIG_SURFACE_SAM_SAN=m -CONFIG_SURFACE_SAM_VHF=m +CONFIG_SURFACE_SAM_KBD=m CONFIG_SURFACE_SAM_DTX=m CONFIG_SURFACE_SAM_HPS=m -CONFIG_SURFACE_SAM_SID=m -CONFIG_SURFACE_SAM_SID_GPELID=m -CONFIG_SURFACE_SAM_SID_PERFMODE=m -CONFIG_SURFACE_SAM_SID_VHF=m -CONFIG_SURFACE_SAM_SID_POWER=m +CONFIG_SURFACE_SAM_PERFMODE=m +CONFIG_SURFACE_SAM_HID=m +CONFIG_SURFACE_SAM_PSY=m # # Other Drivers @@ -29,3 +28,4 @@ CONFIG_INPUT_SOC_BUTTON_ARRAY=m CONFIG_SURFACE_3_BUTTON=m CONFIG_SURFACE_3_POWER_OPREGION=m CONFIG_SURFACE_PRO3_BUTTON=m +CONFIG_SURFACE_GPE=m diff --git a/patches/4.19/0001-surface3-power.patch b/patches/4.19/0001-surface3-power.patch index 1ee9628d2..9884d26db 100644 --- a/patches/4.19/0001-surface3-power.patch +++ b/patches/4.19/0001-surface3-power.patch @@ -1,7 +1,7 @@ -From 7758260ca65d048fc048997fde2b2835aeb56bf2 Mon Sep 17 00:00:00 2001 +From 642ccc9a85515f6330b90ec2e8024af737b165ab Mon Sep 17 00:00:00 2001 From: Maximilian Luz Date: Sat, 28 Sep 2019 18:00:43 +0200 -Subject: [PATCH 1/9] surface3-power +Subject: [PATCH 01/10] surface3-power --- drivers/platform/x86/Kconfig | 7 + diff --git a/patches/4.19/0002-surface3-touchscreen-dma-fix.patch b/patches/4.19/0002-surface3-touchscreen-dma-fix.patch index f57e664d4..97104600e 100644 --- a/patches/4.19/0002-surface3-touchscreen-dma-fix.patch +++ b/patches/4.19/0002-surface3-touchscreen-dma-fix.patch @@ -1,7 +1,7 @@ -From 4ddb434540558cdfbf4c6a7302e5cf96a1cf016c Mon Sep 17 00:00:00 2001 +From ace1524a2a614b411772973a07a0f9894db71527 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Sun, 5 Jul 2020 14:56:20 +0300 -Subject: [PATCH 2/9] surface3-touchscreen-dma-fix +Subject: [PATCH 02/10] surface3-touchscreen-dma-fix --- drivers/dma/dw/core.c | 12 ------------ diff --git a/patches/4.19/0003-surface3-oemb.patch b/patches/4.19/0003-surface3-oemb.patch index 2b7b7c1c3..d78c7a9da 100644 --- a/patches/4.19/0003-surface3-oemb.patch +++ b/patches/4.19/0003-surface3-oemb.patch @@ -1,7 +1,7 @@ -From 319a7962c8241d6d42765f31f932e3e71502e642 Mon Sep 17 00:00:00 2001 +From 3e7fd7e46281ea36ec0b3f7f75e32ba0dc4a2b41 Mon Sep 17 00:00:00 2001 From: Chih-Wei Huang Date: Tue, 18 Sep 2018 11:01:37 +0800 -Subject: [PATCH 3/9] surface3-oemb +Subject: [PATCH 03/10] surface3-oemb --- drivers/platform/x86/surface3-wmi.c | 7 +++++++ diff --git a/patches/4.19/0004-surface-buttons.patch b/patches/4.19/0004-surface-buttons.patch index 8a27a118c..5709ffeac 100644 --- a/patches/4.19/0004-surface-buttons.patch +++ b/patches/4.19/0004-surface-buttons.patch @@ -1,7 +1,7 @@ -From 56a637b630a48dc4483c1d6e9dfbf09b324abb1b Mon Sep 17 00:00:00 2001 +From 44ae2b5f2dec6f2bca7381728a0ebfde27efad17 Mon Sep 17 00:00:00 2001 From: Maximilian Luz Date: Sat, 27 Jul 2019 17:51:37 +0200 -Subject: [PATCH 4/9] surface-buttons +Subject: [PATCH 04/10] surface-buttons --- drivers/input/misc/Kconfig | 6 +- diff --git a/patches/4.19/0007-suspend.patch b/patches/4.19/0005-suspend.patch similarity index 99% rename from patches/4.19/0007-suspend.patch rename to patches/4.19/0005-suspend.patch index df4fbad03..9f065e138 100644 --- a/patches/4.19/0007-suspend.patch +++ b/patches/4.19/0005-suspend.patch @@ -1,7 +1,7 @@ -From b6a4004590ccd476e3880c03d8e9fb4b80a54eba Mon Sep 17 00:00:00 2001 +From f8393b3a5b16f19cc9af9bc171f65187a9a2c54e Mon Sep 17 00:00:00 2001 From: kitakar5525 <34676735+kitakar5525@users.noreply.github.com> Date: Sat, 28 Sep 2019 17:48:21 +0200 -Subject: [PATCH 7/9] suspend +Subject: [PATCH 05/10] suspend --- drivers/nvme/host/core.c | 36 ++++++++++++-- diff --git a/patches/4.19/0008-ipts.patch b/patches/4.19/0006-ipts.patch similarity index 99% rename from patches/4.19/0008-ipts.patch rename to patches/4.19/0006-ipts.patch index d0012d95d..0400fdb71 100644 --- a/patches/4.19/0008-ipts.patch +++ b/patches/4.19/0006-ipts.patch @@ -1,7 +1,7 @@ -From bfa54c16d6e9e112be44081b80d422100b839ead Mon Sep 17 00:00:00 2001 +From a330d174c772715c1f6979cc9e8ec8268d1b85e2 Mon Sep 17 00:00:00 2001 From: Maximilian Luz Date: Sat, 28 Sep 2019 17:58:17 +0200 -Subject: [PATCH 8/9] ipts +Subject: [PATCH 06/10] ipts --- drivers/gpu/drm/i915/Makefile | 3 + diff --git a/patches/4.19/0009-wifi.patch b/patches/4.19/0007-wifi.patch similarity index 98% rename from patches/4.19/0009-wifi.patch rename to patches/4.19/0007-wifi.patch index 6934602bc..8f339dca6 100644 --- a/patches/4.19/0009-wifi.patch +++ b/patches/4.19/0007-wifi.patch @@ -1,7 +1,7 @@ -From a2cac7e8bbc67148a6480cde4ccb9bd455e5892e Mon Sep 17 00:00:00 2001 +From d37d94b2f9fbd5ca3529b3b8590a212a3a1a2dd7 Mon Sep 17 00:00:00 2001 From: kitakar5525 <34676735+kitakar5525@users.noreply.github.com> Date: Thu, 20 Feb 2020 16:51:11 +0900 -Subject: [PATCH 9/9] wifi +Subject: [PATCH 07/10] wifi --- .../net/wireless/marvell/mwifiex/cfg80211.c | 26 ++++++ diff --git a/patches/4.19/0005-surface-sam.patch b/patches/4.19/0008-surface-sam.patch similarity index 83% rename from patches/4.19/0005-surface-sam.patch rename to patches/4.19/0008-surface-sam.patch index 6de645546..d4361656a 100644 --- a/patches/4.19/0005-surface-sam.patch +++ b/patches/4.19/0008-surface-sam.patch @@ -1,51 +1,73 @@ -From aca32242599b689b942043205c925c566f706fc3 Mon Sep 17 00:00:00 2001 +From 79253929affbf21d8c4b085e8b62de72dc30f8fe Mon Sep 17 00:00:00 2001 From: qzed Date: Mon, 26 Aug 2019 01:15:40 +0200 -Subject: [PATCH 5/9] surface-sam +Subject: [PATCH 08/10] surface-sam --- drivers/acpi/acpica/dsopcode.c | 2 +- drivers/acpi/acpica/exfield.c | 26 +- - drivers/platform/x86/Kconfig | 2 + - drivers/platform/x86/Makefile | 1 + - drivers/platform/x86/surface_sam/Kconfig | 176 + - drivers/platform/x86/surface_sam/Makefile | 16 + - .../x86/surface_sam/surface_sam_debugfs.c | 270 + - .../x86/surface_sam/surface_sam_dtx.c | 582 ++ - .../x86/surface_sam/surface_sam_hps.c | 1287 ++++ - .../x86/surface_sam/surface_sam_san.c | 930 +++ - .../x86/surface_sam/surface_sam_san.h | 30 + - .../x86/surface_sam/surface_sam_sid.c | 283 + - .../x86/surface_sam/surface_sam_sid_gpelid.c | 232 + - .../surface_sam/surface_sam_sid_perfmode.c | 214 + - .../x86/surface_sam/surface_sam_sid_power.c | 1054 ++++ - .../x86/surface_sam/surface_sam_sid_power.h | 16 + - .../x86/surface_sam/surface_sam_sid_vhf.c | 429 ++ - .../x86/surface_sam/surface_sam_sid_vhf.h | 14 + - .../x86/surface_sam/surface_sam_ssh.c | 5330 +++++++++++++++++ - .../x86/surface_sam/surface_sam_ssh.h | 711 +++ - .../x86/surface_sam/surface_sam_ssh_trace.h | 587 ++ - .../x86/surface_sam/surface_sam_vhf.c | 266 + - drivers/tty/serdev/core.c | 110 +- - 23 files changed, 12539 insertions(+), 29 deletions(-) - create mode 100644 drivers/platform/x86/surface_sam/Kconfig - create mode 100644 drivers/platform/x86/surface_sam/Makefile - create mode 100644 drivers/platform/x86/surface_sam/surface_sam_debugfs.c - create mode 100644 drivers/platform/x86/surface_sam/surface_sam_dtx.c - create mode 100644 drivers/platform/x86/surface_sam/surface_sam_hps.c - create mode 100644 drivers/platform/x86/surface_sam/surface_sam_san.c - create mode 100644 drivers/platform/x86/surface_sam/surface_sam_san.h - create mode 100644 drivers/platform/x86/surface_sam/surface_sam_sid.c - create mode 100644 drivers/platform/x86/surface_sam/surface_sam_sid_gpelid.c - create mode 100644 drivers/platform/x86/surface_sam/surface_sam_sid_perfmode.c - create mode 100644 drivers/platform/x86/surface_sam/surface_sam_sid_power.c - create mode 100644 drivers/platform/x86/surface_sam/surface_sam_sid_power.h - create mode 100644 drivers/platform/x86/surface_sam/surface_sam_sid_vhf.c - create mode 100644 drivers/platform/x86/surface_sam/surface_sam_sid_vhf.h - create mode 100644 drivers/platform/x86/surface_sam/surface_sam_ssh.c - create mode 100644 drivers/platform/x86/surface_sam/surface_sam_ssh.h - create mode 100644 drivers/platform/x86/surface_sam/surface_sam_ssh_trace.h - create mode 100644 drivers/platform/x86/surface_sam/surface_sam_vhf.c + drivers/misc/Kconfig | 1 + + drivers/misc/Makefile | 1 + + drivers/misc/surface_sam/Kconfig | 46 + + drivers/misc/surface_sam/Makefile | 14 + + drivers/misc/surface_sam/bus.c | 276 +++ + drivers/misc/surface_sam/bus.h | 14 + + drivers/misc/surface_sam/clients/Kconfig | 121 ++ + drivers/misc/surface_sam/clients/Makefile | 11 + + .../surface_sam/clients/surface_sam_debugfs.c | 270 +++ + .../clients/surface_sam_device_hub.c | 582 ++++++ + .../surface_sam/clients/surface_sam_dtx.c | 582 ++++++ + .../surface_sam/clients/surface_sam_hps.c | 1287 ++++++++++++ + .../surface_sam/clients/surface_sam_san.c | 930 +++++++++ + .../surface_sam/clients/surface_sam_san.h | 30 + + .../clients/surface_sam_sid_perfmode.c | 194 ++ + .../clients/surface_sam_sid_power.c | 1112 +++++++++++ + .../surface_sam/clients/surface_sam_sid_vhf.c | 430 ++++ + .../surface_sam/clients/surface_sam_vhf.c | 266 +++ + drivers/misc/surface_sam/controller.c | 1475 ++++++++++++++ + drivers/misc/surface_sam/controller.h | 170 ++ + drivers/misc/surface_sam/core.c | 554 ++++++ + drivers/misc/surface_sam/ssam_trace.h | 588 ++++++ + drivers/misc/surface_sam/ssh_msgb.h | 132 ++ + drivers/misc/surface_sam/ssh_packet_layer.c | 1753 +++++++++++++++++ + drivers/misc/surface_sam/ssh_packet_layer.h | 125 ++ + drivers/misc/surface_sam/ssh_parser.c | 132 ++ + drivers/misc/surface_sam/ssh_parser.h | 83 + + drivers/misc/surface_sam/ssh_protocol.h | 65 + + drivers/misc/surface_sam/ssh_request_layer.c | 1074 ++++++++++ + drivers/misc/surface_sam/ssh_request_layer.h | 91 + + drivers/tty/serdev/core.c | 111 +- + include/linux/surface_aggregator_module.h | 892 +++++++++ + 34 files changed, 13411 insertions(+), 29 deletions(-) + create mode 100644 drivers/misc/surface_sam/Kconfig + create mode 100644 drivers/misc/surface_sam/Makefile + create mode 100644 drivers/misc/surface_sam/bus.c + create mode 100644 drivers/misc/surface_sam/bus.h + create mode 100644 drivers/misc/surface_sam/clients/Kconfig + create mode 100644 drivers/misc/surface_sam/clients/Makefile + create mode 100644 drivers/misc/surface_sam/clients/surface_sam_debugfs.c + create mode 100644 drivers/misc/surface_sam/clients/surface_sam_device_hub.c + create mode 100644 drivers/misc/surface_sam/clients/surface_sam_dtx.c + create mode 100644 drivers/misc/surface_sam/clients/surface_sam_hps.c + create mode 100644 drivers/misc/surface_sam/clients/surface_sam_san.c + create mode 100644 drivers/misc/surface_sam/clients/surface_sam_san.h + create mode 100644 drivers/misc/surface_sam/clients/surface_sam_sid_perfmode.c + create mode 100644 drivers/misc/surface_sam/clients/surface_sam_sid_power.c + create mode 100644 drivers/misc/surface_sam/clients/surface_sam_sid_vhf.c + create mode 100644 drivers/misc/surface_sam/clients/surface_sam_vhf.c + create mode 100644 drivers/misc/surface_sam/controller.c + create mode 100644 drivers/misc/surface_sam/controller.h + create mode 100644 drivers/misc/surface_sam/core.c + create mode 100644 drivers/misc/surface_sam/ssam_trace.h + create mode 100644 drivers/misc/surface_sam/ssh_msgb.h + create mode 100644 drivers/misc/surface_sam/ssh_packet_layer.c + create mode 100644 drivers/misc/surface_sam/ssh_packet_layer.h + create mode 100644 drivers/misc/surface_sam/ssh_parser.c + create mode 100644 drivers/misc/surface_sam/ssh_parser.h + create mode 100644 drivers/misc/surface_sam/ssh_protocol.h + create mode 100644 drivers/misc/surface_sam/ssh_request_layer.c + create mode 100644 drivers/misc/surface_sam/ssh_request_layer.h + create mode 100644 include/linux/surface_aggregator_module.h diff --git a/drivers/acpi/acpica/dsopcode.c b/drivers/acpi/acpica/dsopcode.c index 2f4641e5ecde8..beb22d7e245e3 100644 @@ -113,34 +135,31 @@ index b272c329d45db..cf547883a9937 100644 function = ACPI_WRITE | (accessor_type << 16); } else { /* IPMI */ -diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig -index 2ad19dc64a4af..7cee1015981d5 100644 ---- a/drivers/platform/x86/Kconfig -+++ b/drivers/platform/x86/Kconfig -@@ -1250,6 +1250,8 @@ config INTEL_ATOMISP2_PM - To compile this driver as a module, choose M here: the module - will be called intel_atomisp2_pm. - -+source "drivers/platform/x86/surface_sam/Kconfig" -+ - endif # X86_PLATFORM_DEVICES - - config PMC_ATOM -diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile -index 2ea90039a3e49..cbea9579c1d2f 100644 ---- a/drivers/platform/x86/Makefile -+++ b/drivers/platform/x86/Makefile -@@ -94,3 +94,4 @@ obj-$(CONFIG_INTEL_TURBO_MAX_3) += intel_turbo_max_3.o - obj-$(CONFIG_INTEL_CHTDC_TI_PWRBTN) += intel_chtdc_ti_pwrbtn.o - obj-$(CONFIG_I2C_MULTI_INSTANTIATE) += i2c-multi-instantiate.o - obj-$(CONFIG_INTEL_ATOMISP2_PM) += intel_atomisp2_pm.o +diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig +index 77263b5f5915a..b5f262511f4cf 100644 +--- a/drivers/misc/Kconfig ++++ b/drivers/misc/Kconfig +@@ -528,4 +528,5 @@ source "drivers/misc/echo/Kconfig" + source "drivers/misc/cxl/Kconfig" + source "drivers/misc/ocxl/Kconfig" + source "drivers/misc/cardreader/Kconfig" ++source "drivers/misc/surface_sam/Kconfig" + endmenu +diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile +index eb1eb0d58c327..6a9796dc4860c 100644 +--- a/drivers/misc/Makefile ++++ b/drivers/misc/Makefile +@@ -59,3 +59,4 @@ obj-$(CONFIG_ASPEED_LPC_SNOOP) += aspeed-lpc-snoop.o + obj-$(CONFIG_PCI_ENDPOINT_TEST) += pci_endpoint_test.o + obj-$(CONFIG_OCXL) += ocxl/ + obj-$(CONFIG_MISC_RTSX) += cardreader/ +obj-$(CONFIG_SURFACE_SAM) += surface_sam/ -diff --git a/drivers/platform/x86/surface_sam/Kconfig b/drivers/platform/x86/surface_sam/Kconfig +diff --git a/drivers/misc/surface_sam/Kconfig b/drivers/misc/surface_sam/Kconfig new file mode 100644 -index 0000000000000..b5bb55248a5d5 +index 0000000000000..ca560d91e2291 --- /dev/null -+++ b/drivers/platform/x86/surface_sam/Kconfig -@@ -0,0 +1,176 @@ ++++ b/drivers/misc/surface_sam/Kconfig +@@ -0,0 +1,46 @@ +menuconfig SURFACE_SAM + depends on ACPI + tristate "Microsoft Surface/System Aggregator Module and Platform Drivers" @@ -158,7 +177,7 @@ index 0000000000000..b5bb55248a5d5 +config SURFACE_SAM_SSH + tristate "Surface Serial Hub Driver" + depends on SURFACE_SAM -+ depends on SERIAL_DEV_CTRL_TTYPORT ++ depends on SERIAL_DEV_BUS + select CRC_CCITT + default m + help @@ -186,6 +205,335 @@ index 0000000000000..b5bb55248a5d5 + + If you are not sure, say N here. + ++source "drivers/misc/surface_sam/clients/Kconfig" +diff --git a/drivers/misc/surface_sam/Makefile b/drivers/misc/surface_sam/Makefile +new file mode 100644 +index 0000000000000..0a07dd2297874 +--- /dev/null ++++ b/drivers/misc/surface_sam/Makefile +@@ -0,0 +1,14 @@ ++# SPDX-License-Identifier: GPL-2.0-or-later ++ ++# For include/trace/define_trace.h to include ssam_trace.h ++CFLAGS_core.o = -I$(src) ++ ++obj-$(CONFIG_SURFACE_SAM_SSH) += surface_sam_ssh.o ++obj-$(CONFIG_SURFACE_SAM_SSH) += clients/ ++ ++surface_sam_ssh-objs := core.o ++surface_sam_ssh-objs += ssh_parser.o ++surface_sam_ssh-objs += ssh_packet_layer.o ++surface_sam_ssh-objs += ssh_request_layer.o ++surface_sam_ssh-objs += controller.o ++surface_sam_ssh-objs += bus.o +diff --git a/drivers/misc/surface_sam/bus.c b/drivers/misc/surface_sam/bus.c +new file mode 100644 +index 0000000000000..e1c4c8acc762d +--- /dev/null ++++ b/drivers/misc/surface_sam/bus.c +@@ -0,0 +1,276 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++ ++#include ++#include ++ ++#include "bus.h" ++#include "controller.h" ++ ++ ++static ssize_t modalias_show(struct device *dev, struct device_attribute *attr, ++ char *buf) ++{ ++ struct ssam_device *sdev = to_ssam_device(dev); ++ ++ return snprintf(buf, PAGE_SIZE - 1, "ssam:c%02Xt%02Xi%02xf%02X\n", ++ sdev->uid.category, sdev->uid.channel, ++ sdev->uid.instance, sdev->uid.function); ++} ++static DEVICE_ATTR_RO(modalias); ++ ++static struct attribute *ssam_device_attrs[] = { ++ &dev_attr_modalias.attr, ++ NULL, ++}; ++ATTRIBUTE_GROUPS(ssam_device); ++ ++static int ssam_device_uevent(struct device *dev, struct kobj_uevent_env *env) ++{ ++ struct ssam_device *sdev = to_ssam_device(dev); ++ ++ return add_uevent_var(env, "MODALIAS=ssam:c%02Xt%02Xi%02xf%02X", ++ sdev->uid.category, sdev->uid.channel, ++ sdev->uid.instance, sdev->uid.function); ++} ++ ++static void ssam_device_release(struct device *dev) ++{ ++ struct ssam_device *sdev = to_ssam_device(dev); ++ ++ ssam_controller_put(sdev->ctrl); ++ kfree(sdev); ++} ++ ++const struct device_type ssam_device_type = { ++ .name = "ssam_client", ++ .groups = ssam_device_groups, ++ .uevent = ssam_device_uevent, ++ .release = ssam_device_release, ++}; ++EXPORT_SYMBOL_GPL(ssam_device_type); ++ ++ ++struct ssam_device *ssam_device_alloc(struct ssam_controller *ctrl, ++ struct ssam_device_uid uid) ++{ ++ struct ssam_device *sdev; ++ ++ sdev = kzalloc(sizeof(*sdev), GFP_KERNEL); ++ if (!sdev) ++ return NULL; ++ ++ device_initialize(&sdev->dev); ++ sdev->dev.bus = &ssam_bus_type; ++ sdev->dev.type = &ssam_device_type; ++ sdev->dev.parent = ssam_controller_device(ctrl); ++ sdev->ctrl = ssam_controller_get(ctrl); ++ sdev->uid = uid; ++ ++ dev_set_name(&sdev->dev, "%02x:%02x:%02x:%02x", ++ sdev->uid.category, sdev->uid.channel, sdev->uid.instance, ++ sdev->uid.function); ++ ++ return sdev; ++} ++EXPORT_SYMBOL_GPL(ssam_device_alloc); ++ ++int ssam_device_add(struct ssam_device *sdev) ++{ ++ enum ssam_controller_state state; ++ int status; ++ ++ /* ++ * Ensure that we can only add new devices to a controller if it has ++ * been started and is not going away soon. This works in combination ++ * with ssam_controller_remove_clients to ensure driver presence for the ++ * controller device, i.e. it ensures that the controller (sdev->ctrl) ++ * is always valid and can be used for requests as long as the client ++ * device we add here is registered as child under it. This essentially ++ * guarantees that the client driver can always expect the preconditions ++ * for functions like ssam_request_sync (controller has to be started ++ * and is not suspended) to hold and thus does not have to check for ++ * them. ++ * ++ * Note that for this to work, the controller has to be a parent device. ++ * If it is not a direct parent, care has to be taken that the device is ++ * removed via ssam_device_remove, as device_unregister does not remove ++ * child devices recursively. ++ */ ++ ssam_controller_statelock(sdev->ctrl); ++ ++ state = smp_load_acquire(&sdev->ctrl->state); ++ if (state != SSAM_CONTROLLER_STARTED) { ++ ssam_controller_stateunlock(sdev->ctrl); ++ return -ENXIO; ++ } ++ ++ status = device_add(&sdev->dev); ++ ++ ssam_controller_stateunlock(sdev->ctrl); ++ return status; ++} ++EXPORT_SYMBOL_GPL(ssam_device_add); ++ ++void ssam_device_remove(struct ssam_device *sdev) ++{ ++ device_unregister(&sdev->dev); ++} ++EXPORT_SYMBOL_GPL(ssam_device_remove); ++ ++ ++static inline bool ssam_device_id_compatible(const struct ssam_device_id *id, ++ struct ssam_device_uid uid) ++{ ++ if (id->category != uid.category) ++ return false; ++ ++ if ((id->match_flags & SSAM_MATCH_CHANNEL) && id->channel != uid.channel) ++ return false; ++ ++ if ((id->match_flags & SSAM_MATCH_INSTANCE) && id->instance != uid.instance) ++ return false; ++ ++ if ((id->match_flags & SSAM_MATCH_FUNCTION) && id->function != uid.function) ++ return false; ++ ++ return true; ++} ++ ++static inline bool ssam_device_id_is_null(const struct ssam_device_id *id) ++{ ++ return id->category == 0 ++ && id->channel == 0 ++ && id->instance == 0 ++ && id->function == 0; ++} ++ ++const struct ssam_device_id *ssam_device_id_match( ++ const struct ssam_device_id *table, ++ const struct ssam_device_uid uid) ++{ ++ const struct ssam_device_id *id; ++ ++ for (id = table; !ssam_device_id_is_null(id); ++id) ++ if (ssam_device_id_compatible(id, uid)) ++ return id; ++ ++ return NULL; ++} ++EXPORT_SYMBOL_GPL(ssam_device_id_match); ++ ++const struct ssam_device_id *ssam_device_get_match( ++ const struct ssam_device *dev) ++{ ++ const struct ssam_device_driver *sdrv; ++ ++ sdrv = to_ssam_device_driver(dev->dev.driver); ++ if (!sdrv) ++ return NULL; ++ ++ if (!sdrv->match_table) ++ return NULL; ++ ++ return ssam_device_id_match(sdrv->match_table, dev->uid); ++} ++EXPORT_SYMBOL_GPL(ssam_device_get_match); ++ ++const void *ssam_device_get_match_data(const struct ssam_device *dev) ++{ ++ const struct ssam_device_id *id; ++ ++ id = ssam_device_get_match(dev); ++ if (!id) ++ return NULL; ++ ++ return (const void *)id->driver_data; ++} ++EXPORT_SYMBOL_GPL(ssam_device_get_match_data); ++ ++ ++static int ssam_bus_match(struct device *dev, struct device_driver *drv) ++{ ++ struct ssam_device_driver *sdrv = to_ssam_device_driver(drv); ++ struct ssam_device *sdev = to_ssam_device(dev); ++ ++ if (!is_ssam_device(dev)) ++ return 0; ++ ++ return !!ssam_device_id_match(sdrv->match_table, sdev->uid); ++} ++ ++static int ssam_bus_probe(struct device *dev) ++{ ++ struct ssam_device_driver *sdrv = to_ssam_device_driver(dev->driver); ++ ++ return sdrv->probe(to_ssam_device(dev)); ++} ++ ++static int ssam_bus_remove(struct device *dev) ++{ ++ struct ssam_device_driver *sdrv = to_ssam_device_driver(dev->driver); ++ ++ if (sdrv->remove) ++ sdrv->remove(to_ssam_device(dev)); ++ ++ return 0; ++} ++ ++struct bus_type ssam_bus_type = { ++ .name = "ssam", ++ .match = ssam_bus_match, ++ .probe = ssam_bus_probe, ++ .remove = ssam_bus_remove, ++}; ++EXPORT_SYMBOL_GPL(ssam_bus_type); ++ ++ ++int __ssam_device_driver_register(struct ssam_device_driver *sdrv, ++ struct module *owner) ++{ ++ sdrv->driver.owner = owner; ++ sdrv->driver.bus = &ssam_bus_type; ++ ++ /* force drivers to async probe so I/O is possible in probe */ ++ sdrv->driver.probe_type = PROBE_PREFER_ASYNCHRONOUS; ++ ++ return driver_register(&sdrv->driver); ++} ++EXPORT_SYMBOL_GPL(__ssam_device_driver_register); ++ ++void ssam_device_driver_unregister(struct ssam_device_driver *sdrv) ++{ ++ driver_unregister(&sdrv->driver); ++} ++EXPORT_SYMBOL_GPL(ssam_device_driver_unregister); ++ ++ ++static int ssam_remove_device(struct device *dev, void *_data) ++{ ++ struct ssam_device *sdev = to_ssam_device(dev); ++ ++ if (is_ssam_device(dev)) ++ ssam_device_remove(sdev); ++ ++ return 0; ++} ++ ++/* ++ * Controller lock should be held during this call and subsequent ++ * de-initialization. ++ */ ++void ssam_controller_remove_clients(struct ssam_controller *ctrl) ++{ ++ struct device *dev = ssam_controller_device(ctrl); ++ ++ device_for_each_child_reverse(dev, NULL, ssam_remove_device); ++} ++ ++ ++int ssam_bus_register(void) ++{ ++ return bus_register(&ssam_bus_type); ++} ++ ++void ssam_bus_unregister(void) { ++ return bus_unregister(&ssam_bus_type); ++} +diff --git a/drivers/misc/surface_sam/bus.h b/drivers/misc/surface_sam/bus.h +new file mode 100644 +index 0000000000000..8b3ddf2100870 +--- /dev/null ++++ b/drivers/misc/surface_sam/bus.h +@@ -0,0 +1,14 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++ ++#ifndef _SSAM_BUS_H ++#define _SSAM_BUS_H ++ ++#include ++ ++ ++void ssam_controller_remove_clients(struct ssam_controller *ctrl); ++ ++int ssam_bus_register(void); ++void ssam_bus_unregister(void); ++ ++#endif /* _SSAM_BUS_H */ +diff --git a/drivers/misc/surface_sam/clients/Kconfig b/drivers/misc/surface_sam/clients/Kconfig +new file mode 100644 +index 0000000000000..189bf02e280d7 +--- /dev/null ++++ b/drivers/misc/surface_sam/clients/Kconfig +@@ -0,0 +1,121 @@ +config SURFACE_SAM_DEBUGFS + tristate "Surface Serial Hub Debug Device" + depends on SURFACE_SAM_SSH @@ -198,6 +546,22 @@ index 0000000000000..b5bb55248a5d5 + + If you are not sure, say N here. + ++config SURFACE_SAM_DEVICE_HUB ++ tristate "Surface SAM Device Hub" ++ depends on SURFACE_SAM_SSH ++ default m ++ help ++ This driver acts as a device hub, providing virtual SAM client devices ++ used on the Surface devices to provide interfaces for the performance ++ mode, HID input devices on Surface Laptop 3 and Surface Book 3, and ++ battery and AC devices on 7th generation Surface devices. ++ ++ Note that this module only provides the devices and acts as a sort of ++ registry for them. Both the device hub and the respective drivers for ++ the devices attached to the hub are required for full support. ++ ++ If you are not sure, say M here. ++ +config SURFACE_SAM_SAN + tristate "Surface ACPI Notify Driver" + depends on SURFACE_SAM_SSH @@ -212,7 +576,7 @@ index 0000000000000..b5bb55248a5d5 + + If you are not sure, say M here. + -+config SURFACE_SAM_VHF ++config SURFACE_SAM_KBD + tristate "Surface Virtual HID Framework Driver" + depends on SURFACE_SAM_SSH + depends on HID @@ -255,35 +619,9 @@ index 0000000000000..b5bb55248a5d5 + + If you are not sure, say M here. + -+config SURFACE_SAM_SID -+ tristate "Surface Platform Integration Driver" -+ depends on SURFACE_SAM_SSH -+ default m -+ help -+ Surface Platform Integration Driver for the Microsoft Surface Devices. -+ This driver loads various model-specific sub-drivers, including -+ battery and keyboard support on 7th generation Surface devices, proper -+ lid setup to enable device wakeup when the lid is opened on multiple -+ models, as well as performance mode setting support on the Surface -+ Book 2. -+ -+ If you are not sure, say M here. -+ -+config SURFACE_SAM_SID_GPELID -+ tristate "Surface Lid Wakeup Driver" -+ depends on SURFACE_SAM_SID -+ default m -+ help -+ Driver to set up device wake-up via lid on Intel-based Microsoft -+ Surface devices. These devices do not wake up from sleep as their GPE -+ interrupt is not configured automatically. This driver solves that -+ problem. -+ -+ If you are not sure, say M here. -+ -+config SURFACE_SAM_SID_PERFMODE ++config SURFACE_SAM_PERFMODE + tristate "Surface Performance Mode Driver" -+ depends on SURFACE_SAM_SID ++ depends on SURFACE_SAM_SSH + depends on SYSFS + default m + help @@ -295,9 +633,9 @@ index 0000000000000..b5bb55248a5d5 + + If you are not sure, say M here. + -+config SURFACE_SAM_SID_VHF ++config SURFACE_SAM_HID + tristate "Surface SAM HID Driver" -+ depends on SURFACE_SAM_SID ++ depends on SURFACE_SAM_SSH + depends on HID + default m + help @@ -307,9 +645,9 @@ index 0000000000000..b5bb55248a5d5 + + If you are not sure, say M here. + -+config SURFACE_SAM_SID_POWER ++config SURFACE_SAM_PSY + tristate "Surface SAM Battery/AC Driver" -+ depends on SURFACE_SAM_SID ++ depends on SURFACE_SAM_SSH + select POWER_SUPPLY + default m + help @@ -317,33 +655,28 @@ index 0000000000000..b5bb55248a5d5 + Surface devices. + + If you are not sure, say M here. -diff --git a/drivers/platform/x86/surface_sam/Makefile b/drivers/platform/x86/surface_sam/Makefile +diff --git a/drivers/misc/surface_sam/clients/Makefile b/drivers/misc/surface_sam/clients/Makefile new file mode 100644 -index 0000000000000..89bced46ebcdd +index 0000000000000..1db9db2f86252 --- /dev/null -+++ b/drivers/platform/x86/surface_sam/Makefile -@@ -0,0 +1,16 @@ ++++ b/drivers/misc/surface_sam/clients/Makefile +@@ -0,0 +1,11 @@ +# SPDX-License-Identifier: GPL-2.0-or-later + -+# For include/trace/define_trace.h to include surface_sam_ssh_trace.h -+CFLAGS_surface_sam_ssh.o = -I$(src) -+ -+obj-$(CONFIG_SURFACE_SAM_SSH) += surface_sam_ssh.o -+obj-$(CONFIG_SURFACE_SAM_SAN) += surface_sam_san.o -+obj-$(CONFIG_SURFACE_SAM_DTX) += surface_sam_dtx.o -+obj-$(CONFIG_SURFACE_SAM_HPS) += surface_sam_hps.o -+obj-$(CONFIG_SURFACE_SAM_VHF) += surface_sam_vhf.o -+obj-$(CONFIG_SURFACE_SAM_SID) += surface_sam_sid.o -+obj-$(CONFIG_SURFACE_SAM_SID_GPELID) += surface_sam_sid_gpelid.o -+obj-$(CONFIG_SURFACE_SAM_SID_PERFMODE) += surface_sam_sid_perfmode.o -+obj-$(CONFIG_SURFACE_SAM_SID_POWER) += surface_sam_sid_power.o -+obj-$(CONFIG_SURFACE_SAM_SID_VHF) += surface_sam_sid_vhf.o -+obj-$(CONFIG_SURFACE_SAM_DEBUGFS) += surface_sam_debugfs.o -diff --git a/drivers/platform/x86/surface_sam/surface_sam_debugfs.c b/drivers/platform/x86/surface_sam/surface_sam_debugfs.c ++obj-$(CONFIG_SURFACE_SAM_DEBUGFS) += surface_sam_debugfs.o ++obj-$(CONFIG_SURFACE_SAM_SAN) += surface_sam_san.o ++obj-$(CONFIG_SURFACE_SAM_KBD) += surface_sam_vhf.o ++obj-$(CONFIG_SURFACE_SAM_DTX) += surface_sam_dtx.o ++obj-$(CONFIG_SURFACE_SAM_HPS) += surface_sam_hps.o ++obj-$(CONFIG_SURFACE_SAM_PERFMODE) += surface_sam_sid_perfmode.o ++obj-$(CONFIG_SURFACE_SAM_HID) += surface_sam_sid_vhf.o ++obj-$(CONFIG_SURFACE_SAM_PSY) += surface_sam_sid_power.o ++obj-$(CONFIG_SURFACE_SAM_DEVICE_HUB) += surface_sam_device_hub.o +diff --git a/drivers/misc/surface_sam/clients/surface_sam_debugfs.c b/drivers/misc/surface_sam/clients/surface_sam_debugfs.c new file mode 100644 -index 0000000000000..a7ee5cab6a0b4 +index 0000000000000..cc6c31de73e8b --- /dev/null -+++ b/drivers/platform/x86/surface_sam/surface_sam_debugfs.c ++++ b/drivers/misc/surface_sam/clients/surface_sam_debugfs.c @@ -0,0 +1,270 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + @@ -355,7 +688,7 @@ index 0000000000000..a7ee5cab6a0b4 +#include +#include + -+#include "surface_sam_ssh.h" ++#include + +#define SSAM_DBGDEV_NAME "surface_sam_dbgdev" +#define SSAM_DBGDEV_VERS 0x0100 @@ -615,11 +948,599 @@ index 0000000000000..a7ee5cab6a0b4 +MODULE_AUTHOR("Maximilian Luz "); +MODULE_DESCRIPTION("DebugFS entries for Surface Aggregator Module"); +MODULE_LICENSE("GPL"); -diff --git a/drivers/platform/x86/surface_sam/surface_sam_dtx.c b/drivers/platform/x86/surface_sam/surface_sam_dtx.c +diff --git a/drivers/misc/surface_sam/clients/surface_sam_device_hub.c b/drivers/misc/surface_sam/clients/surface_sam_device_hub.c new file mode 100644 -index 0000000000000..9c844bb0f7739 +index 0000000000000..182b3f4128fe3 --- /dev/null -+++ b/drivers/platform/x86/surface_sam/surface_sam_dtx.c ++++ b/drivers/misc/surface_sam/clients/surface_sam_device_hub.c +@@ -0,0 +1,582 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Surface Device Registry. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++ ++ ++/* -- Device registry structures. ------------------------------------------- */ ++ ++struct ssam_hub_cell { ++ struct ssam_device_uid uid; ++ void *data; ++}; ++ ++struct ssam_hub_desc { ++ const struct ssam_hub_cell *cells; ++ unsigned int num_cells; ++}; ++ ++ ++#define SSAM_DUID_HUB_MAIN SSAM_DUID(_HUB, 0x01, 0x00, 0x00) ++#define SSAM_DUID_HUB_BASE SSAM_DUID(_HUB, 0x02, 0x00, 0x00) ++ ++#define SSAM_DEFINE_HUB_DESC(__name, __cells) \ ++ struct ssam_hub_desc __name = { \ ++ .cells = __cells, \ ++ .num_cells = ARRAY_SIZE(__cells), \ ++ }; ++ ++#define SSAM_DEFINE_PLATFORM_HUB(__suffix) \ ++ static const SSAM_DEFINE_HUB_DESC(ssam_device_hub_##__suffix, \ ++ ssam_devices_##__suffix); \ ++ static const struct ssam_hub_cell ssam_platform_hubs_##__suffix[] = { \ ++ { SSAM_DUID_HUB_MAIN, (void *)&ssam_device_hub_##__suffix }, \ ++ }; \ ++ static const SSAM_DEFINE_HUB_DESC(ssam_platform_hub_##__suffix, \ ++ ssam_platform_hubs_##__suffix); \ ++ ++#define SSAM_DEFINE_PLATFORM_HUB_WITH_BASE(__suffix) \ ++ static const SSAM_DEFINE_HUB_DESC(ssam_device_hub_##__suffix, \ ++ ssam_devices_##__suffix); \ ++ static const SSAM_DEFINE_HUB_DESC(ssam_device_hub_##__suffix##_base, \ ++ ssam_devices_##__suffix##_base); \ ++ static const struct ssam_hub_cell ssam_platform_hubs_##__suffix[] = { \ ++ { SSAM_DUID_HUB_MAIN, (void *)&ssam_device_hub_##__suffix }, \ ++ { SSAM_DUID_HUB_BASE, (void *)&ssam_device_hub_##__suffix##_base },\ ++ }; \ ++ static const SSAM_DEFINE_HUB_DESC(ssam_platform_hub_##__suffix, \ ++ ssam_platform_hubs_##__suffix); \ ++ ++ ++/* -- Device registry. ------------------------------------------------------ */ ++ ++#define SSAM_DUID_BAT_AC SSAM_DUID(BAT, 0x01, 0x01, 0x01) ++#define SSAM_DUID_BAT_MAIN SSAM_DUID(BAT, 0x01, 0x01, 0x00) ++#define SSAM_DUID_BAT_SB3BASE SSAM_DUID(BAT, 0x02, 0x01, 0x00) ++ ++#define SSAM_DUID_TMP_PERF SSAM_DUID(TMP, 0x01, 0x00, 0x02) ++ ++#define SSAM_DUID_HID_KEYBOARD SSAM_DUID(HID, 0x02, 0x01, 0x00) ++#define SSAM_DUID_HID_TOUCHPAD SSAM_DUID(HID, 0x02, 0x03, 0x00) ++#define SSAM_DUID_HID_IID5 SSAM_DUID(HID, 0x02, 0x05, 0x00) ++#define SSAM_DUID_HID_IID6 SSAM_DUID(HID, 0x02, 0x06, 0x00) ++ ++ ++static const struct ssam_hub_cell ssam_devices_sb2[] = { ++ { SSAM_DUID_TMP_PERF }, ++}; ++ ++static const struct ssam_hub_cell ssam_devices_sb3[] = { ++ { SSAM_DUID_TMP_PERF }, ++ { SSAM_DUID_BAT_AC }, ++ { SSAM_DUID_BAT_MAIN }, ++}; ++ ++static const struct ssam_hub_cell ssam_devices_sb3_base[] = { ++ { SSAM_DUID_BAT_SB3BASE }, ++ { SSAM_DUID_HID_KEYBOARD }, ++ { SSAM_DUID_HID_TOUCHPAD }, ++ { SSAM_DUID_HID_IID5 }, ++ { SSAM_DUID_HID_IID6 }, ++}; ++ ++static const struct ssam_hub_cell ssam_devices_sl1[] = { ++ { SSAM_DUID_TMP_PERF }, ++}; ++ ++static const struct ssam_hub_cell ssam_devices_sl2[] = { ++ { SSAM_DUID_TMP_PERF }, ++}; ++ ++static const struct ssam_hub_cell ssam_devices_sl3[] = { ++ { SSAM_DUID_TMP_PERF }, ++ { SSAM_DUID_BAT_AC }, ++ { SSAM_DUID_BAT_MAIN }, ++ { SSAM_DUID_HID_KEYBOARD }, ++ { SSAM_DUID_HID_TOUCHPAD }, ++ { SSAM_DUID_HID_IID5 }, ++}; ++ ++static const struct ssam_hub_cell ssam_devices_sp5[] = { ++ { SSAM_DUID_TMP_PERF }, ++}; ++ ++static const struct ssam_hub_cell ssam_devices_sp6[] = { ++ { SSAM_DUID_TMP_PERF }, ++}; ++ ++static const struct ssam_hub_cell ssam_devices_sp7[] = { ++ { SSAM_DUID_TMP_PERF }, ++ { SSAM_DUID_BAT_AC }, ++ { SSAM_DUID_BAT_MAIN }, ++}; ++ ++SSAM_DEFINE_PLATFORM_HUB(sb2); ++SSAM_DEFINE_PLATFORM_HUB_WITH_BASE(sb3); ++SSAM_DEFINE_PLATFORM_HUB(sl1); ++SSAM_DEFINE_PLATFORM_HUB(sl2); ++SSAM_DEFINE_PLATFORM_HUB(sl3); ++SSAM_DEFINE_PLATFORM_HUB(sp5); ++SSAM_DEFINE_PLATFORM_HUB(sp6); ++SSAM_DEFINE_PLATFORM_HUB(sp7); ++ ++ ++/* -- Device registry helper functions. ------------------------------------- */ ++ ++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, ++ const struct ssam_hub_cell *cell) ++{ ++ struct ssam_device *sdev; ++ int status; ++ ++ sdev = ssam_device_alloc(ctrl, cell->uid); ++ if (!sdev) ++ return -ENOMEM; ++ ++ sdev->dev.parent = parent; ++ sdev->dev.platform_data = cell->data; ++ ++ 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, ++ const struct ssam_hub_desc *desc) ++{ ++ int status, i; ++ ++ for (i = 0; i < desc->num_cells; i++) { ++ status = ssam_hub_add_device(parent, ctrl, &desc->cells[i]); ++ if (status) ++ goto err; ++ } ++ ++ return 0; ++err: ++ ssam_hub_remove_devices(parent); ++ return status; ++} ++ ++ ++/* -- SSAM main-hub driver. ------------------------------------------------- */ ++ ++static int ssam_hub_probe(struct ssam_device *sdev) ++{ ++ const struct ssam_hub_desc *desc = dev_get_platdata(&sdev->dev); ++ ++ if (!desc) ++ return -ENODEV; ++ ++ return ssam_hub_add_devices(&sdev->dev, sdev->ctrl, desc); ++} ++ ++static void ssam_hub_remove(struct ssam_device *sdev) ++{ ++ ssam_hub_remove_devices(&sdev->dev); ++} ++ ++static const struct ssam_device_id ssam_hub_match[] = { ++ { SSAM_DEVICE(_HUB, 0x01, 0x00, 0x00) }, ++ { }, ++}; ++ ++static struct ssam_device_driver ssam_hub_driver = { ++ .probe = ssam_hub_probe, ++ .remove = ssam_hub_remove, ++ .match_table = ssam_hub_match, ++ .driver = { ++ .name = "surface_sam_hub", ++ .probe_type = PROBE_PREFER_ASYNCHRONOUS, ++ }, ++}; ++ ++ ++/* -- 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; ++ const struct ssam_hub_desc *devices; ++ ++ struct mutex lock; ++ 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, ++ .command_id = 0x0d, ++ .instance_id = 0x00, ++ .channel = 0x01, ++}); ++ ++#define SSAM_BAS_OPMODE_TABLET 0x00 ++#define SSAM_EVENT_BAS_CID_CONNECTION 0x0c ++ ++static int ssam_base_hub_query_state(struct ssam_device *sdev, ++ enum ssam_base_hub_state *state) ++{ ++ u8 opmode; ++ int status; ++ ++ status = ssam_bas_query_opmode(sdev->ctrl, &opmode); ++ if (status < 0) { ++ dev_err(&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_device *sdev = to_ssam_device(dev); ++ struct ssam_base_hub *hub = ssam_device_get_drvdata(sdev); ++ bool connected; ++ ++ mutex_lock(&hub->lock); ++ connected = hub->state == SSAM_BASE_HUB_CONNECTED; ++ mutex_unlock(&hub->lock); ++ ++ return snprintf(buf, PAGE_SIZE - 1, "%d\n", connected); ++} ++ ++static struct device_attribute ssam_base_hub_attr_state = ++ __ATTR(state, S_IRUGO, 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_device *sdev, ++ enum ssam_base_hub_state new) ++{ ++ struct ssam_base_hub *hub = ssam_device_get_drvdata(sdev); ++ int status = 0; ++ ++ mutex_lock(&hub->lock); ++ if (hub->state == new) { ++ mutex_unlock(&hub->lock); ++ return 0; ++ } ++ hub->state = new; ++ ++ if (hub->state == SSAM_BASE_HUB_CONNECTED) ++ status = ssam_hub_add_devices(&sdev->dev, sdev->ctrl, hub->devices); ++ ++ if (hub->state != SSAM_BASE_HUB_CONNECTED || status) ++ ssam_hub_remove_devices(&sdev->dev); ++ ++ mutex_unlock(&hub->lock); ++ ++ if (status) { ++ dev_err(&sdev->dev, "failed to update base-hub devices: %d\n", ++ status); ++ } ++ ++ return status; ++} ++ ++static u32 ssam_base_hub_notif(struct ssam_notifier_block *nb, ++ const struct ssam_event *event) ++{ ++ struct ssam_base_hub *hub; ++ struct ssam_device *sdev; ++ enum ssam_base_hub_state new; ++ ++ hub = container_of(nb, struct ssam_base_hub, notif.base); ++ 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; ++ ++ ssam_base_hub_update(sdev, new); ++ ++ /* ++ * 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 ssam_base_hub_resume(struct device *dev) ++{ ++ struct ssam_device *sdev = to_ssam_device(dev); ++ enum ssam_base_hub_state state; ++ int status; ++ ++ status = ssam_base_hub_query_state(sdev, &state); ++ if (status) ++ return status; ++ ++ return ssam_base_hub_update(sdev, state); ++} ++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) ++{ ++ const struct ssam_hub_desc *desc = dev_get_platdata(&sdev->dev); ++ const struct ssam_device_id *match; ++ enum ssam_base_hub_state state; ++ struct ssam_base_hub *hub; ++ int status; ++ ++ if (!desc) ++ return -ENODEV; ++ ++ match = ssam_device_get_match(sdev); ++ if (!match) ++ return -ENODEV; ++ ++ hub = devm_kzalloc(&sdev->dev, sizeof(*hub), GFP_KERNEL); ++ if (!hub) ++ return -ENOMEM; ++ ++ hub->sdev = sdev; ++ hub->devices = desc; ++ hub->state = SSAM_BASE_HUB_UNINITIALIZED; ++ ++ // TODO: still need to verify registry ++ hub->notif.base.priority = 1000; // 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.flags = SSAM_EVENT_SEQUENCED; ++ ++ status = ssam_notifier_register(sdev->ctrl, &hub->notif); ++ if (status) ++ return status; ++ ++ ssam_device_set_drvdata(sdev, hub); ++ ++ status = ssam_base_hub_query_state(sdev, &state); ++ if (status) { ++ ssam_notifier_unregister(sdev->ctrl, &hub->notif); ++ return status; ++ } ++ ++ status = ssam_base_hub_update(sdev, state); ++ if (status) { ++ ssam_notifier_unregister(sdev->ctrl, &hub->notif); ++ return status; ++ } ++ ++ status = sysfs_create_group(&sdev->dev.kobj, &ssam_base_hub_group); ++ if (status) { ++ ssam_notifier_unregister(sdev->ctrl, &hub->notif); ++ ssam_hub_remove_devices(&sdev->dev); ++ return status; ++ } ++ ++ 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); ++ ++ ssam_device_set_drvdata(sdev, NULL); ++ kfree(hub); ++} ++ ++static const struct ssam_device_id ssam_base_hub_match[] = { ++ { SSAM_DEVICE(_HUB, 0x02, 0x00, 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_sam_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[] = { ++ /* Surface Pro 4, 5, and 6 */ ++ { "MSHW0081", (unsigned long)&ssam_platform_hub_sp5 }, ++ ++ /* Surface Pro 6 (OMBR >= 0x10) */ ++ { "MSHW0111", (unsigned long)&ssam_platform_hub_sp6 }, ++ ++ /* Surface Pro 7 */ ++ { "MSHW0116", (unsigned long)&ssam_platform_hub_sp7 }, ++ ++ /* Surface Book 2 */ ++ { "MSHW0107", (unsigned long)&ssam_platform_hub_sb2 }, ++ ++ /* Surface Book 3 */ ++ { "MSHW0117", (unsigned long)&ssam_platform_hub_sb3 }, ++ ++ /* Surface Laptop 1 */ ++ { "MSHW0086", (unsigned long)&ssam_platform_hub_sl1 }, ++ ++ /* Surface Laptop 2 */ ++ { "MSHW0112", (unsigned long)&ssam_platform_hub_sl2 }, ++ ++ /* Surface Laptop 3 (13", Intel) */ ++ { "MSHW0114", (unsigned long)&ssam_platform_hub_sl3 }, ++ ++ /* Surface Laptop 3 (15", AMD) */ ++ { "MSHW0110", (unsigned long)&ssam_platform_hub_sl3 }, ++ ++ { }, ++}; ++MODULE_DEVICE_TABLE(acpi, ssam_platform_hub_match); ++ ++static int ssam_platform_hub_probe(struct platform_device *pdev) ++{ ++ const struct ssam_hub_desc *desc; ++ struct ssam_controller *ctrl; ++ int status; ++ ++ desc = acpi_device_get_match_data(&pdev->dev); ++ if (!desc) ++ 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. ++ */ ++ status = ssam_client_bind(&pdev->dev, &ctrl); ++ if (status) ++ return status == -ENXIO ? -EPROBE_DEFER : status; ++ ++ return ssam_hub_add_devices(&pdev->dev, ctrl, desc); ++} ++ ++static int ssam_platform_hub_remove(struct platform_device *pdev) ++{ ++ ssam_hub_remove_devices(&pdev->dev); ++ return 0; ++} ++ ++static struct platform_driver ssam_platform_hub_driver = { ++ .probe = ssam_platform_hub_probe, ++ .remove = ssam_platform_hub_remove, ++ .driver = { ++ .name = "surface_sam_platform_hub", ++ .acpi_match_table = ssam_platform_hub_match, ++ .probe_type = PROBE_PREFER_ASYNCHRONOUS, ++ }, ++}; ++ ++ ++/* -- Module initialization. ------------------------------------------------ */ ++ ++static int __init ssam_device_hub_init(void) ++{ ++ int status; ++ ++ status = platform_driver_register(&ssam_platform_hub_driver); ++ if (status) ++ goto err_platform; ++ ++ status = ssam_device_driver_register(&ssam_hub_driver); ++ if (status) ++ goto err_main; ++ ++ status = ssam_device_driver_register(&ssam_base_hub_driver); ++ if (status) ++ goto err_base; ++ ++ return 0; ++ ++err_base: ++ ssam_device_driver_unregister(&ssam_hub_driver); ++err_main: ++ platform_driver_unregister(&ssam_platform_hub_driver); ++err_platform: ++ return status; ++} ++ ++static void __exit ssam_device_hub_exit(void) ++{ ++ ssam_device_driver_unregister(&ssam_base_hub_driver); ++ ssam_device_driver_unregister(&ssam_hub_driver); ++ platform_driver_unregister(&ssam_platform_hub_driver); ++} ++ ++module_init(ssam_device_hub_init); ++module_exit(ssam_device_hub_exit); ++ ++MODULE_AUTHOR("Maximilian Luz "); ++MODULE_DESCRIPTION("Surface SAM Device Hub Driver for 5th Generation Surface Devices"); ++MODULE_LICENSE("GPL"); +diff --git a/drivers/misc/surface_sam/clients/surface_sam_dtx.c b/drivers/misc/surface_sam/clients/surface_sam_dtx.c +new file mode 100644 +index 0000000000000..df95124e166e2 +--- /dev/null ++++ b/drivers/misc/surface_sam/clients/surface_sam_dtx.c @@ -0,0 +1,582 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* @@ -640,7 +1561,7 @@ index 0000000000000..9c844bb0f7739 +#include +#include + -+#include "surface_sam_ssh.h" ++#include + + +#define USB_VENDOR_ID_MICROSOFT 0x045e @@ -1203,11 +2124,11 @@ index 0000000000000..9c844bb0f7739 +MODULE_AUTHOR("Maximilian Luz "); +MODULE_DESCRIPTION("Surface Detachment System (DTX) Driver for 5th Generation Surface Devices"); +MODULE_LICENSE("GPL"); -diff --git a/drivers/platform/x86/surface_sam/surface_sam_hps.c b/drivers/platform/x86/surface_sam/surface_sam_hps.c +diff --git a/drivers/misc/surface_sam/clients/surface_sam_hps.c b/drivers/misc/surface_sam/clients/surface_sam_hps.c new file mode 100644 -index 0000000000000..b11f9fa8095fb +index 0000000000000..cee2356054ae9 --- /dev/null -+++ b/drivers/platform/x86/surface_sam/surface_sam_hps.c ++++ b/drivers/misc/surface_sam/clients/surface_sam_hps.c @@ -0,0 +1,1287 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* @@ -1226,7 +2147,7 @@ index 0000000000000..b11f9fa8095fb +#include +#include + -+#include "surface_sam_ssh.h" ++#include +#include "surface_sam_san.h" + + @@ -2496,11 +3417,11 @@ index 0000000000000..b11f9fa8095fb +MODULE_AUTHOR("Maximilian Luz "); +MODULE_DESCRIPTION("Surface Hot-Plug System (HPS) and dGPU power-state Driver for Surface Book 2"); +MODULE_LICENSE("GPL"); -diff --git a/drivers/platform/x86/surface_sam/surface_sam_san.c b/drivers/platform/x86/surface_sam/surface_sam_san.c +diff --git a/drivers/misc/surface_sam/clients/surface_sam_san.c b/drivers/misc/surface_sam/clients/surface_sam_san.c new file mode 100644 -index 0000000000000..eab4e178a8450 +index 0000000000000..14e634f6e3c3c --- /dev/null -+++ b/drivers/platform/x86/surface_sam/surface_sam_san.c ++++ b/drivers/misc/surface_sam/clients/surface_sam_san.c @@ -0,0 +1,930 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* @@ -2515,7 +3436,7 @@ index 0000000000000..eab4e178a8450 +#include +#include + -+#include "surface_sam_ssh.h" ++#include +#include "surface_sam_san.h" + + @@ -3432,11 +4353,11 @@ index 0000000000000..eab4e178a8450 +MODULE_AUTHOR("Maximilian Luz "); +MODULE_DESCRIPTION("Surface ACPI Notify Driver for 5th Generation Surface Devices"); +MODULE_LICENSE("GPL"); -diff --git a/drivers/platform/x86/surface_sam/surface_sam_san.h b/drivers/platform/x86/surface_sam/surface_sam_san.h +diff --git a/drivers/misc/surface_sam/clients/surface_sam_san.h b/drivers/misc/surface_sam/clients/surface_sam_san.h new file mode 100644 index 0000000000000..3408dde964b3c --- /dev/null -+++ b/drivers/platform/x86/surface_sam/surface_sam_san.h ++++ b/drivers/misc/surface_sam/clients/surface_sam_san.h @@ -0,0 +1,30 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* @@ -3468,539 +4389,12 @@ index 0000000000000..3408dde964b3c +int surface_sam_san_set_rqsg_handler(surface_sam_san_rqsg_handler_fn fn, void *data); + +#endif /* _SURFACE_SAM_SAN_H */ -diff --git a/drivers/platform/x86/surface_sam/surface_sam_sid.c b/drivers/platform/x86/surface_sam/surface_sam_sid.c +diff --git a/drivers/misc/surface_sam/clients/surface_sam_sid_perfmode.c b/drivers/misc/surface_sam/clients/surface_sam_sid_perfmode.c new file mode 100644 -index 0000000000000..bcf9a569ee719 +index 0000000000000..4b5c8b4f9e0f1 --- /dev/null -+++ b/drivers/platform/x86/surface_sam/surface_sam_sid.c -@@ -0,0 +1,283 @@ -+// SPDX-License-Identifier: GPL-2.0-or-later -+/* -+ * Surface Integration Driver. -+ * MFD driver to provide device/model dependent functionality. -+ */ -+ -+#include -+#include -+#include -+#include -+#include -+ -+#include "surface_sam_sid_power.h" -+#include "surface_sam_sid_vhf.h" -+ -+ -+static const struct ssam_battery_properties ssam_battery_props_bat1 = { -+ .registry = SSAM_EVENT_REGISTRY_SAM, -+ .num = 0, -+ .channel = 1, -+ .instance = 1, -+}; -+ -+static const struct ssam_battery_properties ssam_battery_props_bat2_sb3 = { -+ .registry = SSAM_EVENT_REGISTRY_KIP, -+ .num = 1, -+ .channel = 2, -+ .instance = 1, -+}; -+ -+ -+static const struct ssam_hid_properties ssam_hid_props_keyboard = { -+ .registry = SSAM_EVENT_REGISTRY_REG, -+ .instance = 1, -+}; -+ -+static const struct ssam_hid_properties ssam_hid_props_touchpad = { -+ .registry = SSAM_EVENT_REGISTRY_REG, -+ .instance = 3, -+}; -+ -+static const struct ssam_hid_properties ssam_hid_props_iid5 = { -+ .registry = SSAM_EVENT_REGISTRY_REG, -+ .instance = 5, -+}; -+ -+static const struct ssam_hid_properties ssam_hid_props_iid6 = { -+ .registry = SSAM_EVENT_REGISTRY_REG, -+ .instance = 6, -+}; -+ -+ -+static const struct mfd_cell sid_devs_sp4[] = { -+ { .name = "surface_sam_sid_gpelid", .id = -1 }, -+ { .name = "surface_sam_sid_perfmode", .id = -1 }, -+ { }, -+}; -+ -+static const struct mfd_cell sid_devs_sp6[] = { -+ { .name = "surface_sam_sid_gpelid", .id = -1 }, -+ { .name = "surface_sam_sid_perfmode", .id = -1 }, -+ { }, -+}; -+ -+static const struct mfd_cell sid_devs_sp7[] = { -+ { .name = "surface_sam_sid_gpelid", .id = -1 }, -+ { .name = "surface_sam_sid_perfmode", .id = -1 }, -+ { .name = "surface_sam_sid_ac", .id = -1 }, -+ { -+ .name = "surface_sam_sid_battery", -+ .id = -1, -+ .platform_data = (void *)&ssam_battery_props_bat1, -+ .pdata_size = sizeof(struct ssam_battery_properties), -+ }, -+ { }, -+}; -+ -+static const struct mfd_cell sid_devs_sb1[] = { -+ { .name = "surface_sam_sid_gpelid", .id = -1 }, -+ { }, -+}; -+ -+static const struct mfd_cell sid_devs_sb2[] = { -+ { .name = "surface_sam_sid_gpelid", .id = -1 }, -+ { .name = "surface_sam_sid_perfmode", .id = -1 }, -+ { }, -+}; -+ -+static const struct mfd_cell sid_devs_sb3[] = { -+ { .name = "surface_sam_sid_gpelid", .id = -1 }, -+ { .name = "surface_sam_sid_perfmode", .id = -1 }, -+ { .name = "surface_sam_sid_ac", .id = -1 }, -+ { -+ .name = "surface_sam_sid_battery", -+ .id = 1, -+ .platform_data = (void *)&ssam_battery_props_bat1, -+ .pdata_size = sizeof(struct ssam_battery_properties), -+ }, -+ { -+ .name = "surface_sam_sid_battery", -+ .id = 2, -+ .platform_data = (void *)&ssam_battery_props_bat2_sb3, -+ .pdata_size = sizeof(struct ssam_battery_properties), -+ }, -+ { -+ .name = "surface_sam_sid_vhf", -+ .id = 1, -+ .platform_data = (void *)&ssam_hid_props_keyboard, -+ .pdata_size = sizeof(struct ssam_hid_properties), -+ }, -+ { -+ .name = "surface_sam_sid_vhf", -+ .id = 3, -+ .platform_data = (void *)&ssam_hid_props_touchpad, -+ .pdata_size = sizeof(struct ssam_hid_properties), -+ }, -+ { -+ .name = "surface_sam_sid_vhf", -+ .id = 5, -+ .platform_data = (void *)&ssam_hid_props_iid5, -+ .pdata_size = sizeof(struct ssam_hid_properties), -+ }, -+ { -+ .name = "surface_sam_sid_vhf", -+ .id = 6, -+ .platform_data = (void *)&ssam_hid_props_iid6, -+ .pdata_size = sizeof(struct ssam_hid_properties), -+ }, -+ { }, -+}; -+ -+static const struct mfd_cell sid_devs_sl1[] = { -+ { .name = "surface_sam_sid_gpelid", .id = -1 }, -+ { .name = "surface_sam_sid_perfmode", .id = -1 }, -+ { }, -+}; -+ -+static const struct mfd_cell sid_devs_sl2[] = { -+ { .name = "surface_sam_sid_gpelid", .id = -1 }, -+ { .name = "surface_sam_sid_perfmode", .id = -1 }, -+ { }, -+}; -+ -+static const struct mfd_cell sid_devs_sl3_13[] = { -+ { .name = "surface_sam_sid_gpelid", .id = -1 }, -+ { .name = "surface_sam_sid_perfmode", .id = -1 }, -+ { .name = "surface_sam_sid_ac", .id = -1 }, -+ { -+ .name = "surface_sam_sid_battery", -+ .id = -1, -+ .platform_data = (void *)&ssam_battery_props_bat1, -+ .pdata_size = sizeof(struct ssam_battery_properties), -+ }, -+ { -+ .name = "surface_sam_sid_vhf", -+ .id = 1, -+ .platform_data = (void *)&ssam_hid_props_keyboard, -+ .pdata_size = sizeof(struct ssam_hid_properties), -+ }, -+ { -+ .name = "surface_sam_sid_vhf", -+ .id = 3, -+ .platform_data = (void *)&ssam_hid_props_touchpad, -+ .pdata_size = sizeof(struct ssam_hid_properties), -+ }, -+ { -+ .name = "surface_sam_sid_vhf", -+ .id = 5, -+ .platform_data = (void *)&ssam_hid_props_iid5, -+ .pdata_size = sizeof(struct ssam_hid_properties), -+ }, -+ { }, -+}; -+ -+static const struct mfd_cell sid_devs_sl3_15[] = { -+ { .name = "surface_sam_sid_perfmode", .id = -1 }, -+ { .name = "surface_sam_sid_ac", .id = -1 }, -+ { -+ .name = "surface_sam_sid_battery", -+ .id = -1, -+ .platform_data = (void *)&ssam_battery_props_bat1, -+ .pdata_size = sizeof(struct ssam_battery_properties), -+ }, -+ { -+ .name = "surface_sam_sid_vhf", -+ .id = 1, -+ .platform_data = (void *)&ssam_hid_props_keyboard, -+ .pdata_size = sizeof(struct ssam_hid_properties), -+ }, -+ { -+ .name = "surface_sam_sid_vhf", -+ .id = 3, -+ .platform_data = (void *)&ssam_hid_props_touchpad, -+ .pdata_size = sizeof(struct ssam_hid_properties), -+ }, -+ { -+ .name = "surface_sam_sid_vhf", -+ .id = 5, -+ .platform_data = (void *)&ssam_hid_props_iid5, -+ .pdata_size = sizeof(struct ssam_hid_properties), -+ }, -+ { }, -+}; -+ -+static const struct acpi_device_id surface_sam_sid_match[] = { -+ /* Surface Pro 4, 5, and 6 */ -+ { "MSHW0081", (unsigned long)sid_devs_sp4 }, -+ -+ /* Surface Pro 6 (OMBR >= 0x10) */ -+ { "MSHW0111", (unsigned long)sid_devs_sp6 }, -+ -+ /* Surface Pro 7 */ -+ { "MSHW0116", (unsigned long)sid_devs_sp7 }, -+ -+ /* Surface Book 1 */ -+ { "MSHW0080", (unsigned long)sid_devs_sb1 }, -+ -+ /* Surface Book 2 */ -+ { "MSHW0107", (unsigned long)sid_devs_sb2 }, -+ -+ /* Surface Book 3 */ -+ { "MSHW0117", (unsigned long)sid_devs_sb3 }, -+ -+ /* Surface Laptop 1 */ -+ { "MSHW0086", (unsigned long)sid_devs_sl1 }, -+ -+ /* Surface Laptop 2 */ -+ { "MSHW0112", (unsigned long)sid_devs_sl2 }, -+ -+ /* Surface Laptop 3 (13") */ -+ { "MSHW0114", (unsigned long)sid_devs_sl3_13 }, -+ -+ /* Surface Laptop 3 (15") */ -+ { "MSHW0110", (unsigned long)sid_devs_sl3_15 }, -+ -+ { }, -+}; -+MODULE_DEVICE_TABLE(acpi, surface_sam_sid_match); -+ -+ -+static int surface_sam_sid_probe(struct platform_device *pdev) -+{ -+ const struct acpi_device_id *match; -+ const struct mfd_cell *cells, *p; -+ -+ match = acpi_match_device(surface_sam_sid_match, &pdev->dev); -+ if (!match) -+ return -ENODEV; -+ -+ cells = (struct mfd_cell *)match->driver_data; -+ if (!cells) -+ return -ENODEV; -+ -+ for (p = cells; p->name; ++p) { -+ /* just count */ -+ } -+ -+ if (p == cells) -+ return -ENODEV; -+ -+ return mfd_add_devices(&pdev->dev, 0, cells, p - cells, NULL, 0, NULL); -+} -+ -+static int surface_sam_sid_remove(struct platform_device *pdev) -+{ -+ mfd_remove_devices(&pdev->dev); -+ return 0; -+} -+ -+static struct platform_driver surface_sam_sid = { -+ .probe = surface_sam_sid_probe, -+ .remove = surface_sam_sid_remove, -+ .driver = { -+ .name = "surface_sam_sid", -+ .acpi_match_table = surface_sam_sid_match, -+ .probe_type = PROBE_PREFER_ASYNCHRONOUS, -+ }, -+}; -+module_platform_driver(surface_sam_sid); -+ -+MODULE_AUTHOR("Maximilian Luz "); -+MODULE_DESCRIPTION("Surface Integration Driver for 5th Generation Surface Devices"); -+MODULE_LICENSE("GPL"); -diff --git a/drivers/platform/x86/surface_sam/surface_sam_sid_gpelid.c b/drivers/platform/x86/surface_sam/surface_sam_sid_gpelid.c -new file mode 100644 -index 0000000000000..f0cee43c859b4 ---- /dev/null -+++ b/drivers/platform/x86/surface_sam/surface_sam_sid_gpelid.c -@@ -0,0 +1,232 @@ -+// SPDX-License-Identifier: GPL-2.0-or-later -+/* -+ * Surface Lid driver to enable wakeup from suspend via the lid. -+ */ -+ -+#include -+#include -+#include -+#include -+#include -+ -+ -+struct sid_lid_device { -+ const char *acpi_path; -+ const u32 gpe_number; -+}; -+ -+ -+static const struct sid_lid_device lid_device_l17 = { -+ .acpi_path = "\\_SB.LID0", -+ .gpe_number = 0x17, -+}; -+ -+static const struct sid_lid_device lid_device_l4D = { -+ .acpi_path = "\\_SB.LID0", -+ .gpe_number = 0x4D, -+}; -+ -+static const struct sid_lid_device lid_device_l4F = { -+ .acpi_path = "\\_SB.LID0", -+ .gpe_number = 0x4F, -+}; -+ -+static const struct sid_lid_device lid_device_l57 = { -+ .acpi_path = "\\_SB.LID0", -+ .gpe_number = 0x57, -+}; -+ -+ -+static const struct dmi_system_id dmi_lid_device_table[] = { -+ { -+ .ident = "Surface Pro 4", -+ .matches = { -+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 4"), -+ }, -+ .driver_data = (void *)&lid_device_l17, -+ }, -+ { -+ .ident = "Surface Pro 5", -+ .matches = { -+ /* match for SKU here due to generic product name "Surface Pro" */ -+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1796"), -+ }, -+ .driver_data = (void *)&lid_device_l4F, -+ }, -+ { -+ .ident = "Surface Pro 5 (LTE)", -+ .matches = { -+ /* match for SKU here due to generic product name "Surface Pro" */ -+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1807"), -+ }, -+ .driver_data = (void *)&lid_device_l4F, -+ }, -+ { -+ .ident = "Surface Pro 6", -+ .matches = { -+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 6"), -+ }, -+ .driver_data = (void *)&lid_device_l4F, -+ }, -+ { -+ .ident = "Surface Pro 7", -+ .matches = { -+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 7"), -+ }, -+ .driver_data = (void *)&lid_device_l4D, -+ }, -+ { -+ .ident = "Surface Book 1", -+ .matches = { -+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book"), -+ }, -+ .driver_data = (void *)&lid_device_l17, -+ }, -+ { -+ .ident = "Surface Book 2", -+ .matches = { -+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book 2"), -+ }, -+ .driver_data = (void *)&lid_device_l17, -+ }, -+ { -+ .ident = "Surface Book 3", -+ .matches = { -+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book 3"), -+ }, -+ .driver_data = (void *)&lid_device_l4D, -+ }, -+ { -+ .ident = "Surface Laptop 1", -+ .matches = { -+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop"), -+ }, -+ .driver_data = (void *)&lid_device_l57, -+ }, -+ { -+ .ident = "Surface Laptop 2", -+ .matches = { -+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop 2"), -+ }, -+ .driver_data = (void *)&lid_device_l57, -+ }, -+ { -+ .ident = "Surface Laptop 3 (13\")", -+ .matches = { -+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Laptop_3_1867:1868"), -+ }, -+ .driver_data = (void *)&lid_device_l4D, -+ }, -+ { } -+}; -+ -+ -+static int sid_lid_enable_wakeup(const struct sid_lid_device *dev, bool enable) -+{ -+ int action = enable ? ACPI_GPE_ENABLE : ACPI_GPE_DISABLE; -+ int status; -+ -+ status = acpi_set_gpe_wake_mask(NULL, dev->gpe_number, action); -+ if (status) -+ return -EFAULT; -+ -+ return 0; -+} -+ -+ -+static int surface_sam_sid_gpelid_suspend(struct device *dev) -+{ -+ const struct sid_lid_device *ldev; -+ -+ ldev = dev_get_drvdata(dev); -+ return sid_lid_enable_wakeup(ldev, true); -+} -+ -+static int surface_sam_sid_gpelid_resume(struct device *dev) -+{ -+ const struct sid_lid_device *ldev; -+ -+ ldev = dev_get_drvdata(dev); -+ return sid_lid_enable_wakeup(ldev, false); -+} -+ -+static SIMPLE_DEV_PM_OPS(surface_sam_sid_gpelid_pm, -+ surface_sam_sid_gpelid_suspend, -+ surface_sam_sid_gpelid_resume); -+ -+ -+static int surface_sam_sid_gpelid_probe(struct platform_device *pdev) -+{ -+ const struct dmi_system_id *match; -+ struct sid_lid_device *dev; -+ acpi_handle lid_handle; -+ int status; -+ -+ match = dmi_first_match(dmi_lid_device_table); -+ if (!match) -+ return -ENODEV; -+ -+ dev = match->driver_data; -+ if (!dev) -+ return -ENODEV; -+ -+ status = acpi_get_handle(NULL, (acpi_string)dev->acpi_path, &lid_handle); -+ if (status) -+ return -EFAULT; -+ -+ status = acpi_setup_gpe_for_wake(lid_handle, NULL, dev->gpe_number); -+ if (status) -+ return -EFAULT; -+ -+ status = acpi_enable_gpe(NULL, dev->gpe_number); -+ if (status) -+ return -EFAULT; -+ -+ status = sid_lid_enable_wakeup(dev, false); -+ if (status) { -+ acpi_disable_gpe(NULL, dev->gpe_number); -+ return status; -+ } -+ -+ platform_set_drvdata(pdev, dev); -+ return 0; -+} -+ -+static int surface_sam_sid_gpelid_remove(struct platform_device *pdev) -+{ -+ struct sid_lid_device *dev = platform_get_drvdata(pdev); -+ -+ /* restore default behavior without this module */ -+ sid_lid_enable_wakeup(dev, false); -+ acpi_disable_gpe(NULL, dev->gpe_number); -+ -+ platform_set_drvdata(pdev, NULL); -+ return 0; -+} -+ -+static struct platform_driver surface_sam_sid_gpelid = { -+ .probe = surface_sam_sid_gpelid_probe, -+ .remove = surface_sam_sid_gpelid_remove, -+ .driver = { -+ .name = "surface_sam_sid_gpelid", -+ .pm = &surface_sam_sid_gpelid_pm, -+ .probe_type = PROBE_PREFER_ASYNCHRONOUS, -+ }, -+}; -+module_platform_driver(surface_sam_sid_gpelid); -+ -+MODULE_AUTHOR("Maximilian Luz "); -+MODULE_DESCRIPTION("Surface Lid Driver for 5th Generation Surface Devices"); -+MODULE_LICENSE("GPL"); -+MODULE_ALIAS("platform:surface_sam_sid_gpelid"); -diff --git a/drivers/platform/x86/surface_sam/surface_sam_sid_perfmode.c b/drivers/platform/x86/surface_sam/surface_sam_sid_perfmode.c -new file mode 100644 -index 0000000000000..e0b1e42c2087f ---- /dev/null -+++ b/drivers/platform/x86/surface_sam/surface_sam_sid_perfmode.c -@@ -0,0 +1,214 @@ ++++ b/drivers/misc/surface_sam/clients/surface_sam_sid_perfmode.c +@@ -0,0 +1,194 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Surface Performance Mode Driver. @@ -4010,9 +4404,9 @@ index 0000000000000..e0b1e42c2087f +#include +#include +#include -+#include ++#include + -+#include "surface_sam_ssh.h" ++#include + + +#define SID_PARAM_PERM 0644 @@ -4038,10 +4432,6 @@ index 0000000000000..e0b1e42c2087f + __SID_PARAM_PERF_MODE__END = 4, +}; + -+struct spm_data { -+ struct ssam_controller *ctrl; -+}; -+ + +struct ssam_perf_info { + __le32 mode; @@ -4106,11 +4496,11 @@ index 0000000000000..e0b1e42c2087f + +static ssize_t perf_mode_show(struct device *dev, struct device_attribute *attr, char *data) +{ -+ struct spm_data *d = dev_get_drvdata(dev); ++ struct ssam_device *sdev = to_ssam_device(dev); + struct ssam_perf_info info; + int status; + -+ status = ssam_tmp_perf_mode_get(d->ctrl, &info); ++ status = ssam_tmp_perf_mode_get(sdev->ctrl, &info); + if (status) { + dev_err(dev, "failed to get current performance mode: %d\n", status); + return -EIO; @@ -4122,7 +4512,7 @@ index 0000000000000..e0b1e42c2087f +static ssize_t perf_mode_store(struct device *dev, struct device_attribute *attr, + const char *data, size_t count) +{ -+ struct spm_data *d = dev_get_drvdata(dev); ++ struct ssam_device *sdev = to_ssam_device(dev); + int perf_mode; + int status; + @@ -4130,7 +4520,7 @@ index 0000000000000..e0b1e42c2087f + if (status) + return status; + -+ status = ssam_tmp_perf_mode_set(d->ctrl, perf_mode); ++ status = ssam_tmp_perf_mode_set(sdev->ctrl, perf_mode); + if (status) + return status; + @@ -4153,74 +4543,58 @@ index 0000000000000..e0b1e42c2087f +static const DEVICE_ATTR_RW(perf_mode); + + -+static int surface_sam_sid_perfmode_probe(struct platform_device *pdev) ++static int surface_sam_sid_perfmode_probe(struct ssam_device *sdev) +{ -+ struct ssam_controller *ctrl; -+ struct spm_data *data; + int status; + -+ // link to ec -+ status = ssam_client_bind(&pdev->dev, &ctrl); -+ if (status) -+ return status == -ENXIO ? -EPROBE_DEFER : status; -+ -+ data = devm_kzalloc(&pdev->dev, sizeof(struct spm_data), GFP_KERNEL); -+ if (!data) -+ return -ENOMEM; -+ -+ data->ctrl = ctrl; -+ platform_set_drvdata(pdev, data); -+ + // set initial perf_mode + if (param_perf_mode_init != SID_PARAM_PERF_MODE_AS_IS) { -+ status = ssam_tmp_perf_mode_set(ctrl, param_perf_mode_init); ++ status = ssam_tmp_perf_mode_set(sdev->ctrl, param_perf_mode_init); + if (status) + return status; + } + + // register perf_mode attribute -+ status = sysfs_create_file(&pdev->dev.kobj, &dev_attr_perf_mode.attr); ++ status = sysfs_create_file(&sdev->dev.kobj, &dev_attr_perf_mode.attr); + if (status) -+ goto err_sysfs; ++ ssam_tmp_perf_mode_set(sdev->ctrl, param_perf_mode_exit); + -+ return 0; -+ -+err_sysfs: -+ ssam_tmp_perf_mode_set(ctrl, param_perf_mode_exit); + return status; +} + -+static int surface_sam_sid_perfmode_remove(struct platform_device *pdev) ++static void surface_sam_sid_perfmode_remove(struct ssam_device *sdev) +{ -+ struct spm_data *data = platform_get_drvdata(pdev); -+ -+ sysfs_remove_file(&pdev->dev.kobj, &dev_attr_perf_mode.attr); -+ ssam_tmp_perf_mode_set(data->ctrl, param_perf_mode_exit); -+ -+ platform_set_drvdata(pdev, NULL); -+ return 0; ++ sysfs_remove_file(&sdev->dev.kobj, &dev_attr_perf_mode.attr); ++ ssam_tmp_perf_mode_set(sdev->ctrl, param_perf_mode_exit); +} + -+static struct platform_driver surface_sam_sid_perfmode = { ++ ++static const struct ssam_device_id ssam_perfmode_match[] = { ++ { SSAM_DEVICE(TMP, 0x01, 0x00, 0x02) }, ++ { }, ++}; ++MODULE_DEVICE_TABLE(ssam, ssam_perfmode_match); ++ ++static struct ssam_device_driver surface_sam_sid_perfmode = { + .probe = surface_sam_sid_perfmode_probe, + .remove = surface_sam_sid_perfmode_remove, ++ .match_table = ssam_perfmode_match, + .driver = { + .name = "surface_sam_sid_perfmode", + .probe_type = PROBE_PREFER_ASYNCHRONOUS, + }, +}; -+module_platform_driver(surface_sam_sid_perfmode); ++module_ssam_device_driver(surface_sam_sid_perfmode); + +MODULE_AUTHOR("Maximilian Luz "); +MODULE_DESCRIPTION("Surface Performance Mode Driver for 5th Generation Surface Devices"); +MODULE_LICENSE("GPL"); -+MODULE_ALIAS("platform:surface_sam_sid_perfmode"); -diff --git a/drivers/platform/x86/surface_sam/surface_sam_sid_power.c b/drivers/platform/x86/surface_sam/surface_sam_sid_power.c +diff --git a/drivers/misc/surface_sam/clients/surface_sam_sid_power.c b/drivers/misc/surface_sam/clients/surface_sam_sid_power.c new file mode 100644 -index 0000000000000..64a3d46a128cc +index 0000000000000..2e5b2c181030f --- /dev/null -+++ b/drivers/platform/x86/surface_sam/surface_sam_sid_power.c -@@ -0,0 +1,1054 @@ ++++ b/drivers/misc/surface_sam/clients/surface_sam_sid_power.c +@@ -0,0 +1,1112 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Surface SID Battery/AC Driver. @@ -4228,19 +4602,15 @@ index 0000000000000..64a3d46a128cc + */ + +#include -+#include +#include +#include ++#include +#include -+#include +#include ++#include +#include + -+#include "surface_sam_ssh.h" -+#include "surface_sam_sid_power.h" -+ -+#define SPWR_WARN KERN_WARNING KBUILD_MODNAME ": " -+#define SPWR_DEBUG KERN_DEBUG KBUILD_MODNAME ": " ++#include + + +// TODO: check BIX/BST for unknown/unsupported 0xffffffff entries @@ -4314,59 +4684,59 @@ index 0000000000000..64a3d46a128cc + + +/* Get battery status (_STA) */ -+static SSAM_DEFINE_SYNC_REQUEST_MD_R(ssam_bat_get_sta, __le32, { ++static 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) */ -+static SSAM_DEFINE_SYNC_REQUEST_MD_R(ssam_bat_get_bix, struct spwr_bix, { ++static 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) */ -+static SSAM_DEFINE_SYNC_REQUEST_MD_R(ssam_bat_get_bst, struct spwr_bst, { ++static 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) */ -+static SSAM_DEFINE_SYNC_REQUEST_MD_W(ssam_bat_set_btp, __le32, { ++static SSAM_DEFINE_SYNC_REQUEST_CL_W(ssam_bat_set_btp, __le32, { + .target_category = SSAM_SSH_TC_BAT, + .command_id = 0x04, +}); + +/* Get platform power soruce for battery (DPTF PSRC) */ -+static SSAM_DEFINE_SYNC_REQUEST_MD_R(ssam_bat_get_psrc, __le32, { ++static SSAM_DEFINE_SYNC_REQUEST_CL_R(ssam_bat_get_psrc, __le32, { + .target_category = SSAM_SSH_TC_BAT, + .command_id = 0x0d, +}); + +/* Get maximum platform power for battery (DPTF PMAX) */ +__always_unused -+static SSAM_DEFINE_SYNC_REQUEST_MD_R(ssam_bat_get_pmax, __le32, { ++static SSAM_DEFINE_SYNC_REQUEST_CL_R(ssam_bat_get_pmax, __le32, { + .target_category = SSAM_SSH_TC_BAT, + .command_id = 0x0b, +}); + +/* Get adapter rating (DPTF ARTG) */ +__always_unused -+static SSAM_DEFINE_SYNC_REQUEST_MD_R(ssam_bat_get_artg, __le32, { ++static SSAM_DEFINE_SYNC_REQUEST_CL_R(ssam_bat_get_artg, __le32, { + .target_category = SSAM_SSH_TC_BAT, + .command_id = 0x0f, +}); + +/* Unknown (DPTF PSOC) */ +__always_unused -+static SSAM_DEFINE_SYNC_REQUEST_MD_R(ssam_bat_get_psoc, __le32, { ++static SSAM_DEFINE_SYNC_REQUEST_CL_R(ssam_bat_get_psoc, __le32, { + .target_category = SSAM_SSH_TC_BAT, + .command_id = 0x0c, +}); + +/* Unknown (DPTF CHGI/ INT3403 SPPC) */ +__always_unused -+static SSAM_DEFINE_SYNC_REQUEST_MD_W(ssam_bat_set_chgi, __le32, { ++static SSAM_DEFINE_SYNC_REQUEST_CL_W(ssam_bat_set_chgi, __le32, { + .target_category = SSAM_SSH_TC_BAT, + .command_id = 0x0e, +}); @@ -4376,10 +4746,13 @@ index 0000000000000..64a3d46a128cc + * Common Power-Subsystem Interface. + */ + ++struct spwr_psy_properties { ++ const char *name; ++ struct ssam_event_registry registry; ++}; ++ +struct spwr_battery_device { -+ struct platform_device *pdev; -+ struct ssam_controller *ctrl; -+ const struct ssam_battery_properties *p; ++ struct ssam_device *sdev; + + char name[32]; + struct power_supply *psy; @@ -4399,8 +4772,7 @@ index 0000000000000..64a3d46a128cc +}; + +struct spwr_ac_device { -+ struct platform_device *pdev; -+ struct ssam_controller *ctrl; ++ struct ssam_device *sdev; + + char name[32]; + struct power_supply *psy; @@ -4455,9 +4827,8 @@ index 0000000000000..64a3d46a128cc + + +static int spwr_battery_register(struct spwr_battery_device *bat, -+ struct platform_device *pdev, -+ struct ssam_controller *ctrl, -+ const struct ssam_battery_properties *p); ++ struct ssam_device *sdev, ++ struct ssam_event_registry registry); + +static void spwr_battery_unregister(struct spwr_battery_device *bat); + @@ -4470,8 +4841,7 @@ index 0000000000000..64a3d46a128cc + +static inline int spwr_battery_load_sta(struct spwr_battery_device *bat) +{ -+ return ssam_bat_get_sta(bat->ctrl, bat->p->channel, bat->p->instance, -+ &bat->sta); ++ return ssam_bat_get_sta(bat->sdev, &bat->sta); +} + +static inline int spwr_battery_load_bix(struct spwr_battery_device *bat) @@ -4479,8 +4849,7 @@ index 0000000000000..64a3d46a128cc + if (!spwr_battery_present(bat)) + return 0; + -+ return ssam_bat_get_bix(bat->ctrl, bat->p->channel, bat->p->instance, -+ &bat->bix); ++ return ssam_bat_get_bix(bat->sdev, &bat->bix); +} + +static inline int spwr_battery_load_bst(struct spwr_battery_device *bat) @@ -4488,21 +4857,21 @@ index 0000000000000..64a3d46a128cc + if (!spwr_battery_present(bat)) + return 0; + -+ return ssam_bat_get_bst(bat->ctrl, bat->p->channel, bat->p->instance, -+ &bat->bst); ++ return ssam_bat_get_bst(bat->sdev, &bat->bst); +} + + -+static inline int spwr_battery_set_alarm_unlocked(struct spwr_battery_device *bat, u32 value) ++static inline int spwr_battery_set_alarm_unlocked( ++ struct spwr_battery_device *bat, u32 value) +{ + __le32 alarm = cpu_to_le32(value); + + bat->alarm = value; -+ return ssam_bat_set_btp(bat->ctrl, bat->p->channel, bat->p->instance, -+ &alarm); ++ return ssam_bat_set_btp(bat->sdev, &alarm); +} + -+static inline int spwr_battery_set_alarm(struct spwr_battery_device *bat, u32 value) ++static inline int spwr_battery_set_alarm(struct spwr_battery_device *bat, ++ u32 value) +{ + int status; + @@ -4513,11 +4882,14 @@ index 0000000000000..64a3d46a128cc + return status; +} + -+static inline int spwr_battery_update_bst_unlocked(struct spwr_battery_device *bat, bool cached) ++static inline int spwr_battery_update_bst_unlocked( ++ struct spwr_battery_device *bat, bool cached) +{ -+ unsigned long cache_deadline = bat->timestamp + msecs_to_jiffies(cache_time); ++ unsigned long cache_deadline; + int status; + ++ cache_deadline = bat->timestamp + msecs_to_jiffies(cache_time); ++ + if (cached && bat->timestamp && time_is_after_jiffies(cache_deadline)) + return 0; + @@ -4577,7 +4949,14 @@ index 0000000000000..64a3d46a128cc + +static inline int spwr_ac_update_unlocked(struct spwr_ac_device *ac) +{ -+ return ssam_bat_get_psrc(ac->ctrl, 0x01, 0x01, &ac->state); ++ int status; ++ u32 old = ac->state; ++ ++ status = 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) @@ -4613,7 +4992,8 @@ index 0000000000000..64a3d46a128cc + // if the unit has changed, re-add the battery + if (unit != get_unaligned_le32(&bat->bix.power_unit)) { + spwr_battery_unregister(bat); -+ status = spwr_battery_register(bat, bat->pdev, bat->ctrl, bat->p); ++ status = spwr_battery_register(bat, bat->sdev, ++ bat->notif.event.reg); + } + + return status; @@ -4666,18 +5046,21 @@ index 0000000000000..64a3d46a128cc + int status; + + status = spwr_ac_update(ac); -+ if (!status) ++ if (status > 0) + power_supply_changed(ac->psy); + + return status; +} + -+static u32 spwr_notify_bat(struct ssam_notifier_block *nb, const struct ssam_event *event) ++static u32 spwr_notify_bat(struct ssam_notifier_block *nb, ++ const struct ssam_event *event) +{ -+ struct spwr_battery_device *bat = container_of(nb, struct spwr_battery_device, notif.base); ++ struct spwr_battery_device *bat; + int status; + -+ dev_dbg(&bat->pdev->dev, "power event (cid = 0x%02x, iid = %d, chn = %d)\n", ++ bat = container_of(nb, struct spwr_battery_device, notif.base); ++ ++ dev_dbg(&bat->sdev->dev, "power event (cid = 0x%02x, iid = %d, chn = %d)\n", + event->command_id, event->instance_id, event->channel); + + // handled here, needs to be handled for all channels/instances @@ -4686,11 +5069,7 @@ index 0000000000000..64a3d46a128cc + return ssam_notifier_from_errno(status) | SSAM_NOTIF_HANDLED; + } + -+ // check for the correct channel and instance ID -+ if (event->channel != bat->p->channel) -+ return 0; -+ -+ if (event->instance_id != bat->p->instance) ++ if (!ssam_event_matches_device(bat->sdev->uid, event)) + return 0; + + switch (event->command_id) { @@ -4709,18 +5088,29 @@ index 0000000000000..64a3d46a128cc + return ssam_notifier_from_errno(status) | SSAM_NOTIF_HANDLED; +} + -+static u32 spwr_notify_ac(struct ssam_notifier_block *nb, const struct ssam_event *event) ++static u32 spwr_notify_ac(struct ssam_notifier_block *nb, ++ const struct ssam_event *event) +{ -+ struct spwr_ac_device *ac = container_of(nb, struct spwr_ac_device, notif.base); ++ struct spwr_ac_device *ac; + int status; + -+ dev_dbg(&ac->pdev->dev, "power event (cid = 0x%02x, iid = %d, chn = %d)\n", ++ ac = container_of(nb, struct spwr_ac_device, notif.base); ++ ++ dev_dbg(&ac->sdev->dev, "power event (cid = 0x%02x, iid = %d, chn = %d)\n", + event->command_id, event->instance_id, event->channel); + -+ // AC has IID = 0 -+ if (event->instance_id != 0) ++ if (event->target_category != ac->sdev->uid.category) + return 0; + ++ /* ++ * Allow events of all channels/instances here. Global adapter status ++ * seems to be handled via channel=1 and instance=1, but events are ++ * reported on all channels/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_PWR_CID_ADAPTER: + status = spwr_notify_adapter_ac(ac); @@ -4734,15 +5124,19 @@ index 0000000000000..64a3d46a128cc +static void spwr_battery_update_bst_workfn(struct work_struct *work) +{ + struct delayed_work *dwork = to_delayed_work(work); -+ struct spwr_battery_device *bat = container_of(dwork, struct spwr_battery_device, update_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) + power_supply_changed(bat->psy); + -+ if (status) -+ dev_err(&bat->pdev->dev, "failed to update battery state: %d\n", status); ++ if (status) { ++ dev_err(&bat->sdev->dev, "failed to update battery state: %d\n", ++ status); ++ } +} + + @@ -4984,16 +5378,21 @@ index 0000000000000..64a3d46a128cc +}; + + ++static void spwr_ac_set_name(struct spwr_ac_device *ac, const char *name) ++{ ++ strncpy(ac->name, name, ARRAY_SIZE(ac->name) - 1); ++} ++ +static int spwr_ac_register(struct spwr_ac_device *ac, -+ struct platform_device *pdev, -+ struct ssam_controller *ctrl) ++ struct ssam_device *sdev, ++ struct ssam_event_registry registry) +{ + struct power_supply_config psy_cfg = {}; + __le32 sta; + int status; + + // make sure the device is there and functioning properly -+ status = ssam_bat_get_sta(ctrl, 0x01, 0x01, &sta); ++ status = ssam_bat_get_sta(sdev, &sta); + if (status) + return status; + @@ -5002,19 +5401,16 @@ index 0000000000000..64a3d46a128cc + + psy_cfg.drv_data = ac; + -+ ac->pdev = pdev; -+ ac->ctrl = ctrl; ++ ac->sdev = sdev; + mutex_init(&ac->lock); + -+ snprintf(ac->name, ARRAY_SIZE(ac->name), "ADP0"); -+ + 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; + -+ ac->psy = power_supply_register(&ac->pdev->dev, &ac->psy_desc, &psy_cfg); ++ ac->psy = power_supply_register(&ac->sdev->dev, &ac->psy_desc, &psy_cfg); + if (IS_ERR(ac->psy)) { + status = PTR_ERR(ac->psy); + goto err_psy; @@ -5022,12 +5418,12 @@ index 0000000000000..64a3d46a128cc + + ac->notif.base.priority = 1; + ac->notif.base.fn = spwr_notify_ac; -+ ac->notif.event.reg = SSAM_EVENT_REGISTRY_SAM; -+ ac->notif.event.id.target_category = SSAM_SSH_TC_BAT; ++ ac->notif.event.reg = registry; ++ ac->notif.event.id.target_category = sdev->uid.category; + ac->notif.event.id.instance = 0; + ac->notif.event.flags = SSAM_EVENT_SEQUENCED; + -+ status = ssam_notifier_register(ctrl, &ac->notif); ++ status = ssam_notifier_register(sdev->ctrl, &ac->notif); + if (status) + goto err_notif; + @@ -5042,27 +5438,30 @@ index 0000000000000..64a3d46a128cc + +static int spwr_ac_unregister(struct spwr_ac_device *ac) +{ -+ ssam_notifier_unregister(ac->ctrl, &ac->notif); ++ ssam_notifier_unregister(ac->sdev->ctrl, &ac->notif); + power_supply_unregister(ac->psy); + mutex_destroy(&ac->lock); + return 0; +} + ++static void spwr_battery_set_name(struct spwr_battery_device *bat, ++ const char *name) ++{ ++ strncpy(bat->name, name, ARRAY_SIZE(bat->name) - 1); ++} ++ +static int spwr_battery_register(struct spwr_battery_device *bat, -+ struct platform_device *pdev, -+ struct ssam_controller *ctrl, -+ const struct ssam_battery_properties *p) ++ struct ssam_device *sdev, ++ struct ssam_event_registry registry) +{ + struct power_supply_config psy_cfg = {}; + __le32 sta; + int status; + -+ bat->pdev = pdev; -+ bat->ctrl = ctrl; -+ bat->p = p; ++ bat->sdev = sdev; + + // make sure the device is there and functioning properly -+ status = ssam_bat_get_sta(ctrl, bat->p->channel, bat->p->instance, &sta); ++ status = ssam_bat_get_sta(sdev, &sta); + if (status) + return status; + @@ -5080,7 +5479,6 @@ index 0000000000000..64a3d46a128cc + return status; + } + -+ snprintf(bat->name, ARRAY_SIZE(bat->name), "BAT%d", bat->p->num); + bat->psy_desc.name = bat->name; + bat->psy_desc.type = POWER_SUPPLY_TYPE_BATTERY; + @@ -5099,7 +5497,7 @@ index 0000000000000..64a3d46a128cc + + INIT_DELAYED_WORK(&bat->update_work, spwr_battery_update_bst_workfn); + -+ bat->psy = power_supply_register(&bat->pdev->dev, &bat->psy_desc, &psy_cfg); ++ bat->psy = power_supply_register(&bat->sdev->dev, &bat->psy_desc, &psy_cfg); + if (IS_ERR(bat->psy)) { + status = PTR_ERR(bat->psy); + goto err_psy; @@ -5107,12 +5505,12 @@ index 0000000000000..64a3d46a128cc + + bat->notif.base.priority = 1; + bat->notif.base.fn = spwr_notify_bat; -+ bat->notif.event.reg = p->registry; -+ bat->notif.event.id.target_category = SSAM_SSH_TC_BAT; ++ bat->notif.event.reg = registry; ++ bat->notif.event.id.target_category = sdev->uid.category; + bat->notif.event.id.instance = 0; + bat->notif.event.flags = SSAM_EVENT_SEQUENCED; + -+ status = ssam_notifier_register(ctrl, &bat->notif); ++ status = ssam_notifier_register(sdev->ctrl, &bat->notif); + if (status) + goto err_notif; + @@ -5123,7 +5521,7 @@ index 0000000000000..64a3d46a128cc + return 0; + +err_file: -+ ssam_notifier_unregister(ctrl, &bat->notif); ++ ssam_notifier_unregister(sdev->ctrl, &bat->notif); +err_notif: + power_supply_unregister(bat->psy); +err_psy: @@ -5133,7 +5531,7 @@ index 0000000000000..64a3d46a128cc + +static void spwr_battery_unregister(struct spwr_battery_device *bat) +{ -+ ssam_notifier_unregister(bat->ctrl, &bat->notif); ++ ssam_notifier_unregister(bat->sdev->ctrl, &bat->notif); + cancel_delayed_work_sync(&bat->update_work); + device_remove_file(&bat->psy->dev, &alarm_attr); + power_supply_unregister(bat->psy); @@ -5145,52 +5543,73 @@ index 0000000000000..64a3d46a128cc + * Battery Driver. + */ + -+#ifdef CONFIG_PM_SLEEP +static int surface_sam_sid_battery_resume(struct device *dev) +{ + struct spwr_battery_device *bat; + ++ // TODO: run this on workqueue ++ + bat = dev_get_drvdata(dev); + return spwr_battery_recheck(bat); +} -+#else -+#define surface_sam_sid_battery_resume NULL -+#endif ++SIMPLE_DEV_PM_OPS(surface_sam_sid_battery_pm, ++ NULL, surface_sam_sid_battery_resume); + -+SIMPLE_DEV_PM_OPS(surface_sam_sid_battery_pm, NULL, surface_sam_sid_battery_resume); -+ -+static int surface_sam_sid_battery_probe(struct platform_device *pdev) ++static int surface_sam_sid_battery_probe(struct ssam_device *sdev) +{ ++ const struct spwr_psy_properties *p; + struct spwr_battery_device *bat; -+ struct ssam_controller *ctrl; + int status; + -+ // link to ec -+ status = ssam_client_bind(&pdev->dev, &ctrl); -+ if (status) -+ return status == -ENXIO ? -EPROBE_DEFER : status; ++ p = ssam_device_get_match_data(sdev); ++ if (!p) ++ return -ENODEV; + -+ bat = devm_kzalloc(&pdev->dev, sizeof(struct spwr_battery_device), GFP_KERNEL); ++ bat = devm_kzalloc(&sdev->dev, sizeof(*bat), GFP_KERNEL); + if (!bat) + return -ENOMEM; + -+ platform_set_drvdata(pdev, bat); -+ return spwr_battery_register(bat, pdev, ctrl, pdev->dev.platform_data); ++ spwr_battery_set_name(bat, p->name); ++ ssam_device_set_drvdata(sdev, bat); ++ ++ status = spwr_battery_register(bat, sdev, p->registry); ++ if (status) ++ ssam_device_set_drvdata(sdev, NULL); ++ ++ return status; +} + -+static int surface_sam_sid_battery_remove(struct platform_device *pdev) ++static void surface_sam_sid_battery_remove(struct ssam_device *sdev) +{ + struct spwr_battery_device *bat; + -+ bat = platform_get_drvdata(pdev); ++ bat = ssam_device_get_drvdata(sdev); + spwr_battery_unregister(bat); + -+ return 0; ++ ssam_device_set_drvdata(sdev, NULL); +} + -+static struct platform_driver surface_sam_sid_battery = { ++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_REG, ++}; ++ ++static const struct ssam_device_id surface_sam_sid_battery_match[] = { ++ { SSAM_DEVICE(BAT, 0x01, 0x01, 0x00), (unsigned long)&spwr_psy_props_bat1 }, ++ { SSAM_DEVICE(BAT, 0x02, 0x01, 0x00), (unsigned long)&spwr_psy_props_bat2_sb3 }, ++ { }, ++}; ++MODULE_DEVICE_TABLE(ssam, surface_sam_sid_battery_match); ++ ++static struct ssam_device_driver surface_sam_sid_battery = { + .probe = surface_sam_sid_battery_probe, + .remove = surface_sam_sid_battery_remove, ++ .match_table = surface_sam_sid_battery_match, + .driver = { + .name = "surface_sam_sid_battery", + .pm = &surface_sam_sid_battery_pm, @@ -5203,40 +5622,55 @@ index 0000000000000..64a3d46a128cc + * AC Driver. + */ + -+static int surface_sam_sid_ac_probe(struct platform_device *pdev) ++// TODO: check/update on resume, call power_supply_changed? ++ ++static int surface_sam_sid_ac_probe(struct ssam_device *sdev) +{ ++ const struct spwr_psy_properties *p; + struct spwr_ac_device *ac; -+ struct ssam_controller *ctrl; + int status; + -+ // link to ec -+ status = ssam_client_bind(&pdev->dev, &ctrl); -+ if (status) -+ return status == -ENXIO ? -EPROBE_DEFER : status; ++ p = ssam_device_get_match_data(sdev); ++ if (!p) ++ return -ENODEV; + -+ ac = devm_kzalloc(&pdev->dev, sizeof(struct spwr_ac_device), GFP_KERNEL); ++ ac = devm_kzalloc(&sdev->dev, sizeof(*ac), GFP_KERNEL); + if (!ac) + return -ENOMEM; + -+ status = spwr_ac_register(ac, pdev, ctrl); ++ spwr_ac_set_name(ac, p->name); ++ ssam_device_set_drvdata(sdev, ac); ++ ++ status = spwr_ac_register(ac, sdev, p->registry); + if (status) -+ return status; ++ ssam_device_set_drvdata(sdev, NULL); + -+ platform_set_drvdata(pdev, ac); -+ return 0; ++ return status; +} + -+static int surface_sam_sid_ac_remove(struct platform_device *pdev) ++static void surface_sam_sid_ac_remove(struct ssam_device *sdev) +{ -+ struct spwr_ac_device *ac; ++ struct spwr_ac_device *ac = ssam_device_get_drvdata(sdev); + -+ ac = platform_get_drvdata(pdev); -+ return spwr_ac_unregister(ac); ++ spwr_ac_unregister(ac); ++ ssam_device_set_drvdata(sdev, NULL); +} + -+static struct platform_driver surface_sam_sid_ac = { ++static const struct spwr_psy_properties spwr_psy_props_adp1 = { ++ .name = "ADP1", ++ .registry = SSAM_EVENT_REGISTRY_SAM, ++}; ++ ++static const struct ssam_device_id surface_sam_sid_ac_match[] = { ++ { SSAM_DEVICE(BAT, 0x01, 0x01, 0x01), (unsigned long)&spwr_psy_props_adp1 }, ++ { }, ++}; ++MODULE_DEVICE_TABLE(ssam, surface_sam_sid_ac_match); ++ ++static struct ssam_device_driver surface_sam_sid_ac = { + .probe = surface_sam_sid_ac_probe, + .remove = surface_sam_sid_ac_remove, ++ .match_table = surface_sam_sid_ac_match, + .driver = { + .name = "surface_sam_sid_ac", + .probe_type = PROBE_PREFER_ASYNCHRONOUS, @@ -5248,13 +5682,13 @@ index 0000000000000..64a3d46a128cc +{ + int status; + -+ status = platform_driver_register(&surface_sam_sid_battery); ++ status = ssam_device_driver_register(&surface_sam_sid_battery); + if (status) + return status; + -+ status = platform_driver_register(&surface_sam_sid_ac); ++ status = ssam_device_driver_register(&surface_sam_sid_ac); + if (status) { -+ platform_driver_unregister(&surface_sam_sid_battery); ++ ssam_device_driver_unregister(&surface_sam_sid_battery); + return status; + } + @@ -5263,8 +5697,8 @@ index 0000000000000..64a3d46a128cc + +static void __exit surface_sam_sid_power_exit(void) +{ -+ platform_driver_unregister(&surface_sam_sid_battery); -+ platform_driver_unregister(&surface_sam_sid_ac); ++ ssam_device_driver_unregister(&surface_sam_sid_battery); ++ ssam_device_driver_unregister(&surface_sam_sid_ac); +} + +module_init(surface_sam_sid_power_init); @@ -5273,36 +5707,12 @@ index 0000000000000..64a3d46a128cc +MODULE_AUTHOR("Maximilian Luz "); +MODULE_DESCRIPTION("Surface Battery/AC Driver for 7th Generation Surface Devices"); +MODULE_LICENSE("GPL"); -+MODULE_ALIAS("platform:surface_sam_sid_ac"); -+MODULE_ALIAS("platform:surface_sam_sid_battery"); -diff --git a/drivers/platform/x86/surface_sam/surface_sam_sid_power.h b/drivers/platform/x86/surface_sam/surface_sam_sid_power.h +diff --git a/drivers/misc/surface_sam/clients/surface_sam_sid_vhf.c b/drivers/misc/surface_sam/clients/surface_sam_sid_vhf.c new file mode 100644 -index 0000000000000..d8d9509b7d122 +index 0000000000000..444c9f883f25a --- /dev/null -+++ b/drivers/platform/x86/surface_sam/surface_sam_sid_power.h -@@ -0,0 +1,16 @@ -+ -+#ifndef _SURFACE_SAM_SID_POWER_H -+#define _SURFACE_SAM_SID_POWER_H -+ -+#include -+#include "surface_sam_ssh.h" -+ -+ -+struct ssam_battery_properties { -+ struct ssam_event_registry registry; -+ u8 num; -+ u8 channel; -+ u8 instance; -+}; -+ -+#endif /* _SURFACE_SAM_SID_POWER_H */ -diff --git a/drivers/platform/x86/surface_sam/surface_sam_sid_vhf.c b/drivers/platform/x86/surface_sam/surface_sam_sid_vhf.c -new file mode 100644 -index 0000000000000..a6059d6796619 ---- /dev/null -+++ b/drivers/platform/x86/surface_sam/surface_sam_sid_vhf.c -@@ -0,0 +1,429 @@ ++++ b/drivers/misc/surface_sam/clients/surface_sam_sid_vhf.c +@@ -0,0 +1,430 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Microsofs Surface HID (VHF) driver for HID input events via SAM. @@ -5315,8 +5725,8 @@ index 0000000000000..a6059d6796619 +#include +#include + -+#include "surface_sam_ssh.h" -+#include "surface_sam_sid_vhf.h" ++#include ++ + +#define SID_VHF_INPUT_NAME "Microsoft Surface HID" + @@ -5324,11 +5734,12 @@ index 0000000000000..a6059d6796619 + +#define VHF_HID_STARTED 0 + -+struct sid_vhf { -+ struct platform_device *dev; -+ struct ssam_controller *ctrl; -+ const struct ssam_hid_properties *p; ++struct sid_vhf_properties { ++ struct ssam_event_registry registry; ++}; + ++struct sid_vhf { ++ struct ssam_device *sdev; + struct ssam_event_notifier notif; + + struct hid_device *hid; @@ -5404,8 +5815,7 @@ index 0000000000000..a6059d6796619 +} __packed; + + -+static int vhf_get_metadata(struct ssam_controller *ctrl, u8 iid, -+ struct vhf_device_metadata *meta) ++static int vhf_get_metadata(struct ssam_device *sdev, struct vhf_device_metadata *meta) +{ + struct surface_sam_sid_vhf_meta_resp data = {}; + struct ssam_request rqst; @@ -5417,10 +5827,10 @@ index 0000000000000..a6059d6796619 + data.rqst.length = 0x76; + data.rqst.end = 0; + -+ rqst.target_category = SSAM_SSH_TC_HID;; ++ rqst.target_category = sdev->uid.category; + rqst.command_id = 0x04; -+ rqst.instance_id = iid; -+ rqst.channel = 0x02; ++ rqst.instance_id = sdev->uid.instance; ++ rqst.channel = sdev->uid.channel; + rqst.flags = SSAM_REQUEST_HAS_RESPONSE; + rqst.length = sizeof(struct surface_sam_sid_vhf_meta_rqst); + rqst.payload = (u8 *)&data.rqst; @@ -5429,7 +5839,7 @@ index 0000000000000..a6059d6796619 + rsp.length = 0; + rsp.pointer = (u8 *)&data; + -+ status = ssam_request_sync(ctrl, &rqst, &rsp); ++ status = ssam_request_sync(sdev->ctrl, &rqst, &rsp); + if (status) + return status; + @@ -5438,9 +5848,8 @@ index 0000000000000..a6059d6796619 + return 0; +} + -+static int vhf_get_hid_descriptor(struct hid_device *hid, u8 iid, u8 **desc, int *size) ++static int vhf_get_hid_descriptor(struct ssam_device *sdev, u8 **desc, int *size) +{ -+ struct sid_vhf *vhf = dev_get_drvdata(hid->dev.parent); + struct surface_sam_sid_vhf_meta_resp data = {}; + struct ssam_request rqst; + struct ssam_response rsp; @@ -5452,10 +5861,10 @@ index 0000000000000..a6059d6796619 + data.rqst.length = 0x76; + data.rqst.end = 0; + -+ rqst.target_category = SSAM_SSH_TC_HID; ++ rqst.target_category = sdev->uid.category; + rqst.command_id = 0x04; -+ rqst.instance_id = iid; -+ rqst.channel = 0x02; ++ rqst.instance_id = sdev->uid.instance; ++ rqst.channel = sdev->uid.channel;; + rqst.flags = SSAM_REQUEST_HAS_RESPONSE; + rqst.length = sizeof(struct surface_sam_sid_vhf_meta_rqst); + rqst.payload = (u8 *)&data.rqst; @@ -5465,7 +5874,7 @@ index 0000000000000..a6059d6796619 + rsp.pointer = (u8 *)&data; + + // first fetch 00 to get the total length -+ status = ssam_request_sync(vhf->ctrl, &rqst, &rsp); ++ status = ssam_request_sync(sdev->ctrl, &rqst, &rsp); + if (status) + return status; + @@ -5481,7 +5890,7 @@ index 0000000000000..a6059d6796619 + data.rqst.end = 0; + + while (!data.rqst.end && data.rqst.offset < len) { -+ status = ssam_request_sync(vhf->ctrl, &rqst, &rsp); ++ status = ssam_request_sync(sdev->ctrl, &rqst, &rsp); + if (status) { + kfree(buf); + return status; @@ -5503,7 +5912,7 @@ index 0000000000000..a6059d6796619 + int ret = 0, size; + u8 *buf; + -+ ret = vhf_get_hid_descriptor(hid, vhf->p->instance, &buf, &size); ++ ret = vhf_get_hid_descriptor(vhf->sdev, &buf, &size); + if (ret != 0) { + hid_err(hid, "Failed to read HID descriptor from device: %d\n", ret); + return -EIO; @@ -5562,9 +5971,9 @@ index 0000000000000..a6059d6796619 + return -EIO; + } + -+ rqst.target_category = SSAM_SSH_TC_HID; -+ rqst.channel = 0x02; -+ rqst.instance_id = vhf->p->instance; ++ rqst.target_category = vhf->sdev->uid.category; ++ rqst.channel = vhf->sdev->uid.channel; ++ rqst.instance_id = vhf->sdev->uid.instance; + rqst.command_id = cid; + rqst.flags = reqtype == HID_REQ_GET_REPORT ? SSAM_REQUEST_HAS_RESPONSE : 0; + rqst.length = reqtype == HID_REQ_GET_REPORT ? 1 : len; @@ -5576,7 +5985,7 @@ index 0000000000000..a6059d6796619 + + hid_dbg(hid, "%s: sending to cid=%#04x snc=%#04x\n", __func__, cid, HID_REQ_GET_REPORT == reqtype); + -+ status = ssam_request_sync(vhf->ctrl, &rqst, &rsp); ++ status = ssam_request_sync(vhf->sdev->ctrl, &rqst, &rsp); + hid_dbg(hid, "%s: status %i\n", __func__, status); + + if (status) @@ -5598,7 +6007,7 @@ index 0000000000000..a6059d6796619 +}; + + -+static struct hid_device *sid_vhf_create_hid_device(struct platform_device *pdev, struct vhf_device_metadata *meta) ++static struct hid_device *sid_vhf_create_hid_device(struct ssam_device *sdev, struct vhf_device_metadata *meta) +{ + struct hid_device *hid; + @@ -5606,7 +6015,7 @@ index 0000000000000..a6059d6796619 + if (IS_ERR(hid)) + return hid; + -+ hid->dev.parent = &pdev->dev; ++ hid->dev.parent = &sdev->dev; + + hid->bus = BUS_VIRTUAL; + hid->vendor = meta->vendor_id; @@ -5624,13 +6033,7 @@ index 0000000000000..a6059d6796619 + struct sid_vhf *vhf = container_of(nb, struct sid_vhf, notif.base); + int status; + -+ if (event->target_category != SSAM_SSH_TC_HID) -+ return 0; -+ -+ if (event->channel != 0x02) -+ return 0; -+ -+ if (event->instance_id != vhf->p->instance) ++ if (!ssam_event_matches_device(vhf->sdev->uid, event)) + return 0; + + if (event->command_id != 0x00 && event->command_id != 0x03 && event->command_id != 0x04) @@ -5644,49 +6047,45 @@ index 0000000000000..a6059d6796619 + return ssam_notifier_from_errno(status) | SSAM_NOTIF_HANDLED; +} + -+static int surface_sam_sid_vhf_probe(struct platform_device *pdev) ++static int surface_sam_sid_vhf_probe(struct ssam_device *sdev) +{ -+ const struct ssam_hid_properties *p = pdev->dev.platform_data; -+ struct ssam_controller *ctrl; ++ const struct sid_vhf_properties *p; + struct sid_vhf *vhf; + struct vhf_device_metadata meta = {}; + struct hid_device *hid; + int status; + -+ // add device link to EC -+ status = ssam_client_bind(&pdev->dev, &ctrl); -+ if (status) -+ return status == -ENXIO ? -EPROBE_DEFER : status; ++ p = ssam_device_get_match_data(sdev); ++ if (!p) ++ return -ENODEV; + -+ vhf = kzalloc(sizeof(struct sid_vhf), GFP_KERNEL); ++ vhf = kzalloc(sizeof(*vhf), GFP_KERNEL); + if (!vhf) + return -ENOMEM; + -+ status = vhf_get_metadata(ctrl, p->instance, &meta); ++ status = vhf_get_metadata(sdev, &meta); + if (status) + goto err_create_hid; + -+ hid = sid_vhf_create_hid_device(pdev, &meta); ++ hid = sid_vhf_create_hid_device(sdev, &meta); + if (IS_ERR(hid)) { + status = PTR_ERR(hid); + goto err_create_hid; + } + -+ vhf->dev = pdev; -+ vhf->ctrl = ctrl; -+ vhf->p = pdev->dev.platform_data; ++ vhf->sdev = sdev; + vhf->hid = hid; + + vhf->notif.base.priority = 1; + vhf->notif.base.fn = sid_vhf_event_handler; + vhf->notif.event.reg = p->registry; -+ vhf->notif.event.id.target_category = SSAM_SSH_TC_HID; -+ vhf->notif.event.id.instance = p->instance; ++ vhf->notif.event.id.target_category = sdev->uid.category; ++ vhf->notif.event.id.instance = sdev->uid.instance; + vhf->notif.event.flags = 0; + -+ platform_set_drvdata(pdev, vhf); ++ ssam_device_set_drvdata(sdev, vhf); + -+ status = ssam_notifier_register(ctrl, &vhf->notif); ++ status = ssam_notifier_register(sdev->ctrl, &vhf->notif); + if (status) + goto err_notif; + @@ -5697,173 +6096,362 @@ index 0000000000000..a6059d6796619 + return 0; + +err_add_hid: -+ ssam_notifier_unregister(ctrl, &vhf->notif); ++ ssam_notifier_unregister(sdev->ctrl, &vhf->notif); +err_notif: + hid_destroy_device(hid); -+ platform_set_drvdata(pdev, NULL); ++ ssam_device_set_drvdata(sdev, NULL); +err_create_hid: + kfree(vhf); + return status; +} + -+static int surface_sam_sid_vhf_remove(struct platform_device *pdev) ++static void surface_sam_sid_vhf_remove(struct ssam_device *sdev) +{ -+ struct sid_vhf *vhf = platform_get_drvdata(pdev); ++ struct sid_vhf *vhf = ssam_device_get_drvdata(sdev); + -+ ssam_notifier_unregister(vhf->ctrl, &vhf->notif); ++ ssam_notifier_unregister(sdev->ctrl, &vhf->notif); + hid_destroy_device(vhf->hid); + kfree(vhf); + -+ platform_set_drvdata(pdev, NULL); -+ return 0; ++ ssam_device_set_drvdata(sdev, NULL); +} + -+static struct platform_driver surface_sam_sid_vhf = { ++static const struct sid_vhf_properties sid_vhf_default_props = { ++ .registry = SSAM_EVENT_REGISTRY_REG, ++}; ++ ++static const struct ssam_device_id surface_sam_sid_vhf_match[] = { ++ { ++ SSAM_DEVICE(HID, SSAM_ANY_CHN, SSAM_ANY_IID, 0x00), ++ .driver_data = (unsigned long)&sid_vhf_default_props ++ }, ++ { }, ++}; ++MODULE_DEVICE_TABLE(ssam, surface_sam_sid_vhf_match); ++ ++static struct ssam_device_driver surface_sam_sid_vhf = { + .probe = surface_sam_sid_vhf_probe, + .remove = surface_sam_sid_vhf_remove, ++ .match_table = surface_sam_sid_vhf_match, + .driver = { + .name = "surface_sam_sid_vhf", + .probe_type = PROBE_PREFER_ASYNCHRONOUS, + }, +}; -+module_platform_driver(surface_sam_sid_vhf); ++module_ssam_device_driver(surface_sam_sid_vhf); + +MODULE_AUTHOR("Blaž Hrastnik "); +MODULE_DESCRIPTION("Driver for HID devices connected via Surface SAM"); +MODULE_LICENSE("GPL"); -+MODULE_ALIAS("platform:surface_sam_sid_vhf"); -diff --git a/drivers/platform/x86/surface_sam/surface_sam_sid_vhf.h b/drivers/platform/x86/surface_sam/surface_sam_sid_vhf.h +diff --git a/drivers/misc/surface_sam/clients/surface_sam_vhf.c b/drivers/misc/surface_sam/clients/surface_sam_vhf.c new file mode 100644 -index 0000000000000..d956de5cf877a +index 0000000000000..95e730a86fb0c --- /dev/null -+++ b/drivers/platform/x86/surface_sam/surface_sam_sid_vhf.h -@@ -0,0 +1,14 @@ -+ -+#ifndef _SURFACE_SAM_SID_VHF_H -+#define _SURFACE_SAM_SID_VHF_H -+ -+#include -+#include "surface_sam_ssh.h" -+ -+ -+struct ssam_hid_properties { -+ struct ssam_event_registry registry; -+ u8 instance; -+}; -+ -+#endif /* _SURFACE_SAM_SID_VHF_H */ -diff --git a/drivers/platform/x86/surface_sam/surface_sam_ssh.c b/drivers/platform/x86/surface_sam/surface_sam_ssh.c -new file mode 100644 -index 0000000000000..3f39f383e3651 ---- /dev/null -+++ b/drivers/platform/x86/surface_sam/surface_sam_ssh.c -@@ -0,0 +1,5330 @@ ++++ b/drivers/misc/surface_sam/clients/surface_sam_vhf.c +@@ -0,0 +1,266 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* -+ * Surface Serial Hub (SSH) driver for communication with the Surface/System -+ * Aggregator Module. ++ * Virtual HID Framework (VHF) driver for input events via SAM. ++ * Used for keyboard input events on the Surface Laptops. + */ + -+#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++ ++ ++#define USB_VENDOR_ID_MICROSOFT 0x045e ++#define USB_DEVICE_ID_MS_VHF 0xf001 ++ ++#define VHF_INPUT_NAME "Microsoft Virtual HID Framework Device" ++ ++ ++struct vhf_drvdata { ++ struct platform_device *dev; ++ struct ssam_controller *ctrl; ++ ++ struct ssam_event_notifier notif; ++ ++ struct hid_device *hid; ++}; ++ ++ ++/* ++ * These report descriptors have been extracted from a Surface Book 2. ++ * They seems to be similar enough to be usable on the Surface Laptop. ++ */ ++static const u8 vhf_hid_desc[] = { ++ // keyboard descriptor (event command ID 0x03) ++ 0x05, 0x01, /* Usage Page (Desktop), */ ++ 0x09, 0x06, /* Usage (Keyboard), */ ++ 0xA1, 0x01, /* Collection (Application), */ ++ 0x85, 0x01, /* Report ID (1), */ ++ 0x15, 0x00, /* Logical Minimum (0), */ ++ 0x25, 0x01, /* Logical Maximum (1), */ ++ 0x75, 0x01, /* Report Size (1), */ ++ 0x95, 0x08, /* Report Count (8), */ ++ 0x05, 0x07, /* Usage Page (Keyboard), */ ++ 0x19, 0xE0, /* Usage Minimum (KB Leftcontrol), */ ++ 0x29, 0xE7, /* Usage Maximum (KB Right GUI), */ ++ 0x81, 0x02, /* Input (Variable), */ ++ 0x75, 0x08, /* Report Size (8), */ ++ 0x95, 0x0A, /* Report Count (10), */ ++ 0x19, 0x00, /* Usage Minimum (None), */ ++ 0x29, 0x91, /* Usage Maximum (KB LANG2), */ ++ 0x26, 0xFF, 0x00, /* Logical Maximum (255), */ ++ 0x81, 0x00, /* Input, */ ++ 0x05, 0x0C, /* Usage Page (Consumer), */ ++ 0x0A, 0xC0, 0x02, /* Usage (02C0h), */ ++ 0xA1, 0x02, /* Collection (Logical), */ ++ 0x1A, 0xC1, 0x02, /* Usage Minimum (02C1h), */ ++ 0x2A, 0xC6, 0x02, /* Usage Maximum (02C6h), */ ++ 0x95, 0x06, /* Report Count (6), */ ++ 0xB1, 0x03, /* Feature (Constant, Variable), */ ++ 0xC0, /* End Collection, */ ++ 0x05, 0x08, /* Usage Page (LED), */ ++ 0x19, 0x01, /* Usage Minimum (01h), */ ++ 0x29, 0x03, /* Usage Maximum (03h), */ ++ 0x75, 0x01, /* Report Size (1), */ ++ 0x95, 0x03, /* Report Count (3), */ ++ 0x25, 0x01, /* Logical Maximum (1), */ ++ 0x91, 0x02, /* Output (Variable), */ ++ 0x95, 0x05, /* Report Count (5), */ ++ 0x91, 0x01, /* Output (Constant), */ ++ 0xC0, /* End Collection, */ ++ ++ // media key descriptor (event command ID 0x04) ++ 0x05, 0x0C, /* Usage Page (Consumer), */ ++ 0x09, 0x01, /* Usage (Consumer Control), */ ++ 0xA1, 0x01, /* Collection (Application), */ ++ 0x85, 0x03, /* Report ID (3), */ ++ 0x75, 0x10, /* Report Size (16), */ ++ 0x15, 0x00, /* Logical Minimum (0), */ ++ 0x26, 0xFF, 0x03, /* Logical Maximum (1023), */ ++ 0x19, 0x00, /* Usage Minimum (00h), */ ++ 0x2A, 0xFF, 0x03, /* Usage Maximum (03FFh), */ ++ 0x81, 0x00, /* Input, */ ++ 0xC0, /* End Collection, */ ++}; ++ ++ ++static int vhf_hid_start(struct hid_device *hid) ++{ ++ hid_dbg(hid, "%s\n", __func__); ++ return 0; ++} ++ ++static void vhf_hid_stop(struct hid_device *hid) ++{ ++ hid_dbg(hid, "%s\n", __func__); ++} ++ ++static int vhf_hid_open(struct hid_device *hid) ++{ ++ hid_dbg(hid, "%s\n", __func__); ++ return 0; ++} ++ ++static void vhf_hid_close(struct hid_device *hid) ++{ ++ hid_dbg(hid, "%s\n", __func__); ++} ++ ++static int vhf_hid_parse(struct hid_device *hid) ++{ ++ return hid_parse_report(hid, (u8 *)vhf_hid_desc, ARRAY_SIZE(vhf_hid_desc)); ++} ++ ++static int vhf_hid_raw_request(struct hid_device *hid, unsigned char reportnum, ++ u8 *buf, size_t len, unsigned char rtype, ++ int reqtype) ++{ ++ hid_dbg(hid, "%s\n", __func__); ++ return 0; ++} ++ ++static int vhf_hid_output_report(struct hid_device *hid, u8 *buf, size_t len) ++{ ++ hid_dbg(hid, "%s\n", __func__); ++ print_hex_dump_debug("report:", DUMP_PREFIX_OFFSET, 16, 1, buf, len, false); ++ ++ return len; ++} ++ ++static struct hid_ll_driver vhf_hid_ll_driver = { ++ .start = vhf_hid_start, ++ .stop = vhf_hid_stop, ++ .open = vhf_hid_open, ++ .close = vhf_hid_close, ++ .parse = vhf_hid_parse, ++ .raw_request = vhf_hid_raw_request, ++ .output_report = vhf_hid_output_report, ++}; ++ ++ ++static struct hid_device *vhf_create_hid_device(struct platform_device *pdev) ++{ ++ struct hid_device *hid; ++ ++ hid = hid_allocate_device(); ++ if (IS_ERR(hid)) ++ return hid; ++ ++ hid->dev.parent = &pdev->dev; ++ ++ hid->bus = BUS_VIRTUAL; ++ hid->vendor = USB_VENDOR_ID_MICROSOFT; ++ hid->product = USB_DEVICE_ID_MS_VHF; ++ ++ hid->ll_driver = &vhf_hid_ll_driver; ++ ++ sprintf(hid->name, "%s", VHF_INPUT_NAME); ++ ++ return hid; ++} ++ ++static u32 vhf_event_handler(struct ssam_notifier_block *nb, const struct ssam_event *event) ++{ ++ struct vhf_drvdata *drvdata = container_of(nb, struct vhf_drvdata, notif.base); ++ int status; ++ ++ if (event->target_category != 0x08) ++ return 0; ++ ++ if (event->command_id == 0x03 || event->command_id == 0x04) { ++ status = hid_input_report(drvdata->hid, HID_INPUT_REPORT, (u8 *)&event->data[0], event->length, 1); ++ return ssam_notifier_from_errno(status) | SSAM_NOTIF_HANDLED; ++ } ++ ++ return 0; ++} ++ ++static int surface_sam_vhf_probe(struct platform_device *pdev) ++{ ++ struct ssam_controller *ctrl; ++ struct vhf_drvdata *drvdata; ++ struct hid_device *hid; ++ int status; ++ ++ // add device link to EC ++ status = ssam_client_bind(&pdev->dev, &ctrl); ++ if (status) ++ return status == -ENXIO ? -EPROBE_DEFER : status; ++ ++ drvdata = kzalloc(sizeof(struct vhf_drvdata), GFP_KERNEL); ++ if (!drvdata) ++ return -ENOMEM; ++ ++ hid = vhf_create_hid_device(pdev); ++ if (IS_ERR(hid)) { ++ status = PTR_ERR(hid); ++ goto err_probe_hid; ++ } ++ ++ status = hid_add_device(hid); ++ if (status) ++ goto err_add_hid; ++ ++ drvdata->dev = pdev; ++ drvdata->ctrl = ctrl; ++ drvdata->hid = hid; ++ ++ drvdata->notif.base.priority = 1; ++ drvdata->notif.base.fn = vhf_event_handler; ++ drvdata->notif.event.reg = SSAM_EVENT_REGISTRY_SAM; ++ drvdata->notif.event.id.target_category = SSAM_SSH_TC_KBD; ++ drvdata->notif.event.id.instance = 0; ++ drvdata->notif.event.flags = 0; ++ ++ platform_set_drvdata(pdev, drvdata); ++ ++ status = ssam_notifier_register(ctrl, &drvdata->notif); ++ if (status) ++ goto err_add_hid; ++ ++ return 0; ++ ++err_add_hid: ++ hid_destroy_device(hid); ++ platform_set_drvdata(pdev, NULL); ++err_probe_hid: ++ kfree(drvdata); ++ return status; ++} ++ ++static int surface_sam_vhf_remove(struct platform_device *pdev) ++{ ++ struct vhf_drvdata *drvdata = platform_get_drvdata(pdev); ++ ++ ssam_notifier_unregister(drvdata->ctrl, &drvdata->notif); ++ hid_destroy_device(drvdata->hid); ++ kfree(drvdata); ++ ++ platform_set_drvdata(pdev, NULL); ++ return 0; ++} ++ ++ ++static const struct acpi_device_id surface_sam_vhf_match[] = { ++ { "MSHW0096" }, ++ { }, ++}; ++MODULE_DEVICE_TABLE(acpi, surface_sam_vhf_match); ++ ++static struct platform_driver surface_sam_vhf = { ++ .probe = surface_sam_vhf_probe, ++ .remove = surface_sam_vhf_remove, ++ .driver = { ++ .name = "surface_sam_vhf", ++ .acpi_match_table = surface_sam_vhf_match, ++ .probe_type = PROBE_PREFER_ASYNCHRONOUS, ++ }, ++}; ++module_platform_driver(surface_sam_vhf); ++ ++MODULE_AUTHOR("Maximilian Luz "); ++MODULE_DESCRIPTION("Virtual HID Framework Driver for 5th Generation Surface Devices"); ++MODULE_LICENSE("GPL"); +diff --git a/drivers/misc/surface_sam/controller.c b/drivers/misc/surface_sam/controller.c +new file mode 100644 +index 0000000000000..9672518fe0a80 +--- /dev/null ++++ b/drivers/misc/surface_sam/controller.c +@@ -0,0 +1,1475 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++ +#include +#include +#include -+#include -+#include +#include +#include -+#include -+#include -+#include +#include -+#include -+#include +#include +#include -+#include -+#include ++#include ++#include +#include ++#include +#include -+#include ++#include ++#include +#include + -+#include "surface_sam_ssh.h" ++#include + -+#define CREATE_TRACE_POINTS -+#include "surface_sam_ssh_trace.h" ++#include "controller.h" ++#include "ssh_msgb.h" ++#include "ssh_protocol.h" ++#include "ssh_request_layer.h" + -+ -+/* -- Error injection helpers. ---------------------------------------------- */ -+ -+#if 0 // not supported on 4.19 -+#define noinline_if_inject noinline -+#else /* CONFIG_SURFACE_SAM_SSH_ERROR_INJECTION */ -+#define noinline_if_inject inline -+#endif /* CONFIG_SURFACE_SAM_SSH_ERROR_INJECTION */ -+ -+ -+/* -- SSH protocol utility functions and definitions. ----------------------- */ -+ -+/* -+ * The number of reserved event IDs, used for registering an SSH event -+ * handler. Valid event IDs are numbers below or equal to this value, with -+ * exception of zero, which is not an event ID. Thus, this is also the -+ * absolute maximum number of event handlers that can be registered. -+ */ -+#define SSH_NUM_EVENTS 34 -+ -+/* -+ * The number of communication channels used in the protocol. -+ */ -+#define SSH_NUM_CHANNELS 2 -+ -+ -+static inline u16 ssh_crc(const u8 *buf, size_t len) -+{ -+ return crc_ccitt_false(0xffff, buf, len); -+} -+ -+static inline u16 ssh_rqid_next_valid(u16 rqid) -+{ -+ return rqid > 0 ? rqid + 1u : rqid + SSH_NUM_EVENTS + 1u; -+} -+ -+static inline u16 ssh_rqid_to_event(u16 rqid) -+{ -+ return rqid - 1u; -+} -+ -+static inline bool ssh_rqid_is_event(u16 rqid) -+{ -+ return ssh_rqid_to_event(rqid) < SSH_NUM_EVENTS; -+} -+ -+static inline int ssh_tc_to_rqid(u8 tc) -+{ -+ return tc; -+} -+ -+static inline u8 ssh_channel_to_index(u8 channel) -+{ -+ return channel - 1u; -+} -+ -+static inline bool ssh_channel_is_valid(u8 channel) -+{ -+ return ssh_channel_to_index(channel) < SSH_NUM_CHANNELS; -+} ++#include "ssam_trace.h" + + +/* -- Safe counters. -------------------------------------------------------- */ + -+struct ssh_seq_counter { -+ u8 value; -+}; -+ -+struct ssh_rqid_counter { -+ u16 value; -+}; -+ +static inline void ssh_seq_reset(struct ssh_seq_counter *c) +{ + WRITE_ONCE(c->value, 0); @@ -5903,3285 +6491,6 @@ index 0000000000000..3f39f383e3651 +} + + -+/* -- Builder functions for SAM-over-SSH messages. -------------------------- */ -+ -+struct msgbuf { -+ u8 *begin; -+ u8 *end; -+ u8 *ptr; -+}; -+ -+static inline void msgb_init(struct msgbuf *msgb, u8 *ptr, size_t cap) -+{ -+ msgb->begin = ptr; -+ msgb->end = ptr + cap; -+ msgb->ptr = ptr; -+} -+ -+static inline size_t msgb_bytes_used(const struct msgbuf *msgb) -+{ -+ return msgb->ptr - msgb->begin; -+} -+ -+static inline void msgb_push_u16(struct msgbuf *msgb, u16 value) -+{ -+ if (WARN_ON(msgb->ptr + sizeof(u16) > msgb->end)) -+ return; -+ -+ put_unaligned_le16(value, msgb->ptr); -+ msgb->ptr += sizeof(u16); -+} -+ -+static inline void msgb_push_syn(struct msgbuf *msgb) -+{ -+ msgb_push_u16(msgb, SSH_MSG_SYN); -+} -+ -+static inline void msgb_push_buf(struct msgbuf *msgb, const u8 *buf, size_t len) -+{ -+ msgb->ptr = memcpy(msgb->ptr, buf, len) + len; -+} -+ -+static inline void msgb_push_crc(struct msgbuf *msgb, const u8 *buf, size_t len) -+{ -+ msgb_push_u16(msgb, ssh_crc(buf, len)); -+} -+ -+static inline void msgb_push_frame(struct msgbuf *msgb, u8 ty, u16 len, u8 seq) -+{ -+ struct ssh_frame *frame = (struct ssh_frame *)msgb->ptr; -+ const u8 *const begin = msgb->ptr; -+ -+ if (WARN_ON(msgb->ptr + sizeof(*frame) > msgb->end)) -+ return; -+ -+ frame->type = ty; -+ put_unaligned_le16(len, &frame->len); -+ frame->seq = seq; -+ -+ msgb->ptr += sizeof(*frame); -+ msgb_push_crc(msgb, begin, msgb->ptr - begin); -+} -+ -+static inline void msgb_push_ack(struct msgbuf *msgb, u8 seq) -+{ -+ // SYN -+ msgb_push_syn(msgb); -+ -+ // ACK-type frame + CRC -+ msgb_push_frame(msgb, SSH_FRAME_TYPE_ACK, 0x00, seq); -+ -+ // payload CRC (ACK-type frames do not have a payload) -+ msgb_push_crc(msgb, msgb->ptr, 0); -+} -+ -+static inline void msgb_push_nak(struct msgbuf *msgb) -+{ -+ // SYN -+ msgb_push_syn(msgb); -+ -+ // NAK-type frame + CRC -+ msgb_push_frame(msgb, SSH_FRAME_TYPE_NAK, 0x00, 0x00); -+ -+ // payload CRC (ACK-type frames do not have a payload) -+ msgb_push_crc(msgb, msgb->ptr, 0); -+} -+ -+static inline void msgb_push_cmd(struct msgbuf *msgb, u8 seq, u16 rqid, -+ const struct ssam_request *rqst) -+{ -+ struct ssh_command *cmd; -+ const u8 *cmd_begin; -+ const u8 type = SSH_FRAME_TYPE_DATA_SEQ; -+ -+ // SYN -+ msgb_push_syn(msgb); -+ -+ // command frame + crc -+ msgb_push_frame(msgb, type, sizeof(*cmd) + rqst->length, seq); -+ -+ // frame payload: command struct + payload -+ if (WARN_ON(msgb->ptr + sizeof(*cmd) > msgb->end)) -+ return; -+ -+ cmd_begin = msgb->ptr; -+ cmd = (struct ssh_command *)msgb->ptr; -+ -+ cmd->type = SSH_PLD_TYPE_CMD; -+ cmd->tc = rqst->target_category; -+ cmd->chn_out = rqst->channel; -+ cmd->chn_in = 0x00; -+ cmd->iid = rqst->instance_id; -+ put_unaligned_le16(rqid, &cmd->rqid); -+ cmd->cid = rqst->command_id; -+ -+ msgb->ptr += sizeof(*cmd); -+ -+ // command payload -+ msgb_push_buf(msgb, rqst->payload, rqst->length); -+ -+ // crc for command struct + payload -+ msgb_push_crc(msgb, cmd_begin, msgb->ptr - cmd_begin); -+} -+ -+ -+/* -- Parser functions and utilities for SAM-over-SSH messages. ------------- */ -+ -+struct sshp_buf { -+ u8 *ptr; -+ size_t len; -+ size_t cap; -+}; -+ -+ -+static inline bool sshp_validate_crc(const struct ssam_span *src, const u8 *crc) -+{ -+ u16 actual = ssh_crc(src->ptr, src->len); -+ u16 expected = get_unaligned_le16(crc); -+ -+ return actual == expected; -+} -+ -+static bool sshp_find_syn(const struct ssam_span *src, struct ssam_span *rem) -+{ -+ size_t i; -+ -+ for (i = 0; i < src->len - 1; i++) { -+ if (likely(get_unaligned_le16(src->ptr + i) == SSH_MSG_SYN)) { -+ rem->ptr = src->ptr + i; -+ rem->len = src->len - i; -+ return true; -+ } -+ } -+ -+ if (unlikely(src->ptr[src->len - 1] == (SSH_MSG_SYN & 0xff))) { -+ rem->ptr = src->ptr + src->len - 1; -+ rem->len = 1; -+ return false; -+ } else { -+ rem->ptr = src->ptr + src->len; -+ rem->len = 0; -+ return false; -+ } -+} -+ -+static bool sshp_starts_with_syn(const struct ssam_span *src) -+{ -+ return src->len >= 2 && get_unaligned_le16(src->ptr) == SSH_MSG_SYN; -+} -+ -+static int sshp_parse_frame(const struct device *dev, -+ const struct ssam_span *source, -+ struct ssh_frame **frame, -+ struct ssam_span *payload, -+ size_t maxlen) -+{ -+ struct ssam_span sf; -+ struct ssam_span sp; -+ -+ // initialize output -+ *frame = NULL; -+ payload->ptr = NULL; -+ payload->len = 0; -+ -+ if (!sshp_starts_with_syn(source)) { -+ dev_warn(dev, "rx: parser: invalid start of frame\n"); -+ return -ENOMSG; -+ } -+ -+ // check for minumum packet length -+ if (unlikely(source->len < SSH_MESSAGE_LENGTH(0))) { -+ dev_dbg(dev, "rx: parser: not enough data for frame\n"); -+ return 0; -+ } -+ -+ // pin down frame -+ sf.ptr = source->ptr + sizeof(u16); -+ sf.len = sizeof(struct ssh_frame); -+ -+ // validate frame CRC -+ if (unlikely(!sshp_validate_crc(&sf, sf.ptr + sf.len))) { -+ dev_warn(dev, "rx: parser: invalid frame CRC\n"); -+ return -EBADMSG; -+ } -+ -+ // ensure packet does not exceed maximum length -+ if (unlikely(((struct ssh_frame *)sf.ptr)->len > maxlen)) { -+ dev_warn(dev, "rx: parser: frame too large: %u bytes\n", -+ ((struct ssh_frame *)sf.ptr)->len); -+ return -EMSGSIZE; -+ } -+ -+ // pin down payload -+ sp.ptr = sf.ptr + sf.len + sizeof(u16); -+ sp.len = get_unaligned_le16(&((struct ssh_frame *)sf.ptr)->len); -+ -+ // check for frame + payload length -+ if (source->len < SSH_MESSAGE_LENGTH(sp.len)) { -+ dev_dbg(dev, "rx: parser: not enough data for payload\n"); -+ return 0; -+ } -+ -+ // validate payload crc -+ if (unlikely(!sshp_validate_crc(&sp, sp.ptr + sp.len))) { -+ dev_warn(dev, "rx: parser: invalid payload CRC\n"); -+ return -EBADMSG; -+ } -+ -+ *frame = (struct ssh_frame *)sf.ptr; -+ *payload = sp; -+ -+ dev_dbg(dev, "rx: parser: valid frame found (type: 0x%02x, len: %u)\n", -+ (*frame)->type, (*frame)->len); -+ -+ return 0; -+} -+ -+static int sshp_parse_command(const struct device *dev, -+ const struct ssam_span *source, -+ struct ssh_command **command, -+ struct ssam_span *command_data) -+{ -+ // check for minimum length -+ if (unlikely(source->len < sizeof(struct ssh_command))) { -+ *command = NULL; -+ command_data->ptr = NULL; -+ command_data->len = 0; -+ -+ dev_err(dev, "rx: parser: command payload is too short\n"); -+ return -ENOMSG; -+ } -+ -+ *command = (struct ssh_command *)source->ptr; -+ command_data->ptr = source->ptr + sizeof(struct ssh_command); -+ command_data->len = source->len - sizeof(struct ssh_command); -+ -+ dev_dbg(dev, "rx: parser: valid command found (tc: 0x%02x," -+ " cid: 0x%02x)\n", (*command)->tc, (*command)->cid); -+ -+ return 0; -+} -+ -+ -+static inline void sshp_buf_init(struct sshp_buf *buf, u8 *ptr, size_t cap) -+{ -+ buf->ptr = ptr; -+ buf->len = 0; -+ buf->cap = cap; -+} -+ -+static inline int sshp_buf_alloc(struct sshp_buf *buf, size_t cap, gfp_t flags) -+{ -+ u8 *ptr; -+ -+ ptr = kzalloc(cap, flags); -+ if (!ptr) -+ return -ENOMEM; -+ -+ sshp_buf_init(buf, ptr, cap); -+ return 0; -+ -+} -+ -+static inline void sshp_buf_free(struct sshp_buf *buf) -+{ -+ kfree(buf->ptr); -+ buf->ptr = NULL; -+ buf->len = 0; -+ buf->cap = 0; -+} -+ -+static inline void sshp_buf_drop(struct sshp_buf *buf, size_t n) -+{ -+ memmove(buf->ptr, buf->ptr + n, buf->len - n); -+ buf->len -= n; -+} -+ -+static inline size_t sshp_buf_read_from_fifo(struct sshp_buf *buf, -+ struct kfifo *fifo) -+{ -+ size_t n; -+ -+ n = kfifo_out(fifo, buf->ptr + buf->len, buf->cap - buf->len); -+ buf->len += n; -+ -+ return n; -+} -+ -+static inline void sshp_buf_span_from(struct sshp_buf *buf, size_t offset, -+ struct ssam_span *span) -+{ -+ span->ptr = buf->ptr + offset; -+ span->len = buf->len - offset; -+} -+ -+ -+/* -- Packet transport layer (ptl). ----------------------------------------- */ -+/* -+ * To simplify reasoning about the code below, we define a few concepts. The -+ * system below is similar to a state-machine for packets, however, there are -+ * too many states to explicitly write them down. To (somewhat) manage the -+ * states and packets we rely on flags, reference counting, and some simple -+ * concepts. State transitions are triggered by actions. -+ * -+ * >> Actions << -+ * -+ * - submit -+ * - transmission start (process next item in queue) -+ * - transmission finished (guaranteed to never be parallel to transmission -+ * start) -+ * - ACK received -+ * - NAK received (this is equivalent to issuing re-submit for all pending -+ * packets) -+ * - timeout (this is equivalent to re-issuing a submit or canceling) -+ * - cancel (non-pending and pending) -+ * -+ * >> Data Structures, Packet Ownership, General Overview << -+ * -+ * The code below employs two main data structures: The packet queue, containing -+ * all packets scheduled for transmission, and the set of pending packets, -+ * containing all packets awaiting an ACK. -+ * -+ * Shared ownership of a packet is controlled via reference counting. Inside the -+ * transmission system are a total of five packet owners: -+ * -+ * - the packet queue, -+ * - the pending set, -+ * - the transmitter thread, -+ * - the receiver thread (via ACKing), and -+ * - the timeout work item. -+ * -+ * Normal operation is as follows: The initial reference of the packet is -+ * obtained by submitting the packet and queueing it. The receiver thread -+ * takes packets from the queue. By doing this, it does not increment the -+ * refcount but takes over the reference (removing it from the queue). -+ * If the packet is sequenced (i.e. needs to be ACKed by the client), the -+ * transmitter thread sets-up the timeout and adds the packet to the pending set -+ * before starting to transmit it. As the timeout is handled by a reaper task, -+ * no additional reference for it is needed. After the transmit is done, the -+ * reference hold by the transmitter thread is dropped. If the packet is -+ * unsequenced (i.e. does not need an ACK), the packet is completed by the -+ * transmitter thread before dropping that reference. -+ * -+ * On receial of an ACK, the receiver thread removes and obtains the refernce to -+ * the packet from the pending set. On succes, the receiver thread will then -+ * complete the packet and drop its reference. -+ * -+ * On error, the completion callback is immediately run by on thread on which -+ * the error was detected. -+ * -+ * To ensure that a packet eventually leaves the system it is marked as "locked" -+ * directly before it is going to be completed or when it is canceled. Marking a -+ * packet as "locked" has the effect that passing and creating new references -+ * of the packet will be blocked. This means that the packet cannot be added -+ * to the queue, the pending set, and the timeout, or be picked up by the -+ * transmitter thread or receiver thread. To remove a packet from the system it -+ * has to be marked as locked and subsequently all references from the data -+ * structures (queue, pending) have to be removed. References held by threads -+ * will eventually be dropped automatically as their execution progresses. -+ * -+ * Note that the packet completion callback is, in case of success and for a -+ * sequenced packet, guaranteed to run on the receiver thread, thus providing a -+ * way to reliably identify responses to the packet. The packet completion -+ * callback is only run once and it does not indicate that the packet has fully -+ * left the system. In case of re-submission (and with somewhat unlikely -+ * timing), it may be possible that the packet is being re-transmitted while the -+ * completion callback runs. Completion will occur both on success and internal -+ * error, as well as when the packet is canceled. -+ * -+ * >> Flags << -+ * -+ * Flags are used to indicate the state and progression of a packet. Some flags -+ * have stricter guarantees than other: -+ * -+ * - locked -+ * Indicates if the packet is locked. If the packet is locked, passing and/or -+ * creating additional references to the packet is forbidden. The packet thus -+ * may not be queued, dequeued, or removed or added to the pending set. Note -+ * that the packet state flags may still change (e.g. it may be marked as -+ * ACKed, transmitted, ...). -+ * -+ * - completed -+ * Indicates if the packet completion has been run or is about to be run. This -+ * flag is used to ensure that the packet completion callback is only run -+ * once. -+ * -+ * - queued -+ * Indicates if a packet is present in the submission queue or not. This flag -+ * must only be modified with the queue lock held, and must be coherent -+ * presence of the packet in the queue. -+ * -+ * - pending -+ * Indicates if a packet is present in the set of pending packets or not. -+ * This flag must only be modified with the pending lock held, and must be -+ * coherent presence of the packet in the pending set. -+ * -+ * - transmitting -+ * Indicates if the packet is currently transmitting. In case of -+ * re-transmissions, it is only safe to wait on the "transmitted" completion -+ * after this flag has been set. The completion will be set both in success -+ * and error case. -+ * -+ * - transmitted -+ * Indicates if the packet has been transmitted. This flag is not cleared by -+ * the system, thus it indicates the first transmission only. -+ * -+ * - acked -+ * Indicates if the packet has been acknowledged by the client. There are no -+ * other guarantees given. For example, the packet may still be canceled -+ * and/or the completion may be triggered an error even though this bit is -+ * set. Rely on the status provided by completion instead. -+ * -+ * - canceled -+ * Indicates if the packet has been canceled from the outside. There are no -+ * other guarantees given. Specifically, the packet may be completed by -+ * another part of the system before the cancellation attempts to complete it. -+ * -+ * >> General Notes << -+ * -+ * To avoid deadlocks, if both queue and pending locks are required, the pending -+ * lock must be acquired before the queue lock. -+ */ -+ -+/** -+ * Maximum number transmission attempts per sequenced packet in case of -+ * time-outs. Must be smaller than 16. -+ */ -+#define SSH_PTL_MAX_PACKET_TRIES 3 -+ -+/** -+ * Timeout as ktime_t delta for ACKs. If we have not received an ACK in this -+ * time-frame after starting transmission, the packet will be re-submitted. -+ */ -+#define SSH_PTL_PACKET_TIMEOUT ms_to_ktime(1000) -+ -+/** -+ * Maximum time resolution for timeouts. Currently set to max(2 jiffies, 50ms). -+ * Should be larger than one jiffy to avoid direct re-scheduling of reaper -+ * work_struct. -+ */ -+#define SSH_PTL_PACKET_TIMEOUT_RESOLUTION ms_to_ktime(max(2000 / HZ, 50)) -+ -+/** -+ * Maximum number of sequenced packets concurrently waiting for an ACK. -+ * Packets marked as blocking will not be transmitted while this limit is -+ * reached. -+ */ -+#define SSH_PTL_MAX_PENDING 1 -+ -+#define SSH_PTL_RX_BUF_LEN 4096 -+ -+#define SSH_PTL_RX_FIFO_LEN 4096 -+ -+ -+enum ssh_ptl_state_flags { -+ SSH_PTL_SF_SHUTDOWN_BIT, -+}; -+ -+struct ssh_ptl_ops { -+ void (*data_received)(struct ssh_ptl *p, const struct ssam_span *data); -+}; -+ -+struct ssh_ptl { -+ struct serdev_device *serdev; -+ unsigned long state; -+ -+ struct { -+ spinlock_t lock; -+ struct list_head head; -+ } queue; -+ -+ struct { -+ spinlock_t lock; -+ struct list_head head; -+ atomic_t count; -+ } pending; -+ -+ struct { -+ bool thread_signal; -+ struct task_struct *thread; -+ struct wait_queue_head thread_wq; -+ struct wait_queue_head packet_wq; -+ struct ssh_packet *packet; -+ size_t offset; -+ } tx; -+ -+ struct { -+ struct task_struct *thread; -+ struct wait_queue_head wq; -+ struct kfifo fifo; -+ struct sshp_buf buf; -+ -+ struct { -+ u16 seqs[8]; -+ u16 offset; -+ } blocked; -+ } rx; -+ -+ struct { -+ ktime_t timeout; -+ ktime_t expires; -+ struct delayed_work reaper; -+ } rtx_timeout; -+ -+ struct ssh_ptl_ops ops; -+}; -+ -+ -+#define __ssam_prcond(func, p, fmt, ...) \ -+ do { \ -+ if ((p)) \ -+ func((p), fmt, ##__VA_ARGS__); \ -+ } while (0); -+ -+#define ptl_dbg(p, fmt, ...) dev_dbg(&(p)->serdev->dev, fmt, ##__VA_ARGS__) -+#define ptl_info(p, fmt, ...) dev_info(&(p)->serdev->dev, fmt, ##__VA_ARGS__) -+#define ptl_warn(p, fmt, ...) dev_warn(&(p)->serdev->dev, fmt, ##__VA_ARGS__) -+#define ptl_err(p, fmt, ...) dev_err(&(p)->serdev->dev, fmt, ##__VA_ARGS__) -+#define ptl_dbg_cond(p, fmt, ...) __ssam_prcond(ptl_dbg, p, fmt, ##__VA_ARGS__) -+ -+#define to_ssh_packet(ptr, member) \ -+ container_of(ptr, struct ssh_packet, member) -+ -+#define to_ssh_ptl(ptr, member) \ -+ container_of(ptr, struct ssh_ptl, member) -+ -+ -+#if 0 // not supported on 4.19 -+ -+/** -+ * ssh_ptl_should_drop_ack_packet - error injection hook to drop ACK packets -+ * -+ * Useful to test detection and handling of automated re-transmits by the EC. -+ * Specifically of packets that the EC consideres not-ACKed but the driver -+ * already consideres ACKed (due to dropped ACK). In this case, the EC -+ * re-transmits the packet-to-be-ACKed and the driver should detect it as -+ * duplicate/already handled. Note that the driver should still send an ACK -+ * for the re-transmitted packet. -+ */ -+static noinline bool ssh_ptl_should_drop_ack_packet(void) -+{ -+ return false; -+} -+ALLOW_ERROR_INJECTION(ssh_ptl_should_drop_ack_packet, TRUE); -+ -+/** -+ * ssh_ptl_should_drop_nak_packet - error injection hook to drop NAK packets -+ * -+ * Useful to test/force automated (timeout-based) re-transmit by the EC. -+ * Specifically, packets that have not reached the driver completely/with valid -+ * checksums. Only useful in combination with receival of (injected) bad data. -+ */ -+static noinline bool ssh_ptl_should_drop_nak_packet(void) -+{ -+ return false; -+} -+ALLOW_ERROR_INJECTION(ssh_ptl_should_drop_nak_packet, TRUE); -+ -+/** -+ * ssh_ptl_should_drop_dsq_packet - error injection hook to drop sequenced data -+ * packet -+ * -+ * Useful to test re-transmit timeout of the driver. If the data packet has not -+ * been ACKed after a certain time, the driver should re-transmit the packet up -+ * to limited number of times defined in SSH_PTL_MAX_PACKET_TRIES. -+ */ -+static noinline bool ssh_ptl_should_drop_dsq_packet(void) -+{ -+ return false; -+} -+ALLOW_ERROR_INJECTION(ssh_ptl_should_drop_dsq_packet, TRUE); -+ -+/** -+ * ssh_ptl_should_fail_write - error injection hook to make serdev_device_write -+ * fail -+ * -+ * Hook to simulate errors in serdev_device_write when transmitting packets. -+ */ -+static noinline int ssh_ptl_should_fail_write(void) -+{ -+ return 0; -+} -+ALLOW_ERROR_INJECTION(ssh_ptl_should_fail_write, ERRNO); -+ -+/** -+ * ssh_ptl_should_corrupt_tx_data - error injection hook to simualte invalid -+ * data being sent to the EC -+ * -+ * Hook to simulate corrupt/invalid data being sent from host (driver) to EC. -+ * Causes the packet data to be actively corrupted by overwriting it with -+ * pre-defined values, such that it becomes invalid, causing the EC to respond -+ * with a NAK packet. Useful to test handling of NAK packets received by the -+ * driver. -+ */ -+static noinline bool ssh_ptl_should_corrupt_tx_data(void) -+{ -+ return false; -+} -+ALLOW_ERROR_INJECTION(ssh_ptl_should_corrupt_tx_data, TRUE); -+ -+/** -+ * ssh_ptl_should_corrupt_rx_syn - error injection hook to simulate invalid -+ * data being sent by the EC -+ * -+ * Hook to simulate invalid SYN bytes, i.e. an invalid start of messages and -+ * test handling thereof in the driver. -+ */ -+static noinline bool ssh_ptl_should_corrupt_rx_syn(void) -+{ -+ return false; -+} -+ALLOW_ERROR_INJECTION(ssh_ptl_should_corrupt_rx_syn, TRUE); -+ -+/** -+ * ssh_ptl_should_corrupt_rx_data - error injection hook to simulate invalid -+ * data being sent by the EC -+ * -+ * Hook to simulate invalid data/checksum of the message frame and test handling -+ * thereof in the driver. -+ */ -+static noinline bool ssh_ptl_should_corrupt_rx_data(void) -+{ -+ return false; -+} -+ALLOW_ERROR_INJECTION(ssh_ptl_should_corrupt_rx_data, TRUE); -+ -+ -+static inline bool __ssh_ptl_should_drop_ack_packet(struct ssh_packet *packet) -+{ -+ if (likely(!ssh_ptl_should_drop_ack_packet())) -+ return false; -+ -+ trace_ssam_ei_tx_drop_ack_packet(packet); -+ ptl_info(packet->ptl, "packet error injection: dropping ACK packet %p\n", -+ packet); -+ -+ return true; -+} -+ -+static inline bool __ssh_ptl_should_drop_nak_packet(struct ssh_packet *packet) -+{ -+ if (likely(!ssh_ptl_should_drop_nak_packet())) -+ return false; -+ -+ trace_ssam_ei_tx_drop_nak_packet(packet); -+ ptl_info(packet->ptl, "packet error injection: dropping NAK packet %p\n", -+ packet); -+ -+ return true; -+} -+ -+static inline bool __ssh_ptl_should_drop_dsq_packet(struct ssh_packet *packet) -+{ -+ if (likely(!ssh_ptl_should_drop_dsq_packet())) -+ return false; -+ -+ trace_ssam_ei_tx_drop_dsq_packet(packet); -+ ptl_info(packet->ptl, -+ "packet error injection: dropping sequenced data packet %p\n", -+ packet); -+ -+ return true; -+} -+ -+static bool ssh_ptl_should_drop_packet(struct ssh_packet *packet) -+{ -+ // ignore packets that don't carry any data (i.e. flush) -+ if (!packet->data.ptr || !packet->data.len) -+ return false; -+ -+ switch (packet->data.ptr[SSH_MSGOFFSET_FRAME(type)]) { -+ case SSH_FRAME_TYPE_ACK: -+ return __ssh_ptl_should_drop_ack_packet(packet); -+ -+ case SSH_FRAME_TYPE_NAK: -+ return __ssh_ptl_should_drop_nak_packet(packet); -+ -+ case SSH_FRAME_TYPE_DATA_SEQ: -+ return __ssh_ptl_should_drop_dsq_packet(packet); -+ -+ default: -+ return false; -+ } -+} -+ -+static int ssh_ptl_write_buf(struct ssh_ptl *ptl, struct ssh_packet *packet, -+ const unsigned char *buf, size_t count) -+{ -+ int status; -+ -+ status = ssh_ptl_should_fail_write(); -+ if (unlikely(status)) { -+ trace_ssam_ei_tx_fail_write(packet, status); -+ ptl_info(packet->ptl, -+ "packet error injection: simulating transmit error %d, packet %p\n", -+ status, packet); -+ -+ return status; -+ } -+ -+ return serdev_device_write_buf(ptl->serdev, buf, count); -+} -+ -+static void ssh_ptl_tx_inject_invalid_data(struct ssh_packet *packet) -+{ -+ // ignore packets that don't carry any data (i.e. flush) -+ if (!packet->data.ptr || !packet->data.len) -+ return; -+ -+ // only allow sequenced data packets to be modified -+ if (packet->data.ptr[SSH_MSGOFFSET_FRAME(type)] != SSH_FRAME_TYPE_DATA_SEQ) -+ return; -+ -+ if (likely(!ssh_ptl_should_corrupt_tx_data())) -+ return; -+ -+ trace_ssam_ei_tx_corrupt_data(packet); -+ ptl_info(packet->ptl, -+ "packet error injection: simulating invalid transmit data on packet %p\n", -+ packet); -+ -+ /* -+ * NB: The value 0xb3 has been chosen more or less randomly so that it -+ * doesn't have any (major) overlap with the SYN bytes (aa 55) and is -+ * non-trivial (i.e. non-zero, non-0xff). -+ */ -+ memset(packet->data.ptr, 0xb3, packet->data.len); -+} -+ -+static void ssh_ptl_rx_inject_invalid_syn(struct ssh_ptl *ptl, -+ struct ssam_span *data) -+{ -+ struct ssam_span frame; -+ -+ // check if there actually is something to corrupt -+ if (!sshp_find_syn(data, &frame)) -+ return; -+ -+ if (likely(!ssh_ptl_should_corrupt_rx_syn())) -+ return; -+ -+ trace_ssam_ei_rx_corrupt_syn("data_length", data->len); -+ -+ data->ptr[1] = 0xb3; // set second byte of SYN to "random" value -+} -+ -+static void ssh_ptl_rx_inject_invalid_data(struct ssh_ptl *ptl, -+ struct ssam_span *frame) -+{ -+ size_t payload_len, message_len; -+ struct ssh_frame *sshf; -+ -+ // ignore incomplete messages, will get handled once it's complete -+ if (frame->len < SSH_MESSAGE_LENGTH(0)) -+ return; -+ -+ // ignore incomplete messages, part 2 -+ payload_len = get_unaligned_le16(&frame->ptr[SSH_MSGOFFSET_FRAME(len)]); -+ message_len = SSH_MESSAGE_LENGTH(payload_len); -+ if (frame->len < message_len) -+ return; -+ -+ if (likely(!ssh_ptl_should_corrupt_rx_data())) -+ return; -+ -+ sshf = (struct ssh_frame *)&frame->ptr[SSH_MSGOFFSET_FRAME(type)]; -+ trace_ssam_ei_rx_corrupt_data(sshf); -+ -+ /* -+ * Flip bits in first byte of payload checksum. This is basically -+ * equivalent to a payload/frame data error without us having to worry -+ * about (the, arguably pretty small, probability of) accidental -+ * checksum collisions. -+ */ -+ frame->ptr[frame->len - 2] = ~frame->ptr[frame->len - 2]; -+} -+ -+#else /* CONFIG_SURFACE_SAM_SSH_ERROR_INJECTION */ -+ -+static inline bool ssh_ptl_should_drop_packet(struct ssh_packet *packet) -+{ -+ return false; -+} -+ -+static inline int ssh_ptl_write_buf(struct ssh_ptl *ptl, -+ struct ssh_packet *packet, -+ const unsigned char *buf, -+ size_t count) -+{ -+ return serdev_device_write_buf(ptl->serdev, buf, count); -+} -+ -+static inline void ssh_ptl_tx_inject_invalid_data(struct ssh_packet *packet) -+{ -+} -+ -+static inline void ssh_ptl_rx_inject_invalid_syn(struct ssh_ptl *ptl, -+ struct ssam_span *data) -+{ -+} -+ -+static inline void ssh_ptl_rx_inject_invalid_data(struct ssh_ptl *ptl, -+ struct ssam_span *frame) -+{ -+} -+ -+#endif /* CONFIG_SURFACE_SAM_SSH_ERROR_INJECTION */ -+ -+ -+static void __ssh_ptl_packet_release(struct kref *kref) -+{ -+ struct ssh_packet *p = to_ssh_packet(kref, refcnt); -+ -+ trace_ssam_packet_release(p); -+ -+ ptl_dbg_cond(p->ptl, "ptl: releasing packet %p\n", p); -+ p->ops->release(p); -+} -+ -+void ssh_packet_get(struct ssh_packet *packet) -+{ -+ kref_get(&packet->refcnt); -+} -+EXPORT_SYMBOL_GPL(ssh_packet_get); -+ -+void ssh_packet_put(struct ssh_packet *packet) -+{ -+ kref_put(&packet->refcnt, __ssh_ptl_packet_release); -+} -+EXPORT_SYMBOL_GPL(ssh_packet_put); -+ -+static inline u8 ssh_packet_get_seq(struct ssh_packet *packet) -+{ -+ return packet->data.ptr[SSH_MSGOFFSET_FRAME(seq)]; -+} -+ -+ -+struct ssh_packet_args { -+ unsigned long type; -+ u8 priority; -+ const struct ssh_packet_ops *ops; -+}; -+ -+static void ssh_packet_init(struct ssh_packet *packet, -+ const struct ssh_packet_args *args) -+{ -+ kref_init(&packet->refcnt); -+ -+ packet->ptl = NULL; -+ INIT_LIST_HEAD(&packet->queue_node); -+ INIT_LIST_HEAD(&packet->pending_node); -+ -+ packet->state = args->type & SSH_PACKET_FLAGS_TY_MASK; -+ packet->priority = args->priority; -+ packet->timestamp = KTIME_MAX; -+ -+ packet->data.ptr = NULL; -+ packet->data.len = 0; -+ -+ packet->ops = args->ops; -+} -+ -+ -+static struct kmem_cache *ssh_ctrl_packet_cache; -+ -+static int __init ssh_ctrl_packet_cache_init(void) -+{ -+ const unsigned int size = sizeof(struct ssh_packet) + SSH_MSG_LEN_CTRL; -+ const unsigned int align = __alignof__(struct ssh_packet); -+ struct kmem_cache *cache; -+ -+ cache = kmem_cache_create("ssam_ctrl_packet", size, align, 0, NULL); -+ if (!cache) -+ return -ENOMEM; -+ -+ ssh_ctrl_packet_cache = cache; -+ return 0; -+} -+ -+static void __exit ssh_ctrl_packet_cache_destroy(void) -+{ -+ kmem_cache_destroy(ssh_ctrl_packet_cache); -+ ssh_ctrl_packet_cache = NULL; -+} -+ -+static int ssh_ctrl_packet_alloc(struct ssh_packet **packet, -+ struct ssam_span *buffer, gfp_t flags) -+{ -+ *packet = kmem_cache_alloc(ssh_ctrl_packet_cache, flags); -+ if (!*packet) -+ return -ENOMEM; -+ -+ buffer->ptr = (u8 *)(*packet + 1); -+ buffer->len = SSH_MSG_LEN_CTRL; -+ -+ trace_ssam_ctrl_packet_alloc(*packet, buffer->len); -+ return 0; -+} -+ -+static void ssh_ctrl_packet_free(struct ssh_packet *p) -+{ -+ trace_ssam_ctrl_packet_free(p); -+ kmem_cache_free(ssh_ctrl_packet_cache, p); -+} -+ -+static const struct ssh_packet_ops ssh_ptl_ctrl_packet_ops = { -+ .complete = NULL, -+ .release = ssh_ctrl_packet_free, -+}; -+ -+ -+static void ssh_ptl_timeout_reaper_mod(struct ssh_ptl *ptl, ktime_t now, -+ ktime_t expires) -+{ -+ unsigned long delta = msecs_to_jiffies(ktime_ms_delta(expires, now)); -+ ktime_t aexp = ktime_add(expires, SSH_PTL_PACKET_TIMEOUT_RESOLUTION); -+ ktime_t old; -+ -+ // re-adjust / schedule reaper if it is above resolution delta -+ old = READ_ONCE(ptl->rtx_timeout.expires); -+ while (ktime_before(aexp, old)) -+ old = cmpxchg64(&ptl->rtx_timeout.expires, old, expires); -+ -+ // if we updated the reaper expiration, modify work timeout -+ if (old == expires) -+ mod_delayed_work(system_wq, &ptl->rtx_timeout.reaper, delta); -+} -+ -+static void ssh_ptl_timeout_start(struct ssh_packet *packet) -+{ -+ struct ssh_ptl *ptl = packet->ptl; -+ ktime_t timestamp = ktime_get_coarse_boottime(); -+ ktime_t timeout = ptl->rtx_timeout.timeout; -+ -+ if (test_bit(SSH_PACKET_SF_LOCKED_BIT, &packet->state)) -+ return; -+ -+ WRITE_ONCE(packet->timestamp, timestamp); -+ smp_mb__after_atomic(); -+ -+ ssh_ptl_timeout_reaper_mod(packet->ptl, timestamp, timestamp + timeout); -+} -+ -+ -+static struct list_head *__ssh_ptl_queue_find_entrypoint(struct ssh_packet *p) -+{ -+ struct list_head *head; -+ u8 priority = READ_ONCE(p->priority); -+ -+ /* -+ * We generally assume that there are less control (ACK/NAK) packets and -+ * re-submitted data packets as there are normal data packets (at least -+ * in situations in which many packets are queued; if there aren't many -+ * packets queued the decision on how to iterate should be basically -+ * irrellevant; the number of control/data packets is more or less -+ * limited via the maximum number of pending packets). Thus, when -+ * inserting a control or re-submitted data packet, (determined by their -+ * priority), we search from front to back. Normal data packets are, -+ * usually queued directly at the tail of the queue, so for those search -+ * from back to front. -+ */ -+ -+ if (priority > SSH_PACKET_PRIORITY_DATA) { -+ list_for_each(head, &p->ptl->queue.head) { -+ p = list_entry(head, struct ssh_packet, queue_node); -+ -+ if (READ_ONCE(p->priority) < priority) -+ break; -+ } -+ } else { -+ list_for_each_prev(head, &p->ptl->queue.head) { -+ p = list_entry(head, struct ssh_packet, queue_node); -+ -+ if (READ_ONCE(p->priority) >= priority) { -+ head = head->next; -+ break; -+ } -+ } -+ } -+ -+ -+ return head; -+} -+ -+static int ssh_ptl_queue_push(struct ssh_packet *packet) -+{ -+ struct ssh_ptl *ptl = packet->ptl; -+ struct list_head *head; -+ -+ spin_lock(&ptl->queue.lock); -+ -+ if (test_bit(SSH_PTL_SF_SHUTDOWN_BIT, &ptl->state)) { -+ spin_unlock(&ptl->queue.lock); -+ return -ESHUTDOWN; -+ } -+ -+ // avoid further transitions when cancelling/completing -+ if (test_bit(SSH_PACKET_SF_LOCKED_BIT, &packet->state)) { -+ spin_unlock(&ptl->queue.lock); -+ return -EINVAL; -+ } -+ -+ // if this packet has already been queued, do not add it -+ if (test_and_set_bit(SSH_PACKET_SF_QUEUED_BIT, &packet->state)) { -+ spin_unlock(&ptl->queue.lock); -+ return -EALREADY; -+ } -+ -+ head = __ssh_ptl_queue_find_entrypoint(packet); -+ -+ ssh_packet_get(packet); -+ list_add_tail(&packet->queue_node, &ptl->queue.head); -+ -+ spin_unlock(&ptl->queue.lock); -+ return 0; -+} -+ -+static void ssh_ptl_queue_remove(struct ssh_packet *packet) -+{ -+ struct ssh_ptl *ptl = packet->ptl; -+ bool remove; -+ -+ spin_lock(&ptl->queue.lock); -+ -+ remove = test_and_clear_bit(SSH_PACKET_SF_QUEUED_BIT, &packet->state); -+ if (remove) -+ list_del(&packet->queue_node); -+ -+ spin_unlock(&ptl->queue.lock); -+ -+ if (remove) -+ ssh_packet_put(packet); -+} -+ -+ -+static void ssh_ptl_pending_push(struct ssh_packet *packet) -+{ -+ struct ssh_ptl *ptl = packet->ptl; -+ -+ spin_lock(&ptl->pending.lock); -+ -+ // if we are cancelling/completing this packet, do not add it -+ if (test_bit(SSH_PACKET_SF_LOCKED_BIT, &packet->state)) { -+ spin_unlock(&ptl->pending.lock); -+ return; -+ } -+ -+ // in case it is already pending (e.g. re-submission), do not add it -+ if (test_and_set_bit(SSH_PACKET_SF_PENDING_BIT, &packet->state)) { -+ spin_unlock(&ptl->pending.lock); -+ return; -+ } -+ -+ atomic_inc(&ptl->pending.count); -+ ssh_packet_get(packet); -+ list_add_tail(&packet->pending_node, &ptl->pending.head); -+ -+ spin_unlock(&ptl->pending.lock); -+} -+ -+static void ssh_ptl_pending_remove(struct ssh_packet *packet) -+{ -+ struct ssh_ptl *ptl = packet->ptl; -+ bool remove; -+ -+ spin_lock(&ptl->pending.lock); -+ -+ remove = test_and_clear_bit(SSH_PACKET_SF_PENDING_BIT, &packet->state); -+ if (remove) { -+ list_del(&packet->pending_node); -+ atomic_dec(&ptl->pending.count); -+ } -+ -+ spin_unlock(&ptl->pending.lock); -+ -+ if (remove) -+ ssh_packet_put(packet); -+} -+ -+ -+static void __ssh_ptl_complete(struct ssh_packet *p, int status) -+{ -+ struct ssh_ptl *ptl = READ_ONCE(p->ptl); -+ -+ trace_ssam_packet_complete(p, status); -+ -+ ptl_dbg_cond(ptl, "ptl: completing packet %p\n", p); -+ if (status && status != -ECANCELED) -+ ptl_dbg_cond(ptl, "ptl: packet error: %d\n", status); -+ -+ if (p->ops->complete) -+ p->ops->complete(p, status); -+} -+ -+static void ssh_ptl_remove_and_complete(struct ssh_packet *p, int status) -+{ -+ /* -+ * A call to this function should in general be preceeded by -+ * set_bit(SSH_PACKET_SF_LOCKED_BIT, &p->flags) to avoid re-adding the -+ * packet to the structures it's going to be removed from. -+ * -+ * The set_bit call does not need explicit memory barriers as the -+ * implicit barrier of the test_and_set_bit call below ensure that the -+ * flag is visible before we actually attempt to remove the packet. -+ */ -+ -+ if (test_and_set_bit(SSH_PACKET_SF_COMPLETED_BIT, &p->state)) -+ return; -+ -+ ssh_ptl_queue_remove(p); -+ ssh_ptl_pending_remove(p); -+ -+ __ssh_ptl_complete(p, status); -+} -+ -+ -+static bool ssh_ptl_tx_can_process(struct ssh_packet *packet) -+{ -+ struct ssh_ptl *ptl = packet->ptl; -+ -+ if (test_bit(SSH_PACKET_TY_FLUSH_BIT, &packet->state)) -+ return !atomic_read(&ptl->pending.count); -+ -+ // we can alwas process non-blocking packets -+ if (!test_bit(SSH_PACKET_TY_BLOCKING_BIT, &packet->state)) -+ return true; -+ -+ // if we are already waiting for this packet, send it again -+ if (test_bit(SSH_PACKET_SF_PENDING_BIT, &packet->state)) -+ return true; -+ -+ // otherwise: check if we have the capacity to send -+ return atomic_read(&ptl->pending.count) < SSH_PTL_MAX_PENDING; -+} -+ -+static struct ssh_packet *ssh_ptl_tx_pop(struct ssh_ptl *ptl) -+{ -+ struct ssh_packet *packet = ERR_PTR(-ENOENT); -+ struct ssh_packet *p, *n; -+ -+ spin_lock(&ptl->queue.lock); -+ list_for_each_entry_safe(p, n, &ptl->queue.head, queue_node) { -+ /* -+ * If we are cancelling or completing this packet, ignore it. -+ * It's going to be removed from this queue shortly. -+ */ -+ if (test_bit(SSH_PACKET_SF_LOCKED_BIT, &p->state)) -+ continue; -+ -+ /* -+ * Packets should be ordered non-blocking/to-be-resent first. -+ * If we cannot process this packet, assume that we can't -+ * process any following packet either and abort. -+ */ -+ if (!ssh_ptl_tx_can_process(p)) { -+ packet = ERR_PTR(-EBUSY); -+ break; -+ } -+ -+ /* -+ * We are allowed to change the state now. Remove it from the -+ * queue and mark it as being transmitted. Note that we cannot -+ * add it to the set of pending packets yet, as queue locks must -+ * always be acquired before packet locks (otherwise we might -+ * run into a deadlock). -+ */ -+ -+ list_del(&p->queue_node); -+ -+ /* -+ * Ensure that the "queued" bit gets cleared after setting the -+ * "transmitting" bit to guaranteee non-zero flags. -+ */ -+ set_bit(SSH_PACKET_SF_TRANSMITTING_BIT, &p->state); -+ smp_mb__before_atomic(); -+ clear_bit(SSH_PACKET_SF_QUEUED_BIT, &p->state); -+ -+ packet = p; -+ break; -+ } -+ spin_unlock(&ptl->queue.lock); -+ -+ return packet; -+} -+ -+static struct ssh_packet *ssh_ptl_tx_next(struct ssh_ptl *ptl) -+{ -+ struct ssh_packet *p; -+ -+ p = ssh_ptl_tx_pop(ptl); -+ if (IS_ERR(p)) -+ return p; -+ -+ if (test_bit(SSH_PACKET_TY_SEQUENCED_BIT, &p->state)) { -+ ptl_dbg(ptl, "ptl: transmitting sequenced packet %p\n", p); -+ ssh_ptl_pending_push(p); -+ ssh_ptl_timeout_start(p); -+ } else { -+ ptl_dbg(ptl, "ptl: transmitting non-sequenced packet %p\n", p); -+ } -+ -+ /* -+ * Update number of tries. This directly influences the priority in case -+ * the packet is re-submitted (e.g. via timeout/NAK). Note that this is -+ * the only place where we update the priority in-flight. As this runs -+ * only on the tx-thread, this read-modify-write procedure is safe. -+ */ -+ WRITE_ONCE(p->priority, READ_ONCE(p->priority) + 1); -+ -+ return p; -+} -+ -+static void ssh_ptl_tx_compl_success(struct ssh_packet *packet) -+{ -+ struct ssh_ptl *ptl = packet->ptl; -+ -+ ptl_dbg(ptl, "ptl: successfully transmitted packet %p\n", packet); -+ -+ /* -+ * Transition to state to "transmitted". Ensure that the flags never get -+ * zero with barrier. -+ */ -+ set_bit(SSH_PACKET_SF_TRANSMITTED_BIT, &packet->state); -+ smp_mb__before_atomic(); -+ clear_bit(SSH_PACKET_SF_TRANSMITTING_BIT, &packet->state); -+ -+ // if the packet is unsequenced, we're done: lock and complete -+ if (!test_bit(SSH_PACKET_TY_SEQUENCED_BIT, &packet->state)) { -+ set_bit(SSH_PACKET_SF_LOCKED_BIT, &packet->state); -+ ssh_ptl_remove_and_complete(packet, 0); -+ } -+ -+ /* -+ * Notify that a packet transmission has finished. In general we're only -+ * waiting for one packet (if any), so wake_up_all should be fine. -+ */ -+ wake_up_all(&ptl->tx.packet_wq); -+} -+ -+static void ssh_ptl_tx_compl_error(struct ssh_packet *packet, int status) -+{ -+ /* -+ * Transmission failure: Lock the packet and try to complete it. Ensure -+ * that the flags never get zero with barrier. -+ */ -+ set_bit(SSH_PACKET_SF_LOCKED_BIT, &packet->state); -+ smp_mb__before_atomic(); -+ clear_bit(SSH_PACKET_SF_TRANSMITTING_BIT, &packet->state); -+ -+ ptl_err(packet->ptl, "ptl: transmission error: %d\n", status); -+ ptl_dbg(packet->ptl, "ptl: failed to transmit packet: %p\n", packet); -+ -+ ssh_ptl_remove_and_complete(packet, status); -+ -+ /* -+ * Notify that a packet transmission has finished. In general we're only -+ * waiting for one packet (if any), so wake_up_all should be fine. -+ */ -+ wake_up_all(&packet->ptl->tx.packet_wq); -+} -+ -+static void ssh_ptl_tx_threadfn_wait(struct ssh_ptl *ptl) -+{ -+ wait_event_interruptible(ptl->tx.thread_wq, -+ READ_ONCE(ptl->tx.thread_signal) || kthread_should_stop()); -+ WRITE_ONCE(ptl->tx.thread_signal, false); -+} -+ -+static int ssh_ptl_tx_threadfn(void *data) -+{ -+ struct ssh_ptl *ptl = data; -+ -+ while (!kthread_should_stop()) { -+ unsigned char *buf; -+ bool drop = false; -+ size_t len = 0; -+ int status = 0; -+ -+ // if we don't have a packet, get the next and add it to pending -+ if (IS_ERR_OR_NULL(ptl->tx.packet)) { -+ ptl->tx.packet = ssh_ptl_tx_next(ptl); -+ ptl->tx.offset = 0; -+ -+ // if no packet is available, we are done -+ if (IS_ERR(ptl->tx.packet)) { -+ ssh_ptl_tx_threadfn_wait(ptl); -+ continue; -+ } -+ } -+ -+ // error injection: drop packet to simulate transmission problem -+ if (ptl->tx.offset == 0) -+ drop = ssh_ptl_should_drop_packet(ptl->tx.packet); -+ -+ // error injection: simulate invalid packet data -+ if (ptl->tx.offset == 0 && !drop) -+ ssh_ptl_tx_inject_invalid_data(ptl->tx.packet); -+ -+ // flush-packets don't have any data -+ if (likely(ptl->tx.packet->data.ptr && !drop)) { -+ buf = ptl->tx.packet->data.ptr + ptl->tx.offset; -+ len = ptl->tx.packet->data.len - ptl->tx.offset; -+ -+ ptl_dbg(ptl, "tx: sending data (length: %zu)\n", len); -+ print_hex_dump_debug("tx: ", DUMP_PREFIX_OFFSET, 16, 1, -+ buf, len, false); -+ -+ status = ssh_ptl_write_buf(ptl, ptl->tx.packet, buf, len); -+ } -+ -+ if (status < 0) { -+ // complete packet with error -+ ssh_ptl_tx_compl_error(ptl->tx.packet, status); -+ ssh_packet_put(ptl->tx.packet); -+ ptl->tx.packet = NULL; -+ -+ } else if (status == len) { -+ // complete packet and/or mark as transmitted -+ ssh_ptl_tx_compl_success(ptl->tx.packet); -+ ssh_packet_put(ptl->tx.packet); -+ ptl->tx.packet = NULL; -+ -+ } else { // need more buffer space -+ ptl->tx.offset += status; -+ ssh_ptl_tx_threadfn_wait(ptl); -+ } -+ } -+ -+ // cancel active packet before we actually stop -+ if (!IS_ERR_OR_NULL(ptl->tx.packet)) { -+ ssh_ptl_tx_compl_error(ptl->tx.packet, -ESHUTDOWN); -+ ssh_packet_put(ptl->tx.packet); -+ ptl->tx.packet = NULL; -+ } -+ -+ return 0; -+} -+ -+static inline void ssh_ptl_tx_wakeup(struct ssh_ptl *ptl, bool force) -+{ -+ if (test_bit(SSH_PTL_SF_SHUTDOWN_BIT, &ptl->state)) -+ return; -+ -+ if (force || atomic_read(&ptl->pending.count) < SSH_PTL_MAX_PENDING) { -+ WRITE_ONCE(ptl->tx.thread_signal, true); -+ smp_mb__after_atomic(); -+ wake_up(&ptl->tx.thread_wq); -+ } -+} -+ -+static int ssh_ptl_tx_start(struct ssh_ptl *ptl) -+{ -+ ptl->tx.thread = kthread_run(ssh_ptl_tx_threadfn, ptl, "surface-sh-tx"); -+ if (IS_ERR(ptl->tx.thread)) -+ return PTR_ERR(ptl->tx.thread); -+ -+ return 0; -+} -+ -+static int ssh_ptl_tx_stop(struct ssh_ptl *ptl) -+{ -+ int status = 0; -+ -+ if (ptl->tx.thread) { -+ status = kthread_stop(ptl->tx.thread); -+ ptl->tx.thread = NULL; -+ } -+ -+ return status; -+} -+ -+ -+static struct ssh_packet *ssh_ptl_ack_pop(struct ssh_ptl *ptl, u8 seq_id) -+{ -+ struct ssh_packet *packet = ERR_PTR(-ENOENT); -+ struct ssh_packet *p, *n; -+ -+ spin_lock(&ptl->pending.lock); -+ list_for_each_entry_safe(p, n, &ptl->pending.head, pending_node) { -+ /* -+ * We generally expect packets to be in order, so first packet -+ * to be added to pending is first to be sent, is first to be -+ * ACKed. -+ */ -+ if (unlikely(ssh_packet_get_seq(p) != seq_id)) -+ continue; -+ -+ /* -+ * In case we receive an ACK while handling a transmission error -+ * completion. The packet will be removed shortly. -+ */ -+ if (unlikely(test_bit(SSH_PACKET_SF_LOCKED_BIT, &p->state))) { -+ packet = ERR_PTR(-EPERM); -+ break; -+ } -+ -+ /* -+ * Mark packet as ACKed and remove it from pending. Ensure that -+ * the flags never get zero with barrier. -+ */ -+ set_bit(SSH_PACKET_SF_ACKED_BIT, &p->state); -+ smp_mb__before_atomic(); -+ clear_bit(SSH_PACKET_SF_PENDING_BIT, &p->state); -+ -+ atomic_dec(&ptl->pending.count); -+ list_del(&p->pending_node); -+ packet = p; -+ -+ break; -+ } -+ spin_unlock(&ptl->pending.lock); -+ -+ return packet; -+} -+ -+static void ssh_ptl_wait_until_transmitted(struct ssh_packet *packet) -+{ -+ wait_event(packet->ptl->tx.packet_wq, -+ test_bit(SSH_PACKET_SF_TRANSMITTED_BIT, &packet->state) -+ || test_bit(SSH_PACKET_SF_LOCKED_BIT, &packet->state)); -+} -+ -+static void ssh_ptl_acknowledge(struct ssh_ptl *ptl, u8 seq) -+{ -+ struct ssh_packet *p; -+ int status = 0; -+ -+ p = ssh_ptl_ack_pop(ptl, seq); -+ if (IS_ERR(p)) { -+ if (PTR_ERR(p) == -ENOENT) { -+ /* -+ * The packet has not been found in the set of pending -+ * packets. -+ */ -+ ptl_warn(ptl, "ptl: received ACK for non-pending" -+ " packet\n"); -+ } else { -+ /* -+ * The packet is pending, but we are not allowed to take -+ * it because it has been locked. -+ */ -+ } -+ return; -+ } -+ -+ ptl_dbg(ptl, "ptl: received ACK for packet %p\n", p); -+ -+ /* -+ * It is possible that the packet has been transmitted, but the state -+ * has not been updated from "transmitting" to "transmitted" yet. -+ * In that case, we need to wait for this transition to occur in order -+ * to determine between success or failure. -+ */ -+ if (test_bit(SSH_PACKET_SF_TRANSMITTING_BIT, &p->state)) -+ ssh_ptl_wait_until_transmitted(p); -+ -+ /* -+ * The packet will already be locked in case of a transmission error or -+ * cancellation. Let the transmitter or cancellation issuer complete the -+ * packet. -+ */ -+ if (unlikely(test_and_set_bit(SSH_PACKET_SF_LOCKED_BIT, &p->state))) { -+ ssh_packet_put(p); -+ return; -+ } -+ -+ if (unlikely(!test_bit(SSH_PACKET_SF_TRANSMITTED_BIT, &p->state))) { -+ ptl_err(ptl, "ptl: received ACK before packet had been fully" -+ " transmitted\n"); -+ status = -EREMOTEIO; -+ } -+ -+ ssh_ptl_remove_and_complete(p, status); -+ ssh_packet_put(p); -+ -+ ssh_ptl_tx_wakeup(ptl, false); -+} -+ -+ -+static int ssh_ptl_submit(struct ssh_ptl *ptl, struct ssh_packet *p) -+{ -+ struct ssh_ptl *ptl_old; -+ int status; -+ -+ trace_ssam_packet_submit(p); -+ -+ // validate packet fields -+ if (test_bit(SSH_PACKET_TY_FLUSH_BIT, &p->state)) { -+ if (p->data.ptr || test_bit(SSH_PACKET_TY_SEQUENCED_BIT, &p->state)) -+ return -EINVAL; -+ } else if (!p->data.ptr) { -+ return -EINVAL; -+ } -+ -+ /* -+ * The ptl reference only gets set on or before the first submission. -+ * After the first submission, it has to be read-only. -+ */ -+ ptl_old = READ_ONCE(p->ptl); -+ if (ptl_old == NULL) -+ WRITE_ONCE(p->ptl, ptl); -+ else if (ptl_old != ptl) -+ return -EALREADY; -+ -+ status = ssh_ptl_queue_push(p); -+ if (status) -+ return status; -+ -+ ssh_ptl_tx_wakeup(ptl, !test_bit(SSH_PACKET_TY_BLOCKING_BIT, &p->state)); -+ return 0; -+} -+ -+static void __ssh_ptl_resubmit(struct ssh_packet *packet) -+{ -+ struct list_head *head; -+ -+ trace_ssam_packet_resubmit(packet); -+ -+ spin_lock(&packet->ptl->queue.lock); -+ -+ // if this packet has already been queued, do not add it -+ if (test_and_set_bit(SSH_PACKET_SF_QUEUED_BIT, &packet->state)) { -+ spin_unlock(&packet->ptl->queue.lock); -+ return; -+ } -+ -+ // find first node with lower priority -+ head = __ssh_ptl_queue_find_entrypoint(packet); -+ -+ WRITE_ONCE(packet->timestamp, KTIME_MAX); -+ smp_mb__after_atomic(); -+ -+ // add packet -+ ssh_packet_get(packet); -+ list_add_tail(&packet->queue_node, head); -+ -+ spin_unlock(&packet->ptl->queue.lock); -+} -+ -+static void ssh_ptl_resubmit_pending(struct ssh_ptl *ptl) -+{ -+ struct ssh_packet *p; -+ bool resub = false; -+ u8 try; -+ -+ /* -+ * Note: We deliberately do not remove/attempt to cancel and complete -+ * packets that are out of tires in this function. The packet will be -+ * eventually canceled and completed by the timeout. Removing the packet -+ * here could lead to overly eager cancelation if the packet has not -+ * been re-transmitted yet but the tries-counter already updated (i.e -+ * ssh_ptl_tx_next removed the packet from the queue and updated the -+ * counter, but re-transmission for the last try has not actually -+ * started yet). -+ */ -+ -+ spin_lock(&ptl->pending.lock); -+ -+ // re-queue all pending packets -+ list_for_each_entry(p, &ptl->pending.head, pending_node) { -+ // avoid further transitions if locked -+ if (test_bit(SSH_PACKET_SF_LOCKED_BIT, &p->state)) -+ continue; -+ -+ // do not re-schedule if packet is out of tries -+ try = ssh_packet_priority_get_try(READ_ONCE(p->priority)); -+ if (try >= SSH_PTL_MAX_PACKET_TRIES) -+ continue; -+ -+ resub = true; -+ __ssh_ptl_resubmit(p); -+ } -+ -+ spin_unlock(&ptl->pending.lock); -+ -+ ssh_ptl_tx_wakeup(ptl, resub); -+} -+ -+static void ssh_ptl_cancel(struct ssh_packet *p) -+{ -+ if (test_and_set_bit(SSH_PACKET_SF_CANCELED_BIT, &p->state)) -+ return; -+ -+ trace_ssam_packet_cancel(p); -+ -+ /* -+ * Lock packet and commit with memory barrier. If this packet has -+ * already been locked, it's going to be removed and completed by -+ * another party, which should have precedence. -+ */ -+ if (test_and_set_bit(SSH_PACKET_SF_LOCKED_BIT, &p->state)) -+ return; -+ -+ /* -+ * By marking the packet as locked and employing the implicit memory -+ * barrier of test_and_set_bit, we have guaranteed that, at this point, -+ * the packet cannot be added to the queue any more. -+ * -+ * In case the packet has never been submitted, packet->ptl is NULL. If -+ * the packet is currently being submitted, packet->ptl may be NULL or -+ * non-NULL. Due marking the packet as locked above and committing with -+ * the memory barrier, we have guaranteed that, if packet->ptl is NULL, -+ * the packet will never be added to the queue. If packet->ptl is -+ * non-NULL, we don't have any guarantees. -+ */ -+ -+ if (READ_ONCE(p->ptl)) { -+ ssh_ptl_remove_and_complete(p, -ECANCELED); -+ ssh_ptl_tx_wakeup(p->ptl, false); -+ } else if (!test_and_set_bit(SSH_PACKET_SF_COMPLETED_BIT, &p->state)) { -+ __ssh_ptl_complete(p, -ECANCELED); -+ } -+} -+ -+ -+static ktime_t ssh_packet_get_expiration(struct ssh_packet *p, ktime_t timeout) -+{ -+ ktime_t timestamp = READ_ONCE(p->timestamp); -+ -+ if (timestamp != KTIME_MAX) -+ return ktime_add(timestamp, timeout); -+ else -+ return KTIME_MAX; -+} -+ -+static void ssh_ptl_timeout_reap(struct work_struct *work) -+{ -+ struct ssh_ptl *ptl = to_ssh_ptl(work, rtx_timeout.reaper.work); -+ struct ssh_packet *p, *n; -+ LIST_HEAD(claimed); -+ ktime_t now = ktime_get_coarse_boottime(); -+ ktime_t timeout = ptl->rtx_timeout.timeout; -+ ktime_t next = KTIME_MAX; -+ bool resub = false; -+ -+ trace_ssam_ptl_timeout_reap("pending", atomic_read(&ptl->pending.count)); -+ -+ /* -+ * Mark reaper as "not pending". This is done before checking any -+ * packets to avoid lost-update type problems. -+ */ -+ WRITE_ONCE(ptl->rtx_timeout.expires, KTIME_MAX); -+ smp_mb__after_atomic(); -+ -+ spin_lock(&ptl->pending.lock); -+ -+ list_for_each_entry_safe(p, n, &ptl->pending.head, pending_node) { -+ ktime_t expires = ssh_packet_get_expiration(p, timeout); -+ u8 try; -+ -+ /* -+ * Check if the timeout hasn't expired yet. Find out next -+ * expiration date to be handled after this run. -+ */ -+ if (ktime_after(expires, now)) { -+ next = ktime_before(expires, next) ? expires : next; -+ continue; -+ } -+ -+ // avoid further transitions if locked -+ if (test_bit(SSH_PACKET_SF_LOCKED_BIT, &p->state)) -+ continue; -+ -+ trace_ssam_packet_timeout(p); -+ -+ // check if we still have some tries left -+ try = ssh_packet_priority_get_try(READ_ONCE(p->priority)); -+ if (likely(try < SSH_PTL_MAX_PACKET_TRIES)) { -+ resub = true; -+ __ssh_ptl_resubmit(p); -+ continue; -+ } -+ -+ // no more tries left: cancel the packet -+ -+ // if someone else has locked the packet already, don't use it -+ if (test_and_set_bit(SSH_PACKET_SF_LOCKED_BIT, &p->state)) -+ continue; -+ -+ /* -+ * We have now marked the packet as locked. Thus it cannot be -+ * added to the pending list again after we've removed it here. -+ * We can therefore re-use the pending_node of this packet -+ * temporarily. -+ */ -+ -+ clear_bit(SSH_PACKET_SF_PENDING_BIT, &p->state); -+ -+ atomic_dec(&ptl->pending.count); -+ list_del(&p->pending_node); -+ -+ list_add_tail(&p->pending_node, &claimed); -+ } -+ -+ spin_unlock(&ptl->pending.lock); -+ -+ // cancel and complete the packet -+ list_for_each_entry_safe(p, n, &claimed, pending_node) { -+ if (!test_and_set_bit(SSH_PACKET_SF_COMPLETED_BIT, &p->state)) { -+ ssh_ptl_queue_remove(p); -+ __ssh_ptl_complete(p, -ETIMEDOUT); -+ } -+ -+ // drop the reference we've obtained by removing it from pending -+ list_del(&p->pending_node); -+ ssh_packet_put(p); -+ } -+ -+ // ensure that reaper doesn't run again immediately -+ next = max(next, ktime_add(now, SSH_PTL_PACKET_TIMEOUT_RESOLUTION)); -+ if (next != KTIME_MAX) -+ ssh_ptl_timeout_reaper_mod(ptl, now, next); -+ -+ // force-wakeup to properly handle re-transmits if we've re-submitted -+ ssh_ptl_tx_wakeup(ptl, resub); -+} -+ -+ -+static bool ssh_ptl_rx_retransmit_check(struct ssh_ptl *ptl, u8 seq) -+{ -+ int i; -+ -+ // check if SEQ has been seen recently (i.e. packet was re-transmitted) -+ for (i = 0; i < ARRAY_SIZE(ptl->rx.blocked.seqs); i++) { -+ if (likely(ptl->rx.blocked.seqs[i] != seq)) -+ continue; -+ -+ ptl_dbg(ptl, "ptl: ignoring repeated data packet\n"); -+ return true; -+ } -+ -+ // update list of blocked seuence IDs -+ ptl->rx.blocked.seqs[ptl->rx.blocked.offset] = seq; -+ ptl->rx.blocked.offset = (ptl->rx.blocked.offset + 1) -+ % ARRAY_SIZE(ptl->rx.blocked.seqs); -+ -+ return false; -+} -+ -+static void ssh_ptl_rx_dataframe(struct ssh_ptl *ptl, -+ const struct ssh_frame *frame, -+ const struct ssam_span *payload) -+{ -+ if (ssh_ptl_rx_retransmit_check(ptl, frame->seq)) -+ return; -+ -+ ptl->ops.data_received(ptl, payload); -+} -+ -+static void ssh_ptl_send_ack(struct ssh_ptl *ptl, u8 seq) -+{ -+ struct ssh_packet_args args; -+ struct ssh_packet *packet; -+ struct ssam_span buf; -+ struct msgbuf msgb; -+ int status; -+ -+ status = ssh_ctrl_packet_alloc(&packet, &buf, GFP_KERNEL); -+ if (status) { -+ ptl_err(ptl, "ptl: failed to allocate ACK packet\n"); -+ return; -+ } -+ -+ args.type = 0; -+ args.priority = SSH_PACKET_PRIORITY(ACK, 0); -+ args.ops = &ssh_ptl_ctrl_packet_ops; -+ ssh_packet_init(packet, &args); -+ -+ msgb_init(&msgb, buf.ptr, buf.len); -+ msgb_push_ack(&msgb, seq); -+ ssh_packet_set_data(packet, msgb.begin, msgb_bytes_used(&msgb)); -+ -+ ssh_ptl_submit(ptl, packet); -+ ssh_packet_put(packet); -+} -+ -+static void ssh_ptl_send_nak(struct ssh_ptl *ptl) -+{ -+ struct ssh_packet_args args; -+ struct ssh_packet *packet; -+ struct ssam_span buf; -+ struct msgbuf msgb; -+ int status; -+ -+ status = ssh_ctrl_packet_alloc(&packet, &buf, GFP_KERNEL); -+ if (status) { -+ ptl_err(ptl, "ptl: failed to allocate NAK packet\n"); -+ return; -+ } -+ -+ args.type = 0; -+ args.priority = SSH_PACKET_PRIORITY(NAK, 0); -+ args.ops = &ssh_ptl_ctrl_packet_ops; -+ ssh_packet_init(packet, &args); -+ -+ msgb_init(&msgb, buf.ptr, buf.len); -+ msgb_push_nak(&msgb); -+ ssh_packet_set_data(packet, msgb.begin, msgb_bytes_used(&msgb)); -+ -+ ssh_ptl_submit(ptl, packet); -+ ssh_packet_put(packet); -+} -+ -+static size_t ssh_ptl_rx_eval(struct ssh_ptl *ptl, struct ssam_span *source) -+{ -+ struct ssh_frame *frame; -+ struct ssam_span payload; -+ struct ssam_span aligned; -+ bool syn_found; -+ int status; -+ -+ // error injection: modify data to simulate corrupt SYN bytes -+ ssh_ptl_rx_inject_invalid_syn(ptl, source); -+ -+ // find SYN -+ syn_found = sshp_find_syn(source, &aligned); -+ -+ if (unlikely(aligned.ptr - source->ptr) > 0) { -+ ptl_warn(ptl, "rx: parser: invalid start of frame, skipping\n"); -+ -+ /* -+ * Notes: -+ * - This might send multiple NAKs in case the communication -+ * starts with an invalid SYN and is broken down into multiple -+ * pieces. This should generally be handled fine, we just -+ * might receive duplicate data in this case, which is -+ * detected when handling data frames. -+ * - This path will also be executed on invalid CRCs: When an -+ * invalid CRC is encountered, the code below will skip data -+ * until direclty after the SYN. This causes the search for -+ * the next SYN, which is generally not placed directly after -+ * the last one. -+ */ -+ ssh_ptl_send_nak(ptl); -+ } -+ -+ if (unlikely(!syn_found)) -+ return aligned.ptr - source->ptr; -+ -+ // error injection: modify data to simulate corruption -+ ssh_ptl_rx_inject_invalid_data(ptl, &aligned); -+ -+ // parse and validate frame -+ status = sshp_parse_frame(&ptl->serdev->dev, &aligned, &frame, &payload, -+ SSH_PTL_RX_BUF_LEN); -+ if (status) // invalid frame: skip to next syn -+ return aligned.ptr - source->ptr + sizeof(u16); -+ if (!frame) // not enough data -+ return aligned.ptr - source->ptr; -+ -+ trace_ssam_rx_frame_received(frame); -+ -+ switch (frame->type) { -+ case SSH_FRAME_TYPE_ACK: -+ ssh_ptl_acknowledge(ptl, frame->seq); -+ break; -+ -+ case SSH_FRAME_TYPE_NAK: -+ ssh_ptl_resubmit_pending(ptl); -+ break; -+ -+ case SSH_FRAME_TYPE_DATA_SEQ: -+ ssh_ptl_send_ack(ptl, frame->seq); -+ /* fallthrough */ -+ -+ case SSH_FRAME_TYPE_DATA_NSQ: -+ ssh_ptl_rx_dataframe(ptl, frame, &payload); -+ break; -+ -+ default: -+ ptl_warn(ptl, "ptl: received frame with unknown type 0x%02x\n", -+ frame->type); -+ break; -+ } -+ -+ return aligned.ptr - source->ptr + SSH_MESSAGE_LENGTH(frame->len); -+} -+ -+static int ssh_ptl_rx_threadfn(void *data) -+{ -+ struct ssh_ptl *ptl = data; -+ -+ while (true) { -+ struct ssam_span span; -+ size_t offs = 0; -+ size_t n; -+ -+ wait_event_interruptible(ptl->rx.wq, -+ !kfifo_is_empty(&ptl->rx.fifo) -+ || kthread_should_stop()); -+ if (kthread_should_stop()) -+ break; -+ -+ // copy from fifo to evaluation buffer -+ n = sshp_buf_read_from_fifo(&ptl->rx.buf, &ptl->rx.fifo); -+ -+ ptl_dbg(ptl, "rx: received data (size: %zu)\n", n); -+ print_hex_dump_debug("rx: ", DUMP_PREFIX_OFFSET, 16, 1, -+ ptl->rx.buf.ptr + ptl->rx.buf.len - n, -+ n, false); -+ -+ // parse until we need more bytes or buffer is empty -+ while (offs < ptl->rx.buf.len) { -+ sshp_buf_span_from(&ptl->rx.buf, offs, &span); -+ n = ssh_ptl_rx_eval(ptl, &span); -+ if (n == 0) -+ break; // need more bytes -+ -+ offs += n; -+ } -+ -+ // throw away the evaluated parts -+ sshp_buf_drop(&ptl->rx.buf, offs); -+ } -+ -+ return 0; -+} -+ -+static inline void ssh_ptl_rx_wakeup(struct ssh_ptl *ptl) -+{ -+ wake_up(&ptl->rx.wq); -+} -+ -+static int ssh_ptl_rx_start(struct ssh_ptl *ptl) -+{ -+ if (ptl->rx.thread) -+ return 0; -+ -+ ptl->rx.thread = kthread_run(ssh_ptl_rx_threadfn, ptl, "surface-sh-rx"); -+ if (IS_ERR(ptl->rx.thread)) -+ return PTR_ERR(ptl->rx.thread); -+ -+ return 0; -+} -+ -+static int ssh_ptl_rx_stop(struct ssh_ptl *ptl) -+{ -+ int status = 0; -+ -+ if (ptl->rx.thread) { -+ status = kthread_stop(ptl->rx.thread); -+ ptl->rx.thread = NULL; -+ } -+ -+ return status; -+} -+ -+static int ssh_ptl_rx_rcvbuf(struct ssh_ptl *ptl, const u8 *buf, size_t n) -+{ -+ int used; -+ -+ if (test_bit(SSH_PTL_SF_SHUTDOWN_BIT, &ptl->state)) -+ return -ESHUTDOWN; -+ -+ used = kfifo_in(&ptl->rx.fifo, buf, n); -+ if (used) -+ ssh_ptl_rx_wakeup(ptl); -+ -+ return used; -+} -+ -+ -+struct ssh_flush_packet { -+ struct ssh_packet base; -+ struct completion completion; -+ int status; -+}; -+ -+static void ssh_ptl_flush_complete(struct ssh_packet *p, int status) -+{ -+ struct ssh_flush_packet *packet; -+ -+ packet = container_of(p, struct ssh_flush_packet, base); -+ packet->status = status; -+} -+ -+static void ssh_ptl_flush_release(struct ssh_packet *p) -+{ -+ struct ssh_flush_packet *packet; -+ -+ packet = container_of(p, struct ssh_flush_packet, base); -+ complete_all(&packet->completion); -+} -+ -+static const struct ssh_packet_ops ssh_flush_packet_ops = { -+ .complete = ssh_ptl_flush_complete, -+ .release = ssh_ptl_flush_release, -+}; -+ -+/** -+ * ssh_ptl_shutdown - shut down the packet transmission layer -+ * @ptl: packet transmission layer -+ * -+ * Shuts down the packet transmission layer, removing and canceling all queued -+ * and pending packets. Packets canceled by this operation will be completed -+ * with -ESHUTDOWN as status. -+ * -+ * As a result of this function, the transmission layer will be marked as shut -+ * down. Submission of packets after the transmission layer has been shut down -+ * will fail with -ESHUTDOWN. -+ */ -+static void ssh_ptl_shutdown(struct ssh_ptl *ptl) -+{ -+ LIST_HEAD(complete_q); -+ LIST_HEAD(complete_p); -+ struct ssh_packet *p, *n; -+ int status; -+ -+ // ensure that no new packets (including ACK/NAK) can be submitted -+ set_bit(SSH_PTL_SF_SHUTDOWN_BIT, &ptl->state); -+ smp_mb__after_atomic(); -+ -+ status = ssh_ptl_rx_stop(ptl); -+ if (status) -+ ptl_err(ptl, "ptl: failed to stop receiver thread\n"); -+ -+ status = ssh_ptl_tx_stop(ptl); -+ if (status) -+ ptl_err(ptl, "ptl: failed to stop transmitter thread\n"); -+ -+ cancel_delayed_work_sync(&ptl->rtx_timeout.reaper); -+ -+ /* -+ * At this point, all threads have been stopped. This means that the -+ * only references to packets from inside the system are in the queue -+ * and pending set. -+ * -+ * Note: We still need locks here because someone could still be -+ * cancelling packets. -+ * -+ * Note 2: We can re-use queue_node (or pending_node) if we mark the -+ * packet as locked an then remove it from the queue (or pending set -+ * respecitvely). Marking the packet as locked avoids re-queueing -+ * (which should already be prevented by having stopped the treads...) -+ * and not setting QUEUED_BIT (or PENDING_BIT) prevents removal from a -+ * new list via other threads (e.g. canellation). -+ * -+ * Note 3: There may be overlap between complete_p and complete_q. -+ * This is handled via test_and_set_bit on the "completed" flag -+ * (also handles cancelation). -+ */ -+ -+ // mark queued packets as locked and move them to complete_q -+ spin_lock(&ptl->queue.lock); -+ list_for_each_entry_safe(p, n, &ptl->queue.head, queue_node) { -+ set_bit(SSH_PACKET_SF_LOCKED_BIT, &p->state); -+ smp_mb__before_atomic(); -+ clear_bit(SSH_PACKET_SF_QUEUED_BIT, &p->state); -+ -+ list_del(&p->queue_node); -+ list_add_tail(&p->queue_node, &complete_q); -+ } -+ spin_unlock(&ptl->queue.lock); -+ -+ // mark pending packets as locked and move them to complete_p -+ spin_lock(&ptl->pending.lock); -+ list_for_each_entry_safe(p, n, &ptl->pending.head, pending_node) { -+ set_bit(SSH_PACKET_SF_LOCKED_BIT, &p->state); -+ smp_mb__before_atomic(); -+ clear_bit(SSH_PACKET_SF_PENDING_BIT, &p->state); -+ -+ list_del(&p->pending_node); -+ list_add_tail(&p->pending_node, &complete_q); -+ } -+ atomic_set(&ptl->pending.count, 0); -+ spin_unlock(&ptl->pending.lock); -+ -+ // complete and drop packets on complete_q -+ list_for_each_entry(p, &complete_q, queue_node) { -+ if (!test_and_set_bit(SSH_PACKET_SF_COMPLETED_BIT, &p->state)) -+ __ssh_ptl_complete(p, -ESHUTDOWN); -+ -+ ssh_packet_put(p); -+ } -+ -+ // complete and drop packets on complete_p -+ list_for_each_entry(p, &complete_p, pending_node) { -+ if (!test_and_set_bit(SSH_PACKET_SF_COMPLETED_BIT, &p->state)) -+ __ssh_ptl_complete(p, -ESHUTDOWN); -+ -+ ssh_packet_put(p); -+ } -+ -+ /* -+ * At this point we have guaranteed that the system doesn't reference -+ * any packets any more. -+ */ -+} -+ -+static inline struct device *ssh_ptl_get_device(struct ssh_ptl *ptl) -+{ -+ return ptl->serdev ? &ptl->serdev->dev : NULL; -+} -+ -+static int ssh_ptl_init(struct ssh_ptl *ptl, struct serdev_device *serdev, -+ struct ssh_ptl_ops *ops) -+{ -+ int i, status; -+ -+ ptl->serdev = serdev; -+ ptl->state = 0; -+ -+ spin_lock_init(&ptl->queue.lock); -+ INIT_LIST_HEAD(&ptl->queue.head); -+ -+ spin_lock_init(&ptl->pending.lock); -+ INIT_LIST_HEAD(&ptl->pending.head); -+ atomic_set_release(&ptl->pending.count, 0); -+ -+ ptl->tx.thread = NULL; -+ ptl->tx.thread_signal = false; -+ ptl->tx.packet = NULL; -+ ptl->tx.offset = 0; -+ init_waitqueue_head(&ptl->tx.thread_wq); -+ init_waitqueue_head(&ptl->tx.packet_wq); -+ -+ ptl->rx.thread = NULL; -+ init_waitqueue_head(&ptl->rx.wq); -+ -+ ptl->rtx_timeout.timeout = SSH_PTL_PACKET_TIMEOUT; -+ ptl->rtx_timeout.expires = KTIME_MAX; -+ INIT_DELAYED_WORK(&ptl->rtx_timeout.reaper, ssh_ptl_timeout_reap); -+ -+ ptl->ops = *ops; -+ -+ // initialize list of recent/blocked SEQs with invalid sequence IDs -+ for (i = 0; i < ARRAY_SIZE(ptl->rx.blocked.seqs); i++) -+ ptl->rx.blocked.seqs[i] = 0xFFFF; -+ ptl->rx.blocked.offset = 0; -+ -+ status = kfifo_alloc(&ptl->rx.fifo, SSH_PTL_RX_FIFO_LEN, GFP_KERNEL); -+ if (status) -+ return status; -+ -+ status = sshp_buf_alloc(&ptl->rx.buf, SSH_PTL_RX_BUF_LEN, GFP_KERNEL); -+ if (status) -+ kfifo_free(&ptl->rx.fifo); -+ -+ return status; -+} -+ -+static void ssh_ptl_destroy(struct ssh_ptl *ptl) -+{ -+ kfifo_free(&ptl->rx.fifo); -+ sshp_buf_free(&ptl->rx.buf); -+} -+ -+ -+/* -- Request transport layer (rtl). ---------------------------------------- */ -+ -+#define SSH_RTL_REQUEST_TIMEOUT ms_to_ktime(3000) -+#define SSH_RTL_REQUEST_TIMEOUT_RESOLUTION ms_to_ktime(max(2000 / HZ, 50)) -+ -+#define SSH_RTL_MAX_PENDING 3 -+ -+ -+enum ssh_rtl_state_flags { -+ SSH_RTL_SF_SHUTDOWN_BIT, -+}; -+ -+struct ssh_rtl_ops { -+ void (*handle_event)(struct ssh_rtl *rtl, const struct ssh_command *cmd, -+ const struct ssam_span *data); -+}; -+ -+struct ssh_rtl { -+ struct ssh_ptl ptl; -+ unsigned long state; -+ -+ struct { -+ spinlock_t lock; -+ struct list_head head; -+ } queue; -+ -+ struct { -+ spinlock_t lock; -+ struct list_head head; -+ atomic_t count; -+ } pending; -+ -+ struct { -+ struct work_struct work; -+ } tx; -+ -+ struct { -+ ktime_t timeout; -+ ktime_t expires; -+ struct delayed_work reaper; -+ } rtx_timeout; -+ -+ struct ssh_rtl_ops ops; -+}; -+ -+ -+#define rtl_dbg(r, fmt, ...) ptl_dbg(&(r)->ptl, fmt, ##__VA_ARGS__) -+#define rtl_info(p, fmt, ...) ptl_info(&(p)->ptl, fmt, ##__VA_ARGS__) -+#define rtl_warn(r, fmt, ...) ptl_warn(&(r)->ptl, fmt, ##__VA_ARGS__) -+#define rtl_err(r, fmt, ...) ptl_err(&(r)->ptl, fmt, ##__VA_ARGS__) -+#define rtl_dbg_cond(r, fmt, ...) __ssam_prcond(rtl_dbg, r, fmt, ##__VA_ARGS__) -+ -+#define to_ssh_rtl(ptr, member) \ -+ container_of(ptr, struct ssh_rtl, member) -+ -+#define to_ssh_request(ptr, member) \ -+ container_of(ptr, struct ssh_request, member) -+ -+static inline struct ssh_rtl *ssh_request_rtl(struct ssh_request *rqst) -+{ -+ struct ssh_ptl *ptl = READ_ONCE(rqst->packet.ptl); -+ return likely(ptl) ? to_ssh_rtl(ptl, ptl) : NULL; -+} -+ -+ -+/** -+ * ssh_rtl_should_drop_response - error injection hook to drop request responses -+ * -+ * Useful to cause request transmission timeouts in the driver by dropping the -+ * response to a request. -+ */ -+static noinline_if_inject bool ssh_rtl_should_drop_response(void) -+{ -+ return false; -+} -+// not supported on 4.19 -+// ALLOW_ERROR_INJECTION(ssh_rtl_should_drop_response, TRUE); -+ -+ -+static inline u16 ssh_request_get_rqid(struct ssh_request *rqst) -+{ -+ return get_unaligned_le16(rqst->packet.data.ptr -+ + SSH_MSGOFFSET_COMMAND(rqid)); -+} -+ -+static inline u32 ssh_request_get_rqid_safe(struct ssh_request *rqst) -+{ -+ if (!rqst->packet.data.ptr) -+ return -1; -+ -+ return ssh_request_get_rqid(rqst); -+} -+ -+ -+static void ssh_rtl_queue_remove(struct ssh_request *rqst) -+{ -+ struct ssh_rtl *rtl = ssh_request_rtl(rqst); -+ bool remove; -+ -+ spin_lock(&rtl->queue.lock); -+ -+ remove = test_and_clear_bit(SSH_REQUEST_SF_QUEUED_BIT, &rqst->state); -+ if (remove) -+ list_del(&rqst->node); -+ -+ spin_unlock(&rtl->queue.lock); -+ -+ if (remove) -+ ssh_request_put(rqst); -+} -+ -+static void ssh_rtl_pending_remove(struct ssh_request *rqst) -+{ -+ struct ssh_rtl *rtl = ssh_request_rtl(rqst); -+ bool remove; -+ -+ spin_lock(&rtl->pending.lock); -+ -+ remove = test_and_clear_bit(SSH_REQUEST_SF_PENDING_BIT, &rqst->state); -+ if (remove) { -+ atomic_dec(&rtl->pending.count); -+ list_del(&rqst->node); -+ } -+ -+ spin_unlock(&rtl->pending.lock); -+ -+ if (remove) -+ ssh_request_put(rqst); -+} -+ -+ -+static void ssh_rtl_complete_with_status(struct ssh_request *rqst, int status) -+{ -+ struct ssh_rtl *rtl = ssh_request_rtl(rqst); -+ -+ trace_ssam_request_complete(rqst, status); -+ -+ // rtl/ptl may not be set if we're cancelling before submitting -+ rtl_dbg_cond(rtl, "rtl: completing request (rqid: 0x%04x," -+ " status: %d)\n", ssh_request_get_rqid_safe(rqst), status); -+ -+ if (status && status != -ECANCELED) -+ rtl_dbg_cond(rtl, "rtl: request error: %d\n", status); -+ -+ rqst->ops->complete(rqst, NULL, NULL, status); -+} -+ -+static void ssh_rtl_complete_with_rsp(struct ssh_request *rqst, -+ const struct ssh_command *cmd, -+ const struct ssam_span *data) -+{ -+ struct ssh_rtl *rtl = ssh_request_rtl(rqst); -+ -+ trace_ssam_request_complete(rqst, 0); -+ -+ rtl_dbg(rtl, "rtl: completing request with response" -+ " (rqid: 0x%04x)\n", ssh_request_get_rqid(rqst)); -+ -+ rqst->ops->complete(rqst, cmd, data, 0); -+} -+ -+ -+static bool ssh_rtl_tx_can_process(struct ssh_request *rqst) -+{ -+ struct ssh_rtl *rtl = ssh_request_rtl(rqst); -+ -+ if (test_bit(SSH_REQUEST_TY_FLUSH_BIT, &rqst->state)) -+ return !atomic_read(&rtl->pending.count); -+ -+ return atomic_read(&rtl->pending.count) < SSH_RTL_MAX_PENDING; -+} -+ -+static struct ssh_request *ssh_rtl_tx_next(struct ssh_rtl *rtl) -+{ -+ struct ssh_request *rqst = ERR_PTR(-ENOENT); -+ struct ssh_request *p, *n; -+ -+ spin_lock(&rtl->queue.lock); -+ -+ // find first non-locked request and remove it -+ list_for_each_entry_safe(p, n, &rtl->queue.head, node) { -+ if (unlikely(test_bit(SSH_REQUEST_SF_LOCKED_BIT, &p->state))) -+ continue; -+ -+ if (!ssh_rtl_tx_can_process(p)) { -+ rqst = ERR_PTR(-EBUSY); -+ break; -+ } -+ -+ /* -+ * Remove from queue and mark as transmitting. Ensure that the -+ * state does not get zero via memory barrier. -+ */ -+ set_bit(SSH_REQUEST_SF_TRANSMITTING_BIT, &p->state); -+ smp_mb__before_atomic(); -+ clear_bit(SSH_REQUEST_SF_QUEUED_BIT, &p->state); -+ -+ list_del(&p->node); -+ -+ rqst = p; -+ break; -+ } -+ -+ spin_unlock(&rtl->queue.lock); -+ return rqst; -+} -+ -+static int ssh_rtl_tx_pending_push(struct ssh_request *rqst) -+{ -+ struct ssh_rtl *rtl = ssh_request_rtl(rqst); -+ -+ spin_lock(&rtl->pending.lock); -+ -+ if (test_bit(SSH_REQUEST_SF_LOCKED_BIT, &rqst->state)) { -+ spin_unlock(&rtl->pending.lock); -+ return -EINVAL; -+ } -+ -+ if (test_and_set_bit(SSH_REQUEST_SF_PENDING_BIT, &rqst->state)) { -+ spin_unlock(&rtl->pending.lock); -+ return -EALREADY; -+ } -+ -+ atomic_inc(&rtl->pending.count); -+ ssh_request_get(rqst); -+ list_add_tail(&rqst->node, &rtl->pending.head); -+ -+ spin_unlock(&rtl->pending.lock); -+ return 0; -+} -+ -+static int ssh_rtl_tx_try_process_one(struct ssh_rtl *rtl) -+{ -+ struct ssh_request *rqst; -+ int status; -+ -+ // get and prepare next request for transmit -+ rqst = ssh_rtl_tx_next(rtl); -+ if (IS_ERR(rqst)) -+ return PTR_ERR(rqst); -+ -+ // add to/mark as pending -+ status = ssh_rtl_tx_pending_push(rqst); -+ if (status) { -+ ssh_request_put(rqst); -+ return -EAGAIN; -+ } -+ -+ // submit packet -+ status = ssh_ptl_submit(&rtl->ptl, &rqst->packet); -+ if (status == -ESHUTDOWN) { -+ /* -+ * Packet has been refused due to the packet layer shutting -+ * down. Complete it here. -+ */ -+ set_bit(SSH_REQUEST_SF_LOCKED_BIT, &rqst->state); -+ smp_mb__after_atomic(); -+ -+ ssh_rtl_pending_remove(rqst); -+ ssh_rtl_complete_with_status(rqst, -ESHUTDOWN); -+ -+ ssh_request_put(rqst); -+ return -ESHUTDOWN; -+ -+ } else if (status) { -+ /* -+ * If submitting the packet failed and the packet layer isn't -+ * shutting down, the packet has either been submmitted/queued -+ * before (-EALREADY, which cannot happen as we have guaranteed -+ * that requests cannot be re-submitted), or the packet was -+ * marked as locked (-EINVAL). To mark the packet locked at this -+ * stage, the request, and thus the packets itself, had to have -+ * been canceled. Simply drop the reference. Cancellation itself -+ * will remove it from the set of pending requests. -+ */ -+ -+ WARN_ON(status != -EINVAL); -+ -+ ssh_request_put(rqst); -+ return -EAGAIN; -+ } -+ -+ ssh_request_put(rqst); -+ return 0; -+} -+ -+static bool ssh_rtl_queue_empty(struct ssh_rtl *rtl) -+{ -+ bool empty; -+ -+ spin_lock(&rtl->queue.lock); -+ empty = list_empty(&rtl->queue.head); -+ spin_unlock(&rtl->queue.lock); -+ -+ return empty; -+} -+ -+static bool ssh_rtl_tx_schedule(struct ssh_rtl *rtl) -+{ -+ if (atomic_read(&rtl->pending.count) >= SSH_RTL_MAX_PENDING) -+ return false; -+ -+ if (ssh_rtl_queue_empty(rtl)) -+ return false; -+ -+ return schedule_work(&rtl->tx.work); -+} -+ -+static void ssh_rtl_tx_work_fn(struct work_struct *work) -+{ -+ struct ssh_rtl *rtl = to_ssh_rtl(work, tx.work); -+ int i, status; -+ -+ /* -+ * Try to be nice and not block the workqueue: Run a maximum of 10 -+ * tries, then re-submit if necessary. This should not be neccesary, -+ * for normal execution, but guarantee it anyway. -+ */ -+ for (i = 0; i < 10; i++) { -+ status = ssh_rtl_tx_try_process_one(rtl); -+ if (status == -ENOENT || status == -EBUSY) -+ return; // no more requests to process -+ -+ if (status == -ESHUTDOWN) { -+ /* -+ * Packet system shutting down. No new packets can be -+ * transmitted. Return silently, the party initiating -+ * the shutdown should handle the rest. -+ */ -+ return; -+ } -+ -+ WARN_ON(status != 0 && status != -EAGAIN); -+ } -+ -+ // out of tries, reschedule -+ ssh_rtl_tx_schedule(rtl); -+} -+ -+ -+static int ssh_rtl_submit(struct ssh_rtl *rtl, struct ssh_request *rqst) -+{ -+ trace_ssam_request_submit(rqst); -+ -+ /* -+ * Ensure that requests expecting a response are sequenced. If this -+ * invariant ever changes, see the comment in ssh_rtl_complete on what -+ * is required to be changed in the code. -+ */ -+ if (test_bit(SSH_REQUEST_TY_HAS_RESPONSE_BIT, &rqst->state)) -+ if (!test_bit(SSH_PACKET_TY_SEQUENCED_BIT, &rqst->packet.state)) -+ return -EINVAL; -+ -+ // try to set ptl and check if this request has already been submitted -+ if (cmpxchg(&rqst->packet.ptl, NULL, &rtl->ptl) != NULL) -+ return -EALREADY; -+ -+ spin_lock(&rtl->queue.lock); -+ -+ if (test_bit(SSH_RTL_SF_SHUTDOWN_BIT, &rtl->state)) { -+ spin_unlock(&rtl->queue.lock); -+ return -ESHUTDOWN; -+ } -+ -+ if (test_bit(SSH_REQUEST_SF_LOCKED_BIT, &rqst->state)) { -+ spin_unlock(&rtl->queue.lock); -+ return -EINVAL; -+ } -+ -+ ssh_request_get(rqst); -+ set_bit(SSH_REQUEST_SF_QUEUED_BIT, &rqst->state); -+ list_add_tail(&rqst->node, &rtl->queue.head); -+ -+ spin_unlock(&rtl->queue.lock); -+ -+ ssh_rtl_tx_schedule(rtl); -+ return 0; -+} -+ -+ -+static void ssh_rtl_timeout_reaper_mod(struct ssh_rtl *rtl, ktime_t now, -+ ktime_t expires) -+{ -+ unsigned long delta = msecs_to_jiffies(ktime_ms_delta(expires, now)); -+ ktime_t aexp = ktime_add(expires, SSH_RTL_REQUEST_TIMEOUT_RESOLUTION); -+ ktime_t old; -+ -+ // re-adjust / schedule reaper if it is above resolution delta -+ old = READ_ONCE(rtl->rtx_timeout.expires); -+ while (ktime_before(aexp, old)) -+ old = cmpxchg64(&rtl->rtx_timeout.expires, old, expires); -+ -+ // if we updated the reaper expiration, modify work timeout -+ if (old == expires) -+ mod_delayed_work(system_wq, &rtl->rtx_timeout.reaper, delta); -+} -+ -+static void ssh_rtl_timeout_start(struct ssh_request *rqst) -+{ -+ struct ssh_rtl *rtl = ssh_request_rtl(rqst); -+ ktime_t timestamp = ktime_get_coarse_boottime(); -+ ktime_t timeout = rtl->rtx_timeout.timeout; -+ -+ if (test_bit(SSH_REQUEST_SF_LOCKED_BIT, &rqst->state)) -+ return; -+ -+ WRITE_ONCE(rqst->timestamp, timestamp); -+ smp_mb__after_atomic(); -+ -+ ssh_rtl_timeout_reaper_mod(rtl, timestamp, timestamp + timeout); -+} -+ -+ -+static void ssh_rtl_complete(struct ssh_rtl *rtl, -+ const struct ssh_command *command, -+ const struct ssam_span *command_data) -+{ -+ struct ssh_request *r = NULL; -+ struct ssh_request *p, *n; -+ u16 rqid = get_unaligned_le16(&command->rqid); -+ -+ trace_ssam_rx_response_received(command, command_data->len); -+ -+ /* -+ * Get request from pending based on request ID and mark it as response -+ * received and locked. -+ */ -+ spin_lock(&rtl->pending.lock); -+ list_for_each_entry_safe(p, n, &rtl->pending.head, node) { -+ // we generally expect requests to be processed in order -+ if (unlikely(ssh_request_get_rqid(p) != rqid)) -+ continue; -+ -+ // simulate response timeout -+ if (ssh_rtl_should_drop_response()) { -+ spin_unlock(&rtl->pending.lock); -+ -+ trace_ssam_ei_rx_drop_response(p); -+ rtl_info(rtl, "request error injection: " -+ "dropping response for request %p\n", -+ &p->packet); -+ return; -+ } -+ -+ /* -+ * Mark as "response received" and "locked" as we're going to -+ * complete it. Ensure that the state doesn't get zero by -+ * employing a memory barrier. -+ */ -+ set_bit(SSH_REQUEST_SF_LOCKED_BIT, &p->state); -+ set_bit(SSH_REQUEST_SF_RSPRCVD_BIT, &p->state); -+ smp_mb__before_atomic(); -+ clear_bit(SSH_REQUEST_SF_PENDING_BIT, &p->state); -+ -+ atomic_dec(&rtl->pending.count); -+ list_del(&p->node); -+ -+ r = p; -+ break; -+ } -+ spin_unlock(&rtl->pending.lock); -+ -+ if (!r) { -+ rtl_warn(rtl, "rtl: dropping unexpected command message" -+ " (rqid = 0x%04x)\n", rqid); -+ return; -+ } -+ -+ // if the request hasn't been completed yet, we will do this now -+ if (test_and_set_bit(SSH_REQUEST_SF_COMPLETED_BIT, &r->state)) { -+ ssh_request_put(r); -+ ssh_rtl_tx_schedule(rtl); -+ return; -+ } -+ -+ /* -+ * Make sure the request has been transmitted. In case of a sequenced -+ * request, we are guaranteed that the completion callback will run on -+ * the receiver thread directly when the ACK for the packet has been -+ * received. Similarly, this function is guaranteed to run on the -+ * receiver thread. Thus we are guaranteed that if the packet has been -+ * successfully transmitted and received an ACK, the transmitted flag -+ * has been set and is visible here. -+ * -+ * We are currently not handling unsequenced packets here, as those -+ * should never expect a response as ensured in ssh_rtl_submit. If this -+ * ever changes, one would have to test for -+ * -+ * (r->state & (transmitting | transmitted)) -+ * -+ * on unsequenced packets to determine if they could have been -+ * transmitted. There are no synchronization guarantees as in the -+ * sequenced case, since, in this case, the callback function will not -+ * run on the same thread. Thus an exact determination is impossible. -+ */ -+ if (!test_bit(SSH_REQUEST_SF_TRANSMITTED_BIT, &r->state)) { -+ rtl_err(rtl, "rtl: received response before ACK for request" -+ " (rqid = 0x%04x)\n", rqid); -+ -+ /* -+ * NB: Timeout has already been canceled, request already been -+ * removed from pending and marked as locked and completed. As -+ * we receive a "false" response, the packet might still be -+ * queued though. -+ */ -+ ssh_rtl_queue_remove(r); -+ -+ ssh_rtl_complete_with_status(r, -EREMOTEIO); -+ ssh_request_put(r); -+ -+ ssh_rtl_tx_schedule(rtl); -+ return; -+ } -+ -+ /* -+ * NB: Timeout has already been canceled, request already been -+ * removed from pending and marked as locked and completed. The request -+ * can also not be queued any more, as it has been marked as -+ * transmitting and later transmitted. Thus no need to remove it from -+ * anywhere. -+ */ -+ -+ ssh_rtl_complete_with_rsp(r, command, command_data); -+ ssh_request_put(r); -+ -+ ssh_rtl_tx_schedule(rtl); -+} -+ -+ -+static bool ssh_rtl_cancel_nonpending(struct ssh_request *r) -+{ -+ struct ssh_rtl *rtl; -+ unsigned long state, fixed; -+ bool remove; -+ -+ /* -+ * Handle unsubmitted request: Try to mark the packet as locked, -+ * expecting the state to be zero (i.e. unsubmitted). Note that, if -+ * setting the state worked, we might still be adding the packet to the -+ * queue in a currently executing submit call. In that case, however, -+ * ptl reference must have been set previously, as locked is checked -+ * after setting ptl. Thus only if we successfully lock this request and -+ * ptl is NULL, we have successfully removed the request. -+ * Otherwise we need to try and grab it from the queue. -+ * -+ * Note that if the CMPXCHG fails, we are guaranteed that ptl has -+ * been set and is non-NULL, as states can only be nonzero after this -+ * has been set. Also note that we need to fetch the static (type) flags -+ * to ensure that they don't cause the cmpxchg to fail. -+ */ -+ fixed = READ_ONCE(r->state) & SSH_REQUEST_FLAGS_TY_MASK; -+ state = cmpxchg(&r->state, fixed, SSH_REQUEST_SF_LOCKED_BIT); -+ if (!state && !READ_ONCE(r->packet.ptl)) { -+ if (test_and_set_bit(SSH_REQUEST_SF_COMPLETED_BIT, &r->state)) -+ return true; -+ -+ ssh_rtl_complete_with_status(r, -ECANCELED); -+ return true; -+ } -+ -+ rtl = ssh_request_rtl(r); -+ spin_lock(&rtl->queue.lock); -+ -+ /* -+ * Note: 1) Requests cannot be re-submitted. 2) If a request is queued, -+ * it cannot be "transmitting"/"pending" yet. Thus, if we successfully -+ * remove the the request here, we have removed all its occurences in -+ * the system. -+ */ -+ -+ remove = test_and_clear_bit(SSH_REQUEST_SF_QUEUED_BIT, &r->state); -+ if (!remove) { -+ spin_unlock(&rtl->queue.lock); -+ return false; -+ } -+ -+ set_bit(SSH_REQUEST_SF_LOCKED_BIT, &r->state); -+ list_del(&r->node); -+ -+ spin_unlock(&rtl->queue.lock); -+ -+ ssh_request_put(r); // drop reference obtained from queue -+ -+ if (test_and_set_bit(SSH_REQUEST_SF_COMPLETED_BIT, &r->state)) -+ return true; -+ -+ ssh_rtl_complete_with_status(r, -ECANCELED); -+ return true; -+} -+ -+static bool ssh_rtl_cancel_pending(struct ssh_request *r) -+{ -+ // if the packet is already locked, it's going to be removed shortly -+ if (test_and_set_bit(SSH_REQUEST_SF_LOCKED_BIT, &r->state)) -+ return true; -+ -+ /* -+ * Now that we have locked the packet, we have guaranteed that it can't -+ * be added to the system any more. If rtl is zero, the locked -+ * check in ssh_rtl_submit has not been run and any submission, -+ * currently in progress or called later, won't add the packet. Thus we -+ * can directly complete it. -+ */ -+ if (!ssh_request_rtl(r)) { -+ if (test_and_set_bit(SSH_REQUEST_SF_COMPLETED_BIT, &r->state)) -+ return true; -+ -+ ssh_rtl_complete_with_status(r, -ECANCELED); -+ return true; -+ } -+ -+ /* -+ * Try to cancel the packet. If the packet has not been completed yet, -+ * this will subsequently (and synchronously) call the completion -+ * callback of the packet, which will complete the request. -+ */ -+ ssh_ptl_cancel(&r->packet); -+ -+ /* -+ * If the packet has been completed with success, i.e. has not been -+ * canceled by the above call, the request may not have been completed -+ * yet (may be waiting for a response). Check if we need to do this -+ * here. -+ */ -+ if (test_and_set_bit(SSH_REQUEST_SF_COMPLETED_BIT, &r->state)) -+ return true; -+ -+ ssh_rtl_queue_remove(r); -+ ssh_rtl_pending_remove(r); -+ ssh_rtl_complete_with_status(r, -ECANCELED); -+ -+ return true; -+} -+ -+static bool ssh_rtl_cancel(struct ssh_request *rqst, bool pending) -+{ -+ struct ssh_rtl *rtl; -+ bool canceled; -+ -+ if (test_and_set_bit(SSH_REQUEST_SF_CANCELED_BIT, &rqst->state)) -+ return true; -+ -+ trace_ssam_request_cancel(rqst); -+ -+ if (pending) -+ canceled = ssh_rtl_cancel_pending(rqst); -+ else -+ canceled = ssh_rtl_cancel_nonpending(rqst); -+ -+ // note: rtl may be NULL if request has not been submitted yet -+ rtl = ssh_request_rtl(rqst); -+ if (canceled && rtl) -+ ssh_rtl_tx_schedule(rtl); -+ -+ return canceled; -+} -+ -+ -+static void ssh_rtl_packet_callback(struct ssh_packet *p, int status) -+{ -+ struct ssh_request *r = to_ssh_request(p, packet); -+ -+ if (unlikely(status)) { -+ set_bit(SSH_REQUEST_SF_LOCKED_BIT, &r->state); -+ -+ if (test_and_set_bit(SSH_REQUEST_SF_COMPLETED_BIT, &r->state)) -+ return; -+ -+ /* -+ * The packet may get cancelled even though it has not been -+ * submitted yet. The request may still be queued. Check the -+ * queue and remove it if necessary. As the timeout would have -+ * been started in this function on success, there's no need to -+ * cancel it here. -+ */ -+ ssh_rtl_queue_remove(r); -+ ssh_rtl_pending_remove(r); -+ ssh_rtl_complete_with_status(r, status); -+ -+ ssh_rtl_tx_schedule(ssh_request_rtl(r)); -+ return; -+ } -+ -+ /* -+ * Mark as transmitted, ensure that state doesn't get zero by inserting -+ * a memory barrier. -+ */ -+ set_bit(SSH_REQUEST_SF_TRANSMITTED_BIT, &r->state); -+ smp_mb__before_atomic(); -+ clear_bit(SSH_REQUEST_SF_TRANSMITTING_BIT, &r->state); -+ -+ // if we expect a response, we just need to start the timeout -+ if (test_bit(SSH_REQUEST_TY_HAS_RESPONSE_BIT, &r->state)) { -+ ssh_rtl_timeout_start(r); -+ return; -+ } -+ -+ /* -+ * If we don't expect a response, lock, remove, and complete the -+ * request. Note that, at this point, the request is guaranteed to have -+ * left the queue and no timeout has been started. Thus we only need to -+ * remove it from pending. If the request has already been completed (it -+ * may have been canceled) return. -+ */ -+ -+ set_bit(SSH_REQUEST_SF_LOCKED_BIT, &r->state); -+ if (test_and_set_bit(SSH_REQUEST_SF_COMPLETED_BIT, &r->state)) -+ return; -+ -+ ssh_rtl_pending_remove(r); -+ ssh_rtl_complete_with_status(r, 0); -+ -+ ssh_rtl_tx_schedule(ssh_request_rtl(r)); -+} -+ -+ -+static ktime_t ssh_request_get_expiration(struct ssh_request *r, ktime_t timeo) -+{ -+ ktime_t timestamp = READ_ONCE(r->timestamp); -+ -+ if (timestamp != KTIME_MAX) -+ return ktime_add(timestamp, timeo); -+ else -+ return KTIME_MAX; -+} -+ -+static void ssh_rtl_timeout_reap(struct work_struct *work) -+{ -+ struct ssh_rtl *rtl = to_ssh_rtl(work, rtx_timeout.reaper.work); -+ struct ssh_request *r, *n; -+ LIST_HEAD(claimed); -+ ktime_t now = ktime_get_coarse_boottime(); -+ ktime_t timeout = rtl->rtx_timeout.timeout; -+ ktime_t next = KTIME_MAX; -+ -+ trace_ssam_rtl_timeout_reap("pending", atomic_read(&rtl->pending.count)); -+ -+ /* -+ * Mark reaper as "not pending". This is done before checking any -+ * requests to avoid lost-update type problems. -+ */ -+ WRITE_ONCE(rtl->rtx_timeout.expires, KTIME_MAX); -+ smp_mb__after_atomic(); -+ -+ spin_lock(&rtl->pending.lock); -+ list_for_each_entry_safe(r, n, &rtl->pending.head, node) { -+ ktime_t expires = ssh_request_get_expiration(r, timeout); -+ -+ /* -+ * Check if the timeout hasn't expired yet. Find out next -+ * expiration date to be handled after this run. -+ */ -+ if (ktime_after(expires, now)) { -+ next = ktime_before(expires, next) ? expires : next; -+ continue; -+ } -+ -+ // avoid further transitions if locked -+ if (test_and_set_bit(SSH_REQUEST_SF_LOCKED_BIT, &r->state)) -+ continue; -+ -+ /* -+ * We have now marked the packet as locked. Thus it cannot be -+ * added to the pending or queued lists again after we've -+ * removed it here. We can therefore re-use the node of this -+ * packet temporarily. -+ */ -+ -+ clear_bit(SSH_REQUEST_SF_PENDING_BIT, &r->state); -+ -+ atomic_dec(&rtl->pending.count); -+ list_del(&r->node); -+ -+ list_add_tail(&r->node, &claimed); -+ } -+ spin_unlock(&rtl->pending.lock); -+ -+ // cancel and complete the request -+ list_for_each_entry_safe(r, n, &claimed, node) { -+ trace_ssam_request_timeout(r); -+ -+ /* -+ * At this point we've removed the packet from pending. This -+ * means that we've obtained the last (only) reference of the -+ * system to it. Thus we can just complete it. -+ */ -+ if (!test_and_set_bit(SSH_REQUEST_SF_COMPLETED_BIT, &r->state)) -+ ssh_rtl_complete_with_status(r, -ETIMEDOUT); -+ -+ // drop the reference we've obtained by removing it from pending -+ list_del(&r->node); -+ ssh_request_put(r); -+ } -+ -+ // ensure that reaper doesn't run again immediately -+ next = max(next, ktime_add(now, SSH_RTL_REQUEST_TIMEOUT_RESOLUTION)); -+ if (next != KTIME_MAX) -+ ssh_rtl_timeout_reaper_mod(rtl, now, next); -+ -+ ssh_rtl_tx_schedule(rtl); -+} -+ -+ -+static void ssh_rtl_rx_event(struct ssh_rtl *rtl, const struct ssh_command *cmd, -+ const struct ssam_span *data) -+{ -+ trace_ssam_rx_event_received(cmd, data->len); -+ -+ rtl_dbg(rtl, "rtl: handling event (rqid: 0x%04x)\n", -+ get_unaligned_le16(&cmd->rqid)); -+ -+ rtl->ops.handle_event(rtl, cmd, data); -+} -+ -+static void ssh_rtl_rx_command(struct ssh_ptl *p, const struct ssam_span *data) -+{ -+ struct ssh_rtl *rtl = to_ssh_rtl(p, ptl); -+ struct device *dev = &p->serdev->dev; -+ struct ssh_command *command; -+ struct ssam_span command_data; -+ -+ if (sshp_parse_command(dev, data, &command, &command_data)) -+ return; -+ -+ if (ssh_rqid_is_event(get_unaligned_le16(&command->rqid))) -+ ssh_rtl_rx_event(rtl, command, &command_data); -+ else -+ ssh_rtl_complete(rtl, command, &command_data); -+} -+ -+static void ssh_rtl_rx_data(struct ssh_ptl *p, const struct ssam_span *data) -+{ -+ switch (data->ptr[0]) { -+ case SSH_PLD_TYPE_CMD: -+ ssh_rtl_rx_command(p, data); -+ break; -+ -+ default: -+ ptl_err(p, "rtl: rx: unknown frame payload type" -+ " (type: 0x%02x)\n", data->ptr[0]); -+ break; -+ } -+} -+ -+ -+static inline struct device *ssh_rtl_get_device(struct ssh_rtl *rtl) -+{ -+ return ssh_ptl_get_device(&rtl->ptl); -+} -+ -+static inline bool ssh_rtl_tx_flush(struct ssh_rtl *rtl) -+{ -+ return flush_work(&rtl->tx.work); -+} -+ -+static inline int ssh_rtl_tx_start(struct ssh_rtl *rtl) -+{ -+ int status; -+ bool sched; -+ -+ status = ssh_ptl_tx_start(&rtl->ptl); -+ if (status) -+ return status; -+ -+ /* -+ * If the packet layer has been shut down and restarted without shutting -+ * down the request layer, there may still be requests queued and not -+ * handled. -+ */ -+ spin_lock(&rtl->queue.lock); -+ sched = !list_empty(&rtl->queue.head); -+ spin_unlock(&rtl->queue.lock); -+ -+ if (sched) -+ ssh_rtl_tx_schedule(rtl); -+ -+ return 0; -+} -+ -+static inline int ssh_rtl_rx_start(struct ssh_rtl *rtl) -+{ -+ return ssh_ptl_rx_start(&rtl->ptl); -+} -+ -+static int ssh_rtl_init(struct ssh_rtl *rtl, struct serdev_device *serdev, -+ const struct ssh_rtl_ops *ops) -+{ -+ struct ssh_ptl_ops ptl_ops; -+ int status; -+ -+ ptl_ops.data_received = ssh_rtl_rx_data; -+ -+ status = ssh_ptl_init(&rtl->ptl, serdev, &ptl_ops); -+ if (status) -+ return status; -+ -+ spin_lock_init(&rtl->queue.lock); -+ INIT_LIST_HEAD(&rtl->queue.head); -+ -+ spin_lock_init(&rtl->pending.lock); -+ INIT_LIST_HEAD(&rtl->pending.head); -+ atomic_set_release(&rtl->pending.count, 0); -+ -+ INIT_WORK(&rtl->tx.work, ssh_rtl_tx_work_fn); -+ -+ rtl->rtx_timeout.timeout = SSH_RTL_REQUEST_TIMEOUT; -+ rtl->rtx_timeout.expires = KTIME_MAX; -+ INIT_DELAYED_WORK(&rtl->rtx_timeout.reaper, ssh_rtl_timeout_reap); -+ -+ rtl->ops = *ops; -+ -+ return 0; -+} -+ -+static void ssh_rtl_destroy(struct ssh_rtl *rtl) -+{ -+ ssh_ptl_destroy(&rtl->ptl); -+} -+ -+ -+static void ssh_rtl_packet_release(struct ssh_packet *p) -+{ -+ struct ssh_request *rqst = to_ssh_request(p, packet); -+ rqst->ops->release(rqst); -+} -+ -+static const struct ssh_packet_ops ssh_rtl_packet_ops = { -+ .complete = ssh_rtl_packet_callback, -+ .release = ssh_rtl_packet_release, -+}; -+ -+static void ssh_request_init(struct ssh_request *rqst, -+ enum ssam_request_flags flags, -+ const struct ssh_request_ops *ops) -+{ -+ struct ssh_packet_args packet_args; -+ -+ packet_args.type = BIT(SSH_PACKET_TY_BLOCKING_BIT); -+ if (!(flags & SSAM_REQUEST_UNSEQUENCED)) -+ packet_args.type |= BIT(SSH_PACKET_TY_SEQUENCED_BIT); -+ -+ packet_args.priority = SSH_PACKET_PRIORITY(DATA, 0); -+ packet_args.ops = &ssh_rtl_packet_ops; -+ -+ ssh_packet_init(&rqst->packet, &packet_args); -+ INIT_LIST_HEAD(&rqst->node); -+ -+ rqst->state = 0; -+ if (flags & SSAM_REQUEST_HAS_RESPONSE) -+ rqst->state |= BIT(SSH_REQUEST_TY_HAS_RESPONSE_BIT); -+ -+ rqst->timestamp = KTIME_MAX; -+ rqst->ops = ops; -+} -+ -+ -+struct ssh_flush_request { -+ struct ssh_request base; -+ struct completion completion; -+ int status; -+}; -+ -+static void ssh_rtl_flush_request_complete(struct ssh_request *r, -+ const struct ssh_command *cmd, -+ const struct ssam_span *data, -+ int status) -+{ -+ struct ssh_flush_request *rqst; -+ -+ rqst = container_of(r, struct ssh_flush_request, base); -+ rqst->status = status; -+} -+ -+static void ssh_rtl_flush_request_release(struct ssh_request *r) -+{ -+ struct ssh_flush_request *rqst; -+ -+ rqst = container_of(r, struct ssh_flush_request, base); -+ complete_all(&rqst->completion); -+} -+ -+static const struct ssh_request_ops ssh_rtl_flush_request_ops = { -+ .complete = ssh_rtl_flush_request_complete, -+ .release = ssh_rtl_flush_request_release, -+}; -+ -+/** -+ * ssh_rtl_flush - flush the request transmission layer -+ * @rtl: request transmission layer -+ * @timeout: timeout for the flush operation in jiffies -+ * -+ * Queue a special flush request and wait for its completion. This request -+ * will be completed after all other currently queued and pending requests -+ * have been completed. Instead of a normal data packet, this request submits -+ * a special flush packet, meaning that upon completion, also the underlying -+ * packet transmission layer has been flushed. -+ * -+ * Flushing the request layer gurarantees that all previously submitted -+ * requests have been fully completed before this call returns. Additinally, -+ * flushing blocks execution of all later submitted requests until the flush -+ * has been completed. -+ * -+ * If the caller ensures that no new requests are submitted after a call to -+ * this function, the request transmission layer is guaranteed to have no -+ * remaining requests when this call returns. The same guarantee does not hold -+ * for the packet layer, on which control packets may still be queued after -+ * this call. See the documentation of ssh_ptl_flush for more details on -+ * packet layer flushing. -+ * -+ * Return: Zero on success, -ETIMEDOUT if the flush timed out and has been -+ * canceled as a result of the timeout, or -ESHUTDOWN if the packet and/or -+ * request transmission layer has been shut down before this call. May also -+ * return -EINTR if the underlying packet transmission has been interrupted. -+ */ -+static int ssh_rtl_flush(struct ssh_rtl *rtl, unsigned long timeout) -+{ -+ const unsigned init_flags = SSAM_REQUEST_UNSEQUENCED; -+ struct ssh_flush_request rqst; -+ int status; -+ -+ ssh_request_init(&rqst.base, init_flags, &ssh_rtl_flush_request_ops); -+ rqst.base.packet.state |= BIT(SSH_PACKET_TY_FLUSH_BIT); -+ rqst.base.packet.priority = SSH_PACKET_PRIORITY(FLUSH, 0); -+ rqst.base.state |= BIT(SSH_REQUEST_TY_FLUSH_BIT); -+ -+ init_completion(&rqst.completion); -+ -+ status = ssh_rtl_submit(rtl, &rqst.base); -+ if (status) -+ return status; -+ -+ ssh_request_put(&rqst.base); -+ -+ if (wait_for_completion_timeout(&rqst.completion, timeout)) -+ return 0; -+ -+ ssh_rtl_cancel(&rqst.base, true); -+ wait_for_completion(&rqst.completion); -+ -+ WARN_ON(rqst.status != 0 && rqst.status != -ECANCELED -+ && rqst.status != -ESHUTDOWN && rqst.status != -EINTR); -+ -+ return rqst.status == -ECANCELED ? -ETIMEDOUT : status; -+} -+ -+ -+static void ssh_rtl_shutdown(struct ssh_rtl *rtl) -+{ -+ struct ssh_request *r, *n; -+ LIST_HEAD(claimed); -+ int pending; -+ -+ set_bit(SSH_RTL_SF_SHUTDOWN_BIT, &rtl->state); -+ smp_mb__after_atomic(); -+ -+ // remove requests from queue -+ spin_lock(&rtl->queue.lock); -+ list_for_each_entry_safe(r, n, &rtl->queue.head, node) { -+ set_bit(SSH_REQUEST_SF_LOCKED_BIT, &r->state); -+ smp_mb__before_atomic(); -+ clear_bit(SSH_REQUEST_SF_QUEUED_BIT, &r->state); -+ -+ list_del(&r->node); -+ list_add_tail(&r->node, &claimed); -+ } -+ spin_unlock(&rtl->queue.lock); -+ -+ /* -+ * We have now guaranteed that the queue is empty and no more new -+ * requests can be submitted (i.e. it will stay empty). This means that -+ * calling ssh_rtl_tx_schedule will not schedule tx.work any more. So we -+ * can simply call cancel_work_sync on tx.work here and when that -+ * returns, we've locked it down. This also means that after this call, -+ * we don't submit any more packets to the underlying packet layer, so -+ * we can also shut that down. -+ */ -+ -+ cancel_work_sync(&rtl->tx.work); -+ ssh_ptl_shutdown(&rtl->ptl); -+ cancel_delayed_work_sync(&rtl->rtx_timeout.reaper); -+ -+ /* -+ * Shutting down the packet layer should also have caneled all requests. -+ * Thus the pending set should be empty. Attempt to handle this -+ * gracefully anyways, even though this should be dead code. -+ */ -+ -+ pending = atomic_read(&rtl->pending.count); -+ if (WARN_ON(pending)) { -+ spin_lock(&rtl->pending.lock); -+ list_for_each_entry_safe(r, n, &rtl->pending.head, node) { -+ set_bit(SSH_REQUEST_SF_LOCKED_BIT, &r->state); -+ smp_mb__before_atomic(); -+ clear_bit(SSH_REQUEST_SF_PENDING_BIT, &r->state); -+ -+ list_del(&r->node); -+ list_add_tail(&r->node, &claimed); -+ } -+ spin_unlock(&rtl->pending.lock); -+ } -+ -+ // finally cancel and complete requests -+ list_for_each_entry_safe(r, n, &claimed, node) { -+ // test_and_set because we still might compete with cancellation -+ if (!test_and_set_bit(SSH_REQUEST_SF_COMPLETED_BIT, &r->state)) -+ ssh_rtl_complete_with_status(r, -ESHUTDOWN); -+ -+ // drop the reference we've obtained by removing it from list -+ list_del(&r->node); -+ ssh_request_put(r); -+ } -+} -+ -+ +/* -- Event notifier/callbacks. --------------------------------------------- */ +/* + * The notifier system is based on linux/notifier.h, specifically the SRCU @@ -9192,12 +6501,6 @@ index 0000000000000..3f39f383e3651 + * and identify new/currently unimplemented features. + */ + -+struct ssam_nf_head { -+ struct srcu_struct srcu; -+ struct ssam_notifier_block __rcu *head; -+}; -+ -+ +int ssam_nfblk_call_chain(struct ssam_nf_head *nh, struct ssam_event *event) +{ + struct ssam_notifier_block *nb, *next_nb; @@ -9300,12 +6603,6 @@ index 0000000000000..3f39f383e3651 + int refcount; +}; + -+struct ssam_nf { -+ struct mutex lock; -+ struct rb_root refcount; -+ struct ssam_nf_head head[SSH_NUM_EVENTS]; -+}; -+ + +static int ssam_nf_refcount_inc(struct ssam_nf *nf, + struct ssam_event_registry reg, @@ -9455,45 +6752,6 @@ index 0000000000000..3f39f383e3651 + +#define SSAM_CPLT_WQ_NAME "ssam_cpltq" + -+ -+struct ssam_cplt; -+struct ssam_event_item; -+ -+struct ssam_event_item_ops { -+ void (*free)(struct ssam_event_item *); -+}; -+ -+struct ssam_event_item { -+ struct list_head node; -+ u16 rqid; -+ -+ struct ssam_event_item_ops ops; -+ struct ssam_event event; // must be last -+}; -+ -+struct ssam_event_queue { -+ struct ssam_cplt *cplt; -+ -+ spinlock_t lock; -+ struct list_head head; -+ struct work_struct work; -+}; -+ -+struct ssam_event_channel { -+ struct ssam_event_queue queue[SSH_NUM_EVENTS]; -+}; -+ -+struct ssam_cplt { -+ struct device *dev; -+ struct workqueue_struct *wq; -+ -+ struct { -+ struct ssam_event_channel channel[SSH_NUM_CHANNELS]; -+ struct ssam_nf notif; -+ } event; -+}; -+ -+ +/** + * Maximum payload length for cached `ssam_event_item`s. + * @@ -9504,7 +6762,7 @@ index 0000000000000..3f39f383e3651 + +static struct kmem_cache *ssam_event_item_cache; + -+static int ssam_event_item_cache_init(void) ++int ssam_event_item_cache_init(void) +{ + const unsigned int size = sizeof(struct ssam_event_item) + + SSAM_EVENT_ITEM_CACHE_PAYLOAD_LEN; @@ -9519,7 +6777,7 @@ index 0000000000000..3f39f383e3651 + return 0; +} + -+static void ssam_event_item_cache_destroy(void) ++void ssam_event_item_cache_destroy(void) +{ + kmem_cache_destroy(ssam_event_item_cache); + ssam_event_item_cache = NULL; @@ -9718,53 +6976,56 @@ index 0000000000000..3f39f383e3651 + +/* -- Main SSAM device structures. ------------------------------------------ */ + -+enum ssam_controller_state { -+ SSAM_CONTROLLER_UNINITIALIZED, -+ SSAM_CONTROLLER_INITIALIZED, -+ SSAM_CONTROLLER_STARTED, -+ SSAM_CONTROLLER_STOPPED, -+ SSAM_CONTROLLER_SUSPENDED, -+}; -+ -+struct ssam_device_caps { -+ u32 notif_display:1; -+ u32 notif_d0exit:1; -+}; -+ -+struct ssam_controller { -+ enum ssam_controller_state state; -+ -+ struct ssh_rtl rtl; -+ struct ssam_cplt cplt; -+ -+ struct { -+ struct ssh_seq_counter seq; -+ struct ssh_rqid_counter rqid; -+ } counter; -+ -+ struct { -+ int num; -+ bool wakeup_enabled; -+ } irq; -+ -+ struct ssam_device_caps caps; -+}; -+ -+ -+#define ssam_dbg(ctrl, fmt, ...) rtl_dbg(&(ctrl)->rtl, fmt, ##__VA_ARGS__) -+#define ssam_info(ctrl, fmt, ...) rtl_info(&(ctrl)->rtl, fmt, ##__VA_ARGS__) -+#define ssam_warn(ctrl, fmt, ...) rtl_warn(&(ctrl)->rtl, fmt, ##__VA_ARGS__) -+#define ssam_err(ctrl, fmt, ...) rtl_err(&(ctrl)->rtl, fmt, ##__VA_ARGS__) -+ -+#define to_ssam_controller(ptr, member) \ -+ container_of(ptr, struct ssam_controller, member) -+ +struct device *ssam_controller_device(struct ssam_controller *c) +{ + return ssh_rtl_get_device(&c->rtl); +} +EXPORT_SYMBOL_GPL(ssam_controller_device); + ++static void __ssam_controller_release(struct kref *kref) ++{ ++ struct ssam_controller *ctrl = to_ssam_controller(kref, kref); ++ ++ ssam_controller_destroy(ctrl); ++ kfree(ctrl); ++} ++ ++struct ssam_controller *ssam_controller_get(struct ssam_controller *c) ++{ ++ kref_get(&c->kref); ++ return c; ++} ++EXPORT_SYMBOL_GPL(ssam_controller_get); ++ ++void ssam_controller_put(struct ssam_controller *c) ++{ ++ kref_put(&c->kref, __ssam_controller_release); ++} ++EXPORT_SYMBOL_GPL(ssam_controller_put); ++ ++ ++void ssam_controller_statelock(struct ssam_controller *c) ++{ ++ down_read(&c->lock); ++} ++EXPORT_SYMBOL_GPL(ssam_controller_statelock); ++ ++void ssam_controller_stateunlock(struct ssam_controller *c) ++{ ++ up_read(&c->lock); ++} ++EXPORT_SYMBOL_GPL(ssam_controller_stateunlock); ++ ++void ssam_controller_lock(struct ssam_controller *c) ++{ ++ down_write(&c->lock); ++} ++ ++void ssam_controller_unlock(struct ssam_controller *c) ++{ ++ up_write(&c->lock); ++} ++ + +static void ssam_handle_event(struct ssh_rtl *rtl, + const struct ssh_command *cmd, @@ -9784,7 +7045,7 @@ index 0000000000000..3f39f383e3651 + item->event.channel = cmd->chn_in; + memcpy(&item->event.data[0], data->ptr, data->len); + -+ ssam_cplt_submit_event(&ctrl->cplt, item); ++ WARN_ON(ssam_cplt_submit_event(&ctrl->cplt, item)); +} + +static const struct ssh_rtl_ops ssam_rtl_ops = { @@ -9841,16 +7102,14 @@ index 0000000000000..3f39f383e3651 + return 0; +} + -+static int ssam_controller_init(struct ssam_controller *ctrl, -+ struct serdev_device *serdev) ++int ssam_controller_init(struct ssam_controller *ctrl, ++ struct serdev_device *serdev) +{ + acpi_handle handle = ACPI_HANDLE(&serdev->dev); + int status; + -+ if (smp_load_acquire(&ctrl->state) != SSAM_CONTROLLER_UNINITIALIZED) { -+ dev_err(&serdev->dev, "embedded controller already initialized\n"); -+ return -EBUSY; -+ } ++ init_rwsem(&ctrl->lock); ++ kref_init(&ctrl->kref); + + status = ssam_device_caps_load_from_acpi(handle, &ctrl->caps); + if (status) @@ -9880,7 +7139,7 @@ index 0000000000000..3f39f383e3651 + return 0; +} + -+static int ssam_controller_start(struct ssam_controller *ctrl) ++int ssam_controller_start(struct ssam_controller *ctrl) +{ + int status; + @@ -9901,7 +7160,7 @@ index 0000000000000..3f39f383e3651 + return 0; +} + -+static void ssam_controller_shutdown(struct ssam_controller *ctrl) ++void ssam_controller_shutdown(struct ssam_controller *ctrl) +{ + enum ssam_controller_state s = smp_load_acquire(&ctrl->state); + int status; @@ -9937,9 +7196,10 @@ index 0000000000000..3f39f383e3651 + ssh_rtl_shutdown(&ctrl->rtl); + + smp_store_release(&ctrl->state, SSAM_CONTROLLER_STOPPED); ++ ctrl->rtl.ptl.serdev = NULL; +} + -+static void ssam_controller_destroy(struct ssam_controller *ctrl) ++void ssam_controller_destroy(struct ssam_controller *ctrl) +{ + if (smp_load_acquire(&ctrl->state) == SSAM_CONTROLLER_UNINITIALIZED) + return; @@ -9959,40 +7219,39 @@ index 0000000000000..3f39f383e3651 + smp_store_release(&ctrl->state, SSAM_CONTROLLER_UNINITIALIZED); +} + -+static int ssam_controller_suspend(struct ssam_controller *ctrl) ++int ssam_controller_suspend(struct ssam_controller *ctrl) +{ -+ if (smp_load_acquire(&ctrl->state) != SSAM_CONTROLLER_STARTED) ++ ssam_controller_lock(ctrl); ++ ++ if (smp_load_acquire(&ctrl->state) != SSAM_CONTROLLER_STARTED) { ++ ssam_controller_unlock(ctrl); + return -EINVAL; ++ } + + ssam_dbg(ctrl, "pm: suspending controller\n"); + smp_store_release(&ctrl->state, SSAM_CONTROLLER_SUSPENDED); ++ ++ ssam_controller_unlock(ctrl); + return 0; +} + -+static int ssam_controller_resume(struct ssam_controller *ctrl) ++int ssam_controller_resume(struct ssam_controller *ctrl) +{ -+ if (smp_load_acquire(&ctrl->state) != SSAM_CONTROLLER_SUSPENDED) ++ ssam_controller_lock(ctrl); ++ ++ if (smp_load_acquire(&ctrl->state) != SSAM_CONTROLLER_SUSPENDED) { ++ ssam_controller_unlock(ctrl); + return -EINVAL; ++ } + + ssam_dbg(ctrl, "pm: resuming controller\n"); + smp_store_release(&ctrl->state, SSAM_CONTROLLER_STARTED); ++ ++ ssam_controller_unlock(ctrl); + return 0; +} + + -+static inline -+int ssam_controller_receive_buf(struct ssam_controller *ctrl, -+ const unsigned char *buf, size_t n) -+{ -+ return ssh_ptl_rx_rcvbuf(&ctrl->rtl.ptl, buf, n); -+} -+ -+static inline void ssam_controller_write_wakeup(struct ssam_controller *ctrl) -+{ -+ ssh_ptl_tx_wakeup(&ctrl->rtl.ptl, true); -+} -+ -+ +/* -- Top-level request interface ------------------------------------------- */ + +ssize_t ssam_request_write_data(struct ssam_span *buf, @@ -10026,8 +7285,8 @@ index 0000000000000..3f39f383e3651 + r = container_of(rqst, struct ssam_request_sync, base); + r->status = status; + -+ if (r->resp) -+ r->resp->length = 0; ++ if (r->resp) ++ r->resp->length = 0; + + if (status) { + rtl_dbg_cond(rtl, "rsp: request failed: %d\n", status); @@ -10038,12 +7297,12 @@ index 0000000000000..3f39f383e3651 + return; + + if (!r->resp || !r->resp->pointer) { -+ if (data->len) { -+ rtl_warn(rtl, "rsp: no response buffer provided, " -+ "dropping data\n"); -+ } -+ return; -+ } ++ if (data->len) { ++ rtl_warn(rtl, "rsp: no response buffer provided, " ++ "dropping data\n"); ++ } ++ return; ++ } + + if (data->len > r->resp->capacity) { + rtl_err(rtl, "rsp: response buffer too small, " @@ -10101,14 +7360,21 @@ index 0000000000000..3f39f383e3651 + enum ssam_controller_state state = smp_load_acquire(&ctrl->state); + int status; + -+ if (state == SSAM_CONTROLLER_SUSPENDED) { -+ ssam_warn(ctrl, "rqst: embedded controller is suspended\n"); -+ ssh_request_put(&rqst->base); -+ return -EPERM; -+ } -+ -+ if (state != SSAM_CONTROLLER_STARTED) { -+ ssam_warn(ctrl, "rqst: embedded controller is uninitialized\n"); ++ /* ++ * This is only a superficial checks. In general, the caller needs to ++ * ensure that the controller is initialized and is not (and does not ++ * get) suspended during use, i.e. until the request has been completed ++ * (if _absolutely_ necessary, by use of ssam_controller_statelock/ ++ * ssam_controller_stateunlock, but something like ssam_client_link ++ * should be preferred as this needs to last until the request has been ++ * completed). ++ * ++ * Note that it is actually safe to use this function while the ++ * controller is in the process of being shut down (as ssh_rtl_submit ++ * is safe with regards to this), but it is generally discouraged to do ++ * so. ++ */ ++ if (WARN_ON(state != SSAM_CONTROLLER_STARTED)) { + ssh_request_put(&rqst->base); + return -ENXIO; + } @@ -10324,7 +7590,7 @@ index 0000000000000..3f39f383e3651 + +/* -- Wrappers for internal SAM requests. ----------------------------------- */ + -+static int ssam_log_firmware_version(struct ssam_controller *ctrl) ++int ssam_log_firmware_version(struct ssam_controller *ctrl) +{ + __le32 __version; + u32 version, a, b, c; @@ -10343,7 +7609,7 @@ index 0000000000000..3f39f383e3651 + return 0; +} + -+static int ssam_ctrl_notif_display_off(struct ssam_controller *ctrl) ++int ssam_ctrl_notif_display_off(struct ssam_controller *ctrl) +{ + int status; + u8 response; @@ -10366,7 +7632,7 @@ index 0000000000000..3f39f383e3651 + return 0; +} + -+static int ssam_ctrl_notif_display_on(struct ssam_controller *ctrl) ++int ssam_ctrl_notif_display_on(struct ssam_controller *ctrl) +{ + int status; + u8 response; @@ -10389,7 +7655,7 @@ index 0000000000000..3f39f383e3651 + return 0; +} + -+static int ssam_ctrl_notif_d0_exit(struct ssam_controller *ctrl) ++int ssam_ctrl_notif_d0_exit(struct ssam_controller *ctrl) +{ + int status; + u8 response; @@ -10412,7 +7678,7 @@ index 0000000000000..3f39f383e3651 + return 0; +} + -+static int ssam_ctrl_notif_d0_entry(struct ssam_controller *ctrl) ++int ssam_ctrl_notif_d0_entry(struct ssam_controller *ctrl) +{ + int status; + u8 response; @@ -10454,11 +7720,6 @@ index 0000000000000..3f39f383e3651 + + mutex_lock(&nf->lock); + -+ if (smp_load_acquire(&ctrl->state) != SSAM_CONTROLLER_STARTED) { -+ mutex_unlock(&nf->lock); -+ return -ENXIO; -+ } -+ + rc = ssam_nf_refcount_inc(nf, n->event.reg, n->event.id); + if (rc < 0) { + mutex_unlock(&nf->lock); @@ -10510,11 +7771,6 @@ index 0000000000000..3f39f383e3651 + + mutex_lock(&nf->lock); + -+ if (smp_load_acquire(&ctrl->state) != SSAM_CONTROLLER_STARTED) { -+ mutex_unlock(&nf->lock); -+ return -ENXIO; -+ } -+ + rc = ssam_nf_refcount_dec(nf, n->event.reg, n->event.id); + if (rc < 0) { + mutex_unlock(&nf->lock); @@ -10568,15 +7824,6 @@ index 0000000000000..3f39f383e3651 + +/* -- Wakeup IRQ. ----------------------------------------------------------- */ + -+static const struct acpi_gpio_params gpio_ssam_wakeup_int = { 0, 0, false }; -+static const struct acpi_gpio_params gpio_ssam_wakeup = { 1, 0, false }; -+ -+static const struct acpi_gpio_mapping ssam_acpi_gpios[] = { -+ { "ssam_wakeup-int-gpio", &gpio_ssam_wakeup_int, 1 }, -+ { "ssam_wakeup-gpio", &gpio_ssam_wakeup, 1 }, -+ { }, -+}; -+ +static irqreturn_t ssam_irq_handle(int irq, void *dev_id) +{ + struct ssam_controller *ctrl = dev_id; @@ -10605,7 +7852,7 @@ index 0000000000000..3f39f383e3651 + return IRQ_HANDLED; +} + -+static int ssam_irq_setup(struct ssam_controller *ctrl) ++int ssam_irq_setup(struct ssam_controller *ctrl) +{ + struct device *dev = ssam_controller_device(ctrl); + struct gpio_desc *gpiod; @@ -10644,11 +7891,215 @@ index 0000000000000..3f39f383e3651 + return 0; +} + -+static void ssam_irq_free(struct ssam_controller *ctrl) ++void ssam_irq_free(struct ssam_controller *ctrl) +{ + free_irq(ctrl->irq.num, ctrl); + ctrl->irq.num = -1; +} +diff --git a/drivers/misc/surface_sam/controller.h b/drivers/misc/surface_sam/controller.h +new file mode 100644 +index 0000000000000..9212243b526d4 +--- /dev/null ++++ b/drivers/misc/surface_sam/controller.h +@@ -0,0 +1,170 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++ ++#ifndef _SSAM_CONTROLLER_H ++#define _SSAM_CONTROLLER_H ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++ ++#include "ssh_protocol.h" ++#include "ssh_request_layer.h" ++ ++ ++/* -- Safe counters. -------------------------------------------------------- */ ++ ++struct ssh_seq_counter { ++ u8 value; ++}; ++ ++struct ssh_rqid_counter { ++ u16 value; ++}; ++ ++ ++/* -- Event/notification system. -------------------------------------------- */ ++ ++struct ssam_nf_head { ++ struct srcu_struct srcu; ++ struct ssam_notifier_block __rcu *head; ++}; ++ ++struct ssam_nf { ++ struct mutex lock; ++ struct rb_root refcount; ++ struct ssam_nf_head head[SSH_NUM_EVENTS]; ++}; ++ ++ ++/* -- Event/async request completion system. -------------------------------- */ ++ ++struct ssam_cplt; ++struct ssam_event_item; ++ ++struct ssam_event_item_ops { ++ void (*free)(struct ssam_event_item *); ++}; ++ ++struct ssam_event_item { ++ struct list_head node; ++ u16 rqid; ++ ++ struct ssam_event_item_ops ops; ++ struct ssam_event event; // must be last ++}; ++ ++struct ssam_event_queue { ++ struct ssam_cplt *cplt; ++ ++ spinlock_t lock; ++ struct list_head head; ++ struct work_struct work; ++}; ++ ++struct ssam_event_channel { ++ struct ssam_event_queue queue[SSH_NUM_EVENTS]; ++}; ++ ++struct ssam_cplt { ++ struct device *dev; ++ struct workqueue_struct *wq; ++ ++ struct { ++ struct ssam_event_channel channel[SSH_NUM_CHANNELS]; ++ struct ssam_nf notif; ++ } event; ++}; ++ ++ ++/* -- Main SSAM device structures. ------------------------------------------ */ ++ ++enum ssam_controller_state { ++ SSAM_CONTROLLER_UNINITIALIZED, ++ SSAM_CONTROLLER_INITIALIZED, ++ SSAM_CONTROLLER_STARTED, ++ SSAM_CONTROLLER_STOPPED, ++ SSAM_CONTROLLER_SUSPENDED, ++}; ++ ++struct ssam_device_caps { ++ u32 notif_display:1; ++ u32 notif_d0exit:1; ++}; ++ ++struct ssam_controller { ++ struct kref kref; ++ ++ struct rw_semaphore lock; ++ enum ssam_controller_state state; ++ ++ struct ssh_rtl rtl; ++ struct ssam_cplt cplt; ++ ++ struct { ++ struct ssh_seq_counter seq; ++ struct ssh_rqid_counter rqid; ++ } counter; ++ ++ struct { ++ int num; ++ bool wakeup_enabled; ++ } irq; ++ ++ struct ssam_device_caps caps; ++}; ++ ++#define to_ssam_controller(ptr, member) \ ++ container_of(ptr, struct ssam_controller, member) ++ ++#define ssam_dbg(ctrl, fmt, ...) rtl_dbg(&(ctrl)->rtl, fmt, ##__VA_ARGS__) ++#define ssam_info(ctrl, fmt, ...) rtl_info(&(ctrl)->rtl, fmt, ##__VA_ARGS__) ++#define ssam_warn(ctrl, fmt, ...) rtl_warn(&(ctrl)->rtl, fmt, ##__VA_ARGS__) ++#define ssam_err(ctrl, fmt, ...) rtl_err(&(ctrl)->rtl, fmt, ##__VA_ARGS__) ++ ++ ++static inline ++int ssam_controller_receive_buf(struct ssam_controller *ctrl, ++ const unsigned char *buf, size_t n) ++{ ++ return ssh_ptl_rx_rcvbuf(&ctrl->rtl.ptl, buf, n); ++} ++ ++static inline void ssam_controller_write_wakeup(struct ssam_controller *ctrl) ++{ ++ ssh_ptl_tx_wakeup(&ctrl->rtl.ptl, true); ++} ++ ++ ++int ssam_controller_init(struct ssam_controller *ctrl, struct serdev_device *s); ++int ssam_controller_start(struct ssam_controller *ctrl); ++void ssam_controller_shutdown(struct ssam_controller *ctrl); ++void ssam_controller_destroy(struct ssam_controller *ctrl); ++ ++int ssam_irq_setup(struct ssam_controller *ctrl); ++void ssam_irq_free(struct ssam_controller *ctrl); ++ ++void ssam_controller_lock(struct ssam_controller *c); ++void ssam_controller_unlock(struct ssam_controller *c); ++ ++int ssam_log_firmware_version(struct ssam_controller *ctrl); ++int ssam_ctrl_notif_display_off(struct ssam_controller *ctrl); ++int ssam_ctrl_notif_display_on(struct ssam_controller *ctrl); ++int ssam_ctrl_notif_d0_exit(struct ssam_controller *ctrl); ++int ssam_ctrl_notif_d0_entry(struct ssam_controller *ctrl); ++ ++int ssam_controller_suspend(struct ssam_controller *ctrl); ++int ssam_controller_resume(struct ssam_controller *ctrl); ++ ++int ssam_event_item_cache_init(void); ++void ssam_event_item_cache_destroy(void); ++ ++#endif /* _SSAM_CONTROLLER_H */ +diff --git a/drivers/misc/surface_sam/core.c b/drivers/misc/surface_sam/core.c +new file mode 100644 +index 0000000000000..f8685e5d2bf36 +--- /dev/null ++++ b/drivers/misc/surface_sam/core.c +@@ -0,0 +1,554 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Surface Serial Hub (SSH) driver for communication with the Surface/System ++ * Aggregator Module. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++ ++#include "bus.h" ++#include "controller.h" ++ ++#define CREATE_TRACE_POINTS ++#include "ssam_trace.h" + + +/* -- Glue layer (serdev_device -> ssam_controller). ------------------------ */ @@ -10850,12 +8301,51 @@ index 0000000000000..3f39f383e3651 + surface_sam_ssh_resume); + + -+/* -- Device/driver setup. -------------------------------------------------- */ ++/* -- Static controller reference. ------------------------------------------ */ ++ ++static struct ssam_controller *__ssam_controller = NULL; ++static DEFINE_SPINLOCK(__ssam_controller_lock); ++ ++struct ssam_controller *ssam_get_controller(void) ++{ ++ struct ssam_controller *ctrl; ++ ++ spin_lock(&__ssam_controller_lock); ++ ++ ctrl = __ssam_controller; ++ if (!ctrl) ++ goto out; ++ ++ if (WARN_ON(!kref_get_unless_zero(&ctrl->kref))) ++ ctrl = NULL; ++ ++out: ++ spin_unlock(&__ssam_controller_lock); ++ return ctrl; ++} ++EXPORT_SYMBOL_GPL(ssam_get_controller); ++ ++static int ssam_try_set_controller(struct ssam_controller *ctrl) ++{ ++ int status = 0; ++ ++ spin_lock(&__ssam_controller_lock); ++ if (!__ssam_controller) ++ __ssam_controller = ctrl; ++ else ++ status = -EBUSY; ++ spin_unlock(&__ssam_controller_lock); ++ ++ return status; ++} ++ ++static void ssam_clear_controller(void) ++{ ++ spin_lock(&__ssam_controller_lock); ++ __ssam_controller = NULL; ++ spin_unlock(&__ssam_controller_lock); ++} + -+static struct ssam_controller ssam_controller = { -+ .state = SSAM_CONTROLLER_UNINITIALIZED, -+}; -+static DEFINE_MUTEX(ssam_controller_lock); + +static int __ssam_client_link(struct ssam_controller *c, struct device *client) +{ @@ -10884,14 +8374,37 @@ index 0000000000000..3f39f383e3651 + return 0; +} + -+int ssam_client_bind(struct device *client, struct ssam_controller **ctrl) ++int ssam_client_link(struct ssam_controller *ctrl, struct device *client) +{ -+ struct ssam_controller *c = &ssam_controller; + int status; + -+ mutex_lock(&ssam_controller_lock); -+ status = __ssam_client_link(c, client); -+ mutex_unlock(&ssam_controller_lock); ++ ssam_controller_statelock(ctrl); ++ status = __ssam_client_link(ctrl, client); ++ ssam_controller_stateunlock(ctrl); ++ ++ return status; ++} ++EXPORT_SYMBOL_GPL(ssam_client_link); ++ ++int ssam_client_bind(struct device *client, struct ssam_controller **ctrl) ++{ ++ struct ssam_controller *c; ++ int status; ++ ++ c = ssam_get_controller(); ++ if (!c) ++ return -ENXIO; ++ ++ status = ssam_client_link(c, client); ++ ++ /* ++ * Note that we can drop our controller reference in both success and ++ * failure cases: On success, we have bound the controller lifetime ++ * inherently to the client driver lifetime, i.e. it the controller is ++ * now guaranteed to outlive the client driver. On failure, we're not ++ * going to use the controller any more. ++ */ ++ ssam_controller_put(c); + + *ctrl = status == 0 ? c : NULL; + return status; @@ -10899,9 +8412,20 @@ index 0000000000000..3f39f383e3651 +EXPORT_SYMBOL_GPL(ssam_client_bind); + + ++/* -- Device/driver setup. -------------------------------------------------- */ ++ ++static const struct acpi_gpio_params gpio_ssam_wakeup_int = { 0, 0, false }; ++static const struct acpi_gpio_params gpio_ssam_wakeup = { 1, 0, false }; ++ ++static const struct acpi_gpio_mapping ssam_acpi_gpios[] = { ++ { "ssam_wakeup-int-gpio", &gpio_ssam_wakeup_int, 1 }, ++ { "ssam_wakeup-gpio", &gpio_ssam_wakeup, 1 }, ++ { }, ++}; ++ +static int surface_sam_ssh_probe(struct serdev_device *serdev) +{ -+ struct ssam_controller *ctrl = &ssam_controller; ++ struct ssam_controller *ctrl; + acpi_handle *ssh = ACPI_HANDLE(&serdev->dev); + int status; + @@ -10912,8 +8436,10 @@ index 0000000000000..3f39f383e3651 + if (status) + return status; + -+ // set up EC -+ mutex_lock(&ssam_controller_lock); ++ // allocate controller ++ ctrl = kzalloc(sizeof(struct ssam_controller), GFP_KERNEL); ++ if (!ctrl) ++ return -ENOMEM; + + // initialize controller + status = ssam_controller_init(ctrl, serdev); @@ -10954,7 +8480,10 @@ index 0000000000000..3f39f383e3651 + if (status) + goto err_initrq; + -+ mutex_unlock(&ssam_controller_lock); ++ // finally, set main controller reference ++ status = ssam_try_set_controller(ctrl); ++ if (status) ++ goto err_initrq; + + /* + * TODO: The EC can wake up the system via the associated GPIO interrupt @@ -10977,9 +8506,9 @@ index 0000000000000..3f39f383e3651 + serdev_device_close(serdev); +err_devopen: + ssam_controller_destroy(ctrl); -+err_ctrl_init: + serdev_device_set_drvdata(serdev, NULL); -+ mutex_unlock(&ssam_controller_lock); ++err_ctrl_init: ++ kfree(ctrl); + return status; +} + @@ -10988,10 +8517,16 @@ index 0000000000000..3f39f383e3651 + struct ssam_controller *ctrl = serdev_device_get_drvdata(serdev); + int status; + -+ mutex_lock(&ssam_controller_lock); -+ ssam_irq_free(ctrl); ++ // clear static reference, so that no one else can get a new one ++ ssam_clear_controller(); + -+ // suspend EC and disable events ++ ssam_irq_free(ctrl); ++ ssam_controller_lock(ctrl); ++ ++ // remove all client devices ++ ssam_controller_remove_clients(ctrl); ++ ++ // act as if suspending to disable events + status = ssam_ctrl_notif_display_off(ctrl); + if (status) { + dev_err(&serdev->dev, "display-off notification failed: %d\n", @@ -11004,17 +8539,19 @@ index 0000000000000..3f39f383e3651 + status); + } + ++ // shut down controller and remove serdev device reference from it + ssam_controller_shutdown(ctrl); + + // shut down actual transport + serdev_device_wait_until_sent(serdev, 0); + serdev_device_close(serdev); + -+ ssam_controller_destroy(ctrl); ++ // drop our controller reference ++ ssam_controller_unlock(ctrl); ++ ssam_controller_put(ctrl); + + device_set_wakeup_capable(&serdev->dev, false); + serdev_device_set_drvdata(serdev, NULL); -+ mutex_unlock(&ssam_controller_lock); +} + + @@ -11043,6 +8580,10 @@ index 0000000000000..3f39f383e3651 +{ + int status; + ++ status = ssam_bus_register(); ++ if (status) ++ goto err_bus; ++ + status = ssh_ctrl_packet_cache_init(); + if (status) + goto err_cpkg; @@ -11062,6 +8603,8 @@ index 0000000000000..3f39f383e3651 +err_evitem: + ssh_ctrl_packet_cache_destroy(); +err_cpkg: ++ ssam_bus_unregister(); ++err_bus: + return status; +} + @@ -11070,6 +8613,7 @@ index 0000000000000..3f39f383e3651 + serdev_device_driver_unregister(&surface_sam_ssh); + ssam_event_item_cache_destroy(); + ssh_ctrl_packet_cache_destroy(); ++ ssam_bus_unregister(); +} + +/* @@ -11088,739 +8632,23 @@ index 0000000000000..3f39f383e3651 +MODULE_AUTHOR("Maximilian Luz "); +MODULE_DESCRIPTION("Surface Serial Hub Driver for 5th Generation Surface Devices"); +MODULE_LICENSE("GPL"); -diff --git a/drivers/platform/x86/surface_sam/surface_sam_ssh.h b/drivers/platform/x86/surface_sam/surface_sam_ssh.h +diff --git a/drivers/misc/surface_sam/ssam_trace.h b/drivers/misc/surface_sam/ssam_trace.h new file mode 100644 -index 0000000000000..473927699efd0 +index 0000000000000..b2a4507b3c9a6 --- /dev/null -+++ b/drivers/platform/x86/surface_sam/surface_sam_ssh.h -@@ -0,0 +1,711 @@ ++++ b/drivers/misc/surface_sam/ssam_trace.h +@@ -0,0 +1,588 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ -+/* -+ * Interface for Surface Serial Hub (SSH). -+ * -+ * The SSH is the main communication hub for communication between host and -+ * the Surface/System Aggregator Module (SAM) on newer Microsoft Surface -+ * devices (Book 2, Pro 5, Laptops, ...). Also referred to as SAM-over-SSH. -+ * Older devices (Book 1, Pro 4) use SAM-over-HID (via I2C). -+ */ + -+#ifndef _SURFACE_SAM_SSH_H -+#define _SURFACE_SAM_SSH_H -+ -+#include -+#include -+ -+ -+/* -- Data structures for SAM-over-SSH communication. ----------------------- */ -+ -+/** -+ * enum ssh_frame_type - Frame types for SSH frames. -+ * @SSH_FRAME_TYPE_DATA_SEQ: Indicates a data frame, followed by a payload with -+ * the length specified in the ssh_frame.len field. This -+ * frame is sequenced, meaning that an ACK is required. -+ * @SSH_FRAME_TYPE_DATA_NSQ: Same as SSH_FRAME_TYPE_DATA_SEQ, but unsequenced, -+ * meaning that the message does not have to be ACKed. -+ * @SSH_FRAME_TYPE_ACK: Indicates an ACK message. -+ * @SSH_FRAME_TYPE_NAK: Indicates an error response for previously sent -+ * frame. In general, this means that the frame and/or -+ * payload is malformed, e.g. a CRC is wrong. For command- -+ * type payloads, this can also mean that the command is -+ * invalid. -+ */ -+enum ssh_frame_type { -+ SSH_FRAME_TYPE_DATA_SEQ = 0x80, -+ SSH_FRAME_TYPE_DATA_NSQ = 0x00, -+ SSH_FRAME_TYPE_ACK = 0x40, -+ SSH_FRAME_TYPE_NAK = 0x04, -+}; -+ -+/** -+ * struct ssh_frame - SSH communication frame. -+ * @type: The type of the frame. See &enum ssh_frame_type. -+ * @len: The length of the frame payload directly following the CRC for this -+ * frame. Does not include the final CRC for that payload. -+ * @seq: The sequence number for this message/exchange. -+ */ -+struct ssh_frame { -+ u8 type; -+ __le16 len; -+ u8 seq; -+} __packed; -+ -+/* -+ * Maximum SSH frame payload length in bytes. This is the physical maximum -+ * length of the protocol. Implementations may set a more constrained limit. -+ */ -+#define SSH_FRAME_MAX_PAYLOAD_SIZE U16_MAX -+ -+/** -+ * enum ssh_payload_type - Type indicator for the SSH payload. -+ * @SSH_PLD_TYPE_CMD: The payload is a command structure with optional command -+ * payload. -+ */ -+enum ssh_payload_type { -+ SSH_PLD_TYPE_CMD = 0x80, -+}; -+ -+/** -+ * struct ssh_command - Payload of a command-type frame. -+ * @type: The type of the payload. See &enum ssh_payload_type. Should be -+ * SSH_PLD_TYPE_CMD for this struct. -+ * @tc: Command target category. -+ * @chn_out: Output channel. Should be zero if this an incoming (EC to host) -+ * message. -+ * @chn_in: Input channel. Should be zero if this is an outgoing (hos to EC) -+ * message. -+ * @iid: Instance ID. -+ * @rqid: Request ID. Used to match requests with responses and differentiate -+ * between responses and events. -+ * @cid: Command ID. -+ */ -+struct ssh_command { -+ u8 type; -+ u8 tc; -+ u8 chn_out; -+ u8 chn_in; -+ u8 iid; -+ __le16 rqid; -+ u8 cid; -+} __packed; -+ -+/* -+ * Maximum SSH command payload length in bytes. This is the physical maximum -+ * length of the protocol. Implementations may set a more constrained limit. -+ */ -+#define SSH_COMMAND_MAX_PAYLOAD_SIZE \ -+ (SSH_FRAME_MAX_PAYLOAD_SIZE - sizeof(struct ssh_command)) -+ -+/** -+ * struct ssh_notification_params - Command payload to enable/disable SSH -+ * notifications. -+ * @target_category: The target category for which notifications should be -+ * enabled/disabled. -+ * @flags: Flags determining how notifications are being sent. -+ * @request_id: The request ID that is used to send these notifications. -+ * @instance_id: The specific instance in the given target category for -+ * which notifications should be enabled. -+ */ -+struct ssh_notification_params { -+ u8 target_category; -+ u8 flags; -+ __le16 request_id; -+ u8 instance_id; -+} __packed; -+ -+/** -+ * SSH message syncrhonization (SYN) bytes. -+ */ -+#define SSH_MSG_SYN ((u16)0x55aa) -+ -+/** -+ * Base-length of a SSH message. This is the minimum number of bytes required -+ * to form a message. The actual message length is SSH_MSG_LEN_BASE plus the -+ * length of the frame payload. -+ */ -+#define SSH_MSG_LEN_BASE (sizeof(struct ssh_frame) + 3ull * sizeof(u16)) -+ -+/** -+ * Length of a SSH control message. -+ */ -+#define SSH_MSG_LEN_CTRL SSH_MSG_LEN_BASE -+ -+/** -+ * Length of a SSH message with payload of specified size. -+ */ -+#define SSH_MESSAGE_LENGTH(payload_size) (SSH_MSG_LEN_BASE + payload_size) -+ -+/** -+ * Length of a SSH command message with command payload of specified size. -+ */ -+#define SSH_COMMAND_MESSAGE_LENGTH(payload_size) \ -+ SSH_MESSAGE_LENGTH(sizeof(struct ssh_command) + payload_size) -+ -+/** -+ * Offset of the specified struct ssh_frame field in the raw SSH message data. -+ */ -+#define SSH_MSGOFFSET_FRAME(field) \ -+ (sizeof(u16) + offsetof(struct ssh_frame, field)) -+ -+/** -+ * Offset of the specified struct ssh_command field in the raw SSH message data. -+ */ -+#define SSH_MSGOFFSET_COMMAND(field) \ -+ (2ull * sizeof(u16) + sizeof(struct ssh_frame) \ -+ + offsetof(struct ssh_command, field)) -+ -+/** -+ * struct ssam_span - reference to a buffer region -+ * @ptr: pointer to the buffer region -+ * @len: length of the buffer region -+ * -+ * A reference to a (non-owned) buffer segment, consisting of pointer and -+ * length. Use of this struct indicates non-owned data, i.e. data of which the -+ * life-time is managed (i.e. it is allocated/freed) via another pointer. -+ */ -+struct ssam_span { -+ u8 *ptr; -+ size_t len; -+}; -+ -+ -+/* -- Packet transport layer (ptl). ----------------------------------------- */ -+ -+enum ssh_packet_priority { -+ SSH_PACKET_PRIORITY_FLUSH = 0, -+ SSH_PACKET_PRIORITY_DATA = 0, -+ SSH_PACKET_PRIORITY_NAK = 1 << 4, -+ SSH_PACKET_PRIORITY_ACK = 2 << 4, -+}; -+ -+#define SSH_PACKET_PRIORITY(base, try) \ -+ ((SSH_PACKET_PRIORITY_##base) | ((try) & 0x0f)) -+ -+#define ssh_packet_priority_get_try(p) ((p) & 0x0f) -+ -+ -+enum ssh_packet_flags { -+ SSH_PACKET_SF_LOCKED_BIT, -+ SSH_PACKET_SF_QUEUED_BIT, -+ SSH_PACKET_SF_PENDING_BIT, -+ SSH_PACKET_SF_TRANSMITTING_BIT, -+ SSH_PACKET_SF_TRANSMITTED_BIT, -+ SSH_PACKET_SF_ACKED_BIT, -+ SSH_PACKET_SF_CANCELED_BIT, -+ SSH_PACKET_SF_COMPLETED_BIT, -+ -+ SSH_PACKET_TY_FLUSH_BIT, -+ SSH_PACKET_TY_SEQUENCED_BIT, -+ SSH_PACKET_TY_BLOCKING_BIT, -+ -+ SSH_PACKET_FLAGS_SF_MASK = -+ BIT(SSH_PACKET_SF_LOCKED_BIT) -+ | BIT(SSH_PACKET_SF_QUEUED_BIT) -+ | BIT(SSH_PACKET_SF_PENDING_BIT) -+ | BIT(SSH_PACKET_SF_TRANSMITTING_BIT) -+ | BIT(SSH_PACKET_SF_TRANSMITTED_BIT) -+ | BIT(SSH_PACKET_SF_ACKED_BIT) -+ | BIT(SSH_PACKET_SF_CANCELED_BIT) -+ | BIT(SSH_PACKET_SF_COMPLETED_BIT), -+ -+ SSH_PACKET_FLAGS_TY_MASK = -+ BIT(SSH_PACKET_TY_FLUSH_BIT) -+ | BIT(SSH_PACKET_TY_SEQUENCED_BIT) -+ | BIT(SSH_PACKET_TY_BLOCKING_BIT), -+}; -+ -+ -+struct ssh_ptl; -+struct ssh_packet; -+ -+struct ssh_packet_ops { -+ void (*release)(struct ssh_packet *p); -+ void (*complete)(struct ssh_packet *p, int status); -+}; -+ -+struct ssh_packet { -+ struct ssh_ptl *ptl; -+ struct kref refcnt; -+ -+ u8 priority; -+ -+ struct { -+ size_t len; -+ u8 *ptr; -+ } data; -+ -+ unsigned long state; -+ ktime_t timestamp; -+ -+ struct list_head queue_node; -+ struct list_head pending_node; -+ -+ const struct ssh_packet_ops *ops; -+}; -+ -+ -+void ssh_packet_get(struct ssh_packet *p); -+void ssh_packet_put(struct ssh_packet *p); -+ -+static inline void ssh_packet_set_data(struct ssh_packet *p, u8 *ptr, size_t len) -+{ -+ p->data.ptr = ptr; -+ p->data.len = len; -+} -+ -+ -+/* -- Request transport layer (rtl). ---------------------------------------- */ -+ -+enum ssh_request_flags { -+ SSH_REQUEST_SF_LOCKED_BIT, -+ SSH_REQUEST_SF_QUEUED_BIT, -+ SSH_REQUEST_SF_PENDING_BIT, -+ SSH_REQUEST_SF_TRANSMITTING_BIT, -+ SSH_REQUEST_SF_TRANSMITTED_BIT, -+ SSH_REQUEST_SF_RSPRCVD_BIT, -+ SSH_REQUEST_SF_CANCELED_BIT, -+ SSH_REQUEST_SF_COMPLETED_BIT, -+ -+ SSH_REQUEST_TY_FLUSH_BIT, -+ SSH_REQUEST_TY_HAS_RESPONSE_BIT, -+ -+ SSH_REQUEST_FLAGS_SF_MASK = -+ BIT(SSH_REQUEST_SF_LOCKED_BIT) -+ | BIT(SSH_REQUEST_SF_QUEUED_BIT) -+ | BIT(SSH_REQUEST_SF_PENDING_BIT) -+ | BIT(SSH_REQUEST_SF_TRANSMITTING_BIT) -+ | BIT(SSH_REQUEST_SF_TRANSMITTED_BIT) -+ | BIT(SSH_REQUEST_SF_RSPRCVD_BIT) -+ | BIT(SSH_REQUEST_SF_CANCELED_BIT) -+ | BIT(SSH_REQUEST_SF_COMPLETED_BIT), -+ -+ SSH_REQUEST_FLAGS_TY_MASK = -+ BIT(SSH_REQUEST_TY_FLUSH_BIT) -+ | BIT(SSH_REQUEST_TY_HAS_RESPONSE_BIT), -+}; -+ -+ -+struct ssh_rtl; -+struct ssh_request; -+ -+struct ssh_request_ops { -+ void (*release)(struct ssh_request *rqst); -+ void (*complete)(struct ssh_request *rqst, -+ const struct ssh_command *cmd, -+ const struct ssam_span *data, int status); -+}; -+ -+struct ssh_request { -+ struct ssh_packet packet; -+ struct list_head node; -+ -+ unsigned long state; -+ ktime_t timestamp; -+ -+ const struct ssh_request_ops *ops; -+}; -+ -+ -+static inline void ssh_request_get(struct ssh_request *r) -+{ -+ ssh_packet_get(&r->packet); -+} -+ -+static inline void ssh_request_put(struct ssh_request *r) -+{ -+ ssh_packet_put(&r->packet); -+} -+ -+static inline void ssh_request_set_data(struct ssh_request *r, u8 *ptr, size_t len) -+{ -+ ssh_packet_set_data(&r->packet, ptr, len); -+} -+ -+ -+/* -- Main data types and definitions --------------------------------------- */ -+ -+enum ssam_ssh_tc { -+ SSAM_SSH_TC_SAM = 0x01, // generic system functionality, real-time clock -+ SSAM_SSH_TC_BAT = 0x02, // battery/power subsystem -+ SSAM_SSH_TC_TMP = 0x03, // thermal subsystem -+ SSAM_SSH_TC_PMC = 0x04, -+ SSAM_SSH_TC_FAN = 0x05, -+ SSAM_SSH_TC_PoM = 0x06, -+ SSAM_SSH_TC_DBG = 0x07, -+ SSAM_SSH_TC_KBD = 0x08, // legacy keyboard (Laptop 1/2) -+ SSAM_SSH_TC_FWU = 0x09, -+ SSAM_SSH_TC_UNI = 0x0a, -+ SSAM_SSH_TC_LPC = 0x0b, -+ SSAM_SSH_TC_TCL = 0x0c, -+ SSAM_SSH_TC_SFL = 0x0d, -+ SSAM_SSH_TC_KIP = 0x0e, -+ SSAM_SSH_TC_EXT = 0x0f, -+ SSAM_SSH_TC_BLD = 0x10, -+ SSAM_SSH_TC_BAS = 0x11, // detachment system (Surface Book 2/3) -+ SSAM_SSH_TC_SEN = 0x12, -+ SSAM_SSH_TC_SRQ = 0x13, -+ SSAM_SSH_TC_MCU = 0x14, -+ SSAM_SSH_TC_HID = 0x15, // generic HID input subsystem -+ SSAM_SSH_TC_TCH = 0x16, -+ SSAM_SSH_TC_BKL = 0x17, -+ SSAM_SSH_TC_TAM = 0x18, -+ SSAM_SSH_TC_ACC = 0x19, -+ SSAM_SSH_TC_UFI = 0x1a, -+ SSAM_SSH_TC_USC = 0x1b, -+ SSAM_SSH_TC_PEN = 0x1c, -+ SSAM_SSH_TC_VID = 0x1d, -+ SSAM_SSH_TC_AUD = 0x1e, -+ SSAM_SSH_TC_SMC = 0x1f, -+ SSAM_SSH_TC_KPD = 0x20, -+ SSAM_SSH_TC_REG = 0x21, -+}; -+ -+struct ssam_controller; -+ -+/** -+ * struct ssam_event_flags - Flags for enabling/disabling SAM-over-SSH events -+ * @SSAM_EVENT_SEQUENCED: The event will be sent via a sequenced data frame. -+ */ -+enum ssam_event_flags { -+ SSAM_EVENT_SEQUENCED = BIT(0), -+}; -+ -+struct ssam_event { -+ u8 target_category; -+ u8 command_id; -+ u8 instance_id; -+ u8 channel; -+ u16 length; -+ u8 data[0]; -+}; -+ -+enum ssam_request_flags { -+ SSAM_REQUEST_HAS_RESPONSE = BIT(0), -+ SSAM_REQUEST_UNSEQUENCED = BIT(1), -+}; -+ -+struct ssam_request { -+ u8 target_category; -+ u8 command_id; -+ u8 instance_id; -+ u8 channel; -+ u16 flags; -+ u16 length; -+ const u8 *payload; -+}; -+ -+struct ssam_response { -+ size_t capacity; -+ size_t length; -+ u8 *pointer; -+}; -+ -+ -+int ssam_client_bind(struct device *client, struct ssam_controller **ctrl); -+ -+struct device *ssam_controller_device(struct ssam_controller *c); -+ -+ssize_t ssam_request_write_data(struct ssam_span *buf, -+ struct ssam_controller *ctrl, -+ struct ssam_request *spec); -+ -+ -+/* -- Synchronous request interface. ---------------------------------------- */ -+ -+struct ssam_request_sync { -+ struct ssh_request base; -+ struct completion comp; -+ struct ssam_response *resp; -+ int status; -+}; -+ -+int ssam_request_sync_alloc(size_t payload_len, gfp_t flags, -+ struct ssam_request_sync **rqst, -+ struct ssam_span *buffer); -+ -+void ssam_request_sync_init(struct ssam_request_sync *rqst, -+ enum ssam_request_flags flags); -+ -+static inline void ssam_request_sync_set_data(struct ssam_request_sync *rqst, -+ u8 *ptr, size_t len) -+{ -+ ssh_request_set_data(&rqst->base, ptr, len); -+} -+ -+static inline void ssam_request_sync_set_resp(struct ssam_request_sync *rqst, -+ struct ssam_response *resp) -+{ -+ rqst->resp = resp; -+} -+ -+int ssam_request_sync_submit(struct ssam_controller *ctrl, -+ struct ssam_request_sync *rqst); -+ -+static inline int ssam_request_sync_wait(struct ssam_request_sync *rqst) -+{ -+ wait_for_completion(&rqst->comp); -+ return rqst->status; -+} -+ -+int ssam_request_sync(struct ssam_controller *ctrl, struct ssam_request *spec, -+ struct ssam_response *rsp); -+ -+int ssam_request_sync_with_buffer(struct ssam_controller *ctrl, -+ struct ssam_request *spec, -+ struct ssam_response *rsp, -+ struct ssam_span *buf); -+ -+ -+#define ssam_request_sync_onstack(ctrl, rqst, rsp, payload_len) \ -+ ({ \ -+ u8 __data[SSH_COMMAND_MESSAGE_LENGTH(payload_len)]; \ -+ struct ssam_span __buf = { &__data[0], ARRAY_SIZE(__data) }; \ -+ int __status; \ -+ \ -+ /* ensure input does not overflow buffer */ \ -+ if ((rqst)->length <= payload_len) { \ -+ __status = ssam_request_sync_with_buffer( \ -+ ctrl, rqst, rsp, &__buf); \ -+ } else { \ -+ __status = -EINVAL; \ -+ } \ -+ \ -+ __status; \ -+ }) -+ -+ -+struct ssam_request_spec { -+ u8 target_category; -+ u8 command_id; -+ u8 instance_id; -+ u8 channel; -+ u8 flags; -+}; -+ -+struct ssam_request_spec_md { -+ u8 target_category; -+ u8 command_id; -+ u8 flags; -+}; -+ -+#define SSAM_DEFINE_SYNC_REQUEST_N(name, spec...) \ -+ int name(struct ssam_controller *ctrl) \ -+ { \ -+ struct ssam_request_spec s = (struct ssam_request_spec)spec; \ -+ struct ssam_request rqst; \ -+ \ -+ rqst.target_category = s.target_category; \ -+ rqst.command_id = s.command_id; \ -+ rqst.instance_id = s.instance_id; \ -+ rqst.channel = s.channel; \ -+ rqst.flags = s.flags; \ -+ rqst.length = 0; \ -+ rqst.payload = NULL; \ -+ \ -+ return ssam_request_sync_onstack(ctrl, &rqst, NULL, 0); \ -+ } -+ -+#define SSAM_DEFINE_SYNC_REQUEST_W(name, wtype, spec...) \ -+ int name(struct ssam_controller *ctrl, const wtype *in) \ -+ { \ -+ struct ssam_request_spec s = (struct ssam_request_spec)spec; \ -+ struct ssam_request rqst; \ -+ \ -+ rqst.target_category = s.target_category; \ -+ rqst.command_id = s.command_id; \ -+ rqst.instance_id = s.instance_id; \ -+ rqst.channel = s.channel; \ -+ rqst.flags = s.flags; \ -+ rqst.length = sizeof(wtype); \ -+ rqst.payload = (u8 *)in; \ -+ \ -+ return ssam_request_sync_onstack(ctrl, &rqst, NULL, \ -+ sizeof(wtype)); \ -+ } -+ -+#define SSAM_DEFINE_SYNC_REQUEST_R(name, rtype, spec...) \ -+ int name(struct ssam_controller *ctrl, rtype *out) \ -+ { \ -+ struct ssam_request_spec s = (struct ssam_request_spec)spec; \ -+ struct ssam_request rqst; \ -+ struct ssam_response rsp; \ -+ int status; \ -+ \ -+ rqst.target_category = s.target_category; \ -+ rqst.command_id = s.command_id; \ -+ rqst.instance_id = s.instance_id; \ -+ rqst.channel = s.channel; \ -+ rqst.flags = s.flags | SSAM_REQUEST_HAS_RESPONSE; \ -+ rqst.length = 0; \ -+ rqst.payload = NULL; \ -+ \ -+ rsp.capacity = sizeof(rtype); \ -+ rsp.length = 0; \ -+ rsp.pointer = (u8 *)out; \ -+ \ -+ status = ssam_request_sync_onstack(ctrl, &rqst, &rsp, 0); \ -+ if (status) \ -+ return status; \ -+ \ -+ if (rsp.length != sizeof(rtype)) { \ -+ struct device *dev = ssam_controller_device(ctrl); \ -+ dev_err(dev, "rqst: invalid response length, expected %zu, got %zu" \ -+ " (tc: 0x%02x, cid: 0x%02x)", sizeof(rtype), \ -+ rsp.length, rqst.target_category, \ -+ rqst.command_id); \ -+ return -EIO; \ -+ } \ -+ \ -+ return 0; \ -+ } -+ -+#define SSAM_DEFINE_SYNC_REQUEST_MD_W(name, wtype, spec...) \ -+ int name(struct ssam_controller *ctrl, u8 chn, u8 iid, const wtype *in) \ -+ { \ -+ struct ssam_request_spec_md s \ -+ = (struct ssam_request_spec_md)spec; \ -+ struct ssam_request rqst; \ -+ \ -+ rqst.target_category = s.target_category; \ -+ rqst.command_id = s.command_id; \ -+ rqst.instance_id = iid; \ -+ rqst.channel = chn; \ -+ rqst.flags = s.flags; \ -+ rqst.length = sizeof(wtype); \ -+ rqst.payload = (u8 *)in; \ -+ \ -+ return ssam_request_sync_onstack(ctrl, &rqst, NULL, \ -+ sizeof(wtype)); \ -+ } -+ -+#define SSAM_DEFINE_SYNC_REQUEST_MD_R(name, rtype, spec...) \ -+ int name(struct ssam_controller *ctrl, u8 chn, u8 iid, rtype *out) \ -+ { \ -+ struct ssam_request_spec_md s \ -+ = (struct ssam_request_spec_md)spec; \ -+ struct ssam_request rqst; \ -+ struct ssam_response rsp; \ -+ int status; \ -+ \ -+ rqst.target_category = s.target_category; \ -+ rqst.command_id = s.command_id; \ -+ rqst.instance_id = iid; \ -+ rqst.channel = chn; \ -+ rqst.flags = s.flags | SSAM_REQUEST_HAS_RESPONSE; \ -+ rqst.length = 0; \ -+ rqst.payload = NULL; \ -+ \ -+ rsp.capacity = sizeof(rtype); \ -+ rsp.length = 0; \ -+ rsp.pointer = (u8 *)out; \ -+ \ -+ status = ssam_request_sync_onstack(ctrl, &rqst, &rsp, 0); \ -+ if (status) \ -+ return status; \ -+ \ -+ if (rsp.length != sizeof(rtype)) { \ -+ struct device *dev = ssam_controller_device(ctrl); \ -+ dev_err(dev, "rqst: invalid response length, expected %zu, got %zu" \ -+ " (tc: 0x%02x, cid: 0x%02x)", sizeof(rtype), \ -+ rsp.length, rqst.target_category, \ -+ rqst.command_id); \ -+ return -EIO; \ -+ } \ -+ \ -+ return 0; \ -+ } -+ -+ -+/* -- Event notifier/callbacks. --------------------------------------------- */ -+ -+#define SSAM_NOTIF_STATE_SHIFT 2 -+#define SSAM_NOTIF_STATE_MASK ((1 << SSAM_NOTIF_STATE_SHIFT) - 1) -+ -+#define SSAM_NOTIF_HANDLED BIT(0) -+#define SSAM_NOTIF_STOP BIT(1) -+ -+ -+struct ssam_notifier_block; -+ -+typedef u32 (*ssam_notifier_fn_t)(struct ssam_notifier_block *nb, -+ const struct ssam_event *event); -+ -+struct ssam_notifier_block { -+ struct ssam_notifier_block __rcu *next; -+ ssam_notifier_fn_t fn; -+ int priority; -+}; -+ -+ -+static inline u32 ssam_notifier_from_errno(int err) -+{ -+ if (WARN_ON(err > 0) || err == 0) -+ return 0; -+ else -+ return ((-err) << SSAM_NOTIF_STATE_SHIFT) | SSAM_NOTIF_STOP; -+} -+ -+static inline int ssam_notifier_to_errno(u32 ret) -+{ -+ return -(ret >> SSAM_NOTIF_STATE_SHIFT); -+} -+ -+ -+/* -- Event/notification registry. ------------------------------------------ */ -+ -+struct ssam_event_registry { -+ u8 target_category; -+ u8 channel; -+ u8 cid_enable; -+ u8 cid_disable; -+}; -+ -+struct ssam_event_id { -+ u8 target_category; -+ u8 instance; -+}; -+ -+ -+#define SSAM_EVENT_REGISTRY(tc, chn, cid_en, cid_dis) \ -+ ((struct ssam_event_registry) { \ -+ .target_category = (tc), \ -+ .channel = (chn), \ -+ .cid_enable = (cid_en), \ -+ .cid_disable = (cid_dis), \ -+ }) -+ -+#define SSAM_EVENT_ID(tc, iid) \ -+ ((struct ssam_event_id) { \ -+ .target_category = tc, \ -+ .instance = iid, \ -+ }) -+ -+ -+#define SSAM_EVENT_REGISTRY_SAM \ -+ SSAM_EVENT_REGISTRY(SSAM_SSH_TC_SAM, 0x01, 0x0b, 0x0c) -+ -+#define SSAM_EVENT_REGISTRY_KIP \ -+ SSAM_EVENT_REGISTRY(SSAM_SSH_TC_KIP, 0x02, 0x27, 0x28) -+ -+#define SSAM_EVENT_REGISTRY_REG \ -+ SSAM_EVENT_REGISTRY(SSAM_SSH_TC_REG, 0x02, 0x01, 0x02) -+ -+ -+struct ssam_event_notifier { -+ struct ssam_notifier_block base; -+ -+ struct { -+ struct ssam_event_registry reg; -+ struct ssam_event_id id; -+ u8 flags; -+ } event; -+}; -+ -+int ssam_notifier_register(struct ssam_controller *ctrl, -+ struct ssam_event_notifier *n); -+ -+int ssam_notifier_unregister(struct ssam_controller *ctrl, -+ struct ssam_event_notifier *n); -+ -+#endif /* _SURFACE_SAM_SSH_H */ -diff --git a/drivers/platform/x86/surface_sam/surface_sam_ssh_trace.h b/drivers/platform/x86/surface_sam/surface_sam_ssh_trace.h -new file mode 100644 -index 0000000000000..8ea9a2fc99d7e ---- /dev/null -+++ b/drivers/platform/x86/surface_sam/surface_sam_ssh_trace.h -@@ -0,0 +1,587 @@ +#undef TRACE_SYSTEM +#define TRACE_SYSTEM surface_sam_ssh + +#if !defined(_SURFACE_SAM_SSH_TRACE_H) || defined(TRACE_HEADER_MULTI_READ) +#define _SURFACE_SAM_SSH_TRACE_H + ++#include +#include + -+#include "surface_sam_ssh.h" -+ + +TRACE_DEFINE_ENUM(SSH_FRAME_TYPE_DATA_SEQ); +TRACE_DEFINE_ENUM(SSH_FRAME_TYPE_DATA_NSQ); @@ -12395,283 +9223,3514 @@ index 0000000000000..8ea9a2fc99d7e +#undef TRACE_INCLUDE_FILE + +#define TRACE_INCLUDE_PATH . -+#define TRACE_INCLUDE_FILE surface_sam_ssh_trace ++#define TRACE_INCLUDE_FILE ssam_trace + +#include -diff --git a/drivers/platform/x86/surface_sam/surface_sam_vhf.c b/drivers/platform/x86/surface_sam/surface_sam_vhf.c +diff --git a/drivers/misc/surface_sam/ssh_msgb.h b/drivers/misc/surface_sam/ssh_msgb.h new file mode 100644 -index 0000000000000..8455f952c2724 +index 0000000000000..69b13dbdf939a --- /dev/null -+++ b/drivers/platform/x86/surface_sam/surface_sam_vhf.c -@@ -0,0 +1,266 @@ -+// SPDX-License-Identifier: GPL-2.0-or-later -+/* -+ * Virtual HID Framework (VHF) driver for input events via SAM. -+ * Used for keyboard input events on the Surface Laptops. -+ */ ++++ b/drivers/misc/surface_sam/ssh_msgb.h +@@ -0,0 +1,132 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ + -+#include -+#include -+#include -+#include ++#ifndef _SSAM_SSH_MSGB_H ++#define _SSAM_SSH_MSGB_H ++ ++#include +#include + -+#include "surface_sam_ssh.h" ++#include ++#include "ssh_protocol.h" + + -+#define USB_VENDOR_ID_MICROSOFT 0x045e -+#define USB_DEVICE_ID_MS_VHF 0xf001 -+ -+#define VHF_INPUT_NAME "Microsoft Virtual HID Framework Device" -+ -+ -+struct vhf_drvdata { -+ struct platform_device *dev; -+ struct ssam_controller *ctrl; -+ -+ struct ssam_event_notifier notif; -+ -+ struct hid_device *hid; ++struct msgbuf { ++ u8 *begin; ++ u8 *end; ++ u8 *ptr; +}; + ++static inline void msgb_init(struct msgbuf *msgb, u8 *ptr, size_t cap) ++{ ++ msgb->begin = ptr; ++ msgb->end = ptr + cap; ++ msgb->ptr = ptr; ++} ++ ++static inline size_t msgb_bytes_used(const struct msgbuf *msgb) ++{ ++ return msgb->ptr - msgb->begin; ++} ++ ++static inline void msgb_push_u16(struct msgbuf *msgb, u16 value) ++{ ++ if (WARN_ON(msgb->ptr + sizeof(u16) > msgb->end)) ++ return; ++ ++ put_unaligned_le16(value, msgb->ptr); ++ msgb->ptr += sizeof(u16); ++} ++ ++static inline void msgb_push_syn(struct msgbuf *msgb) ++{ ++ msgb_push_u16(msgb, SSH_MSG_SYN); ++} ++ ++static inline void msgb_push_buf(struct msgbuf *msgb, const u8 *buf, size_t len) ++{ ++ msgb->ptr = memcpy(msgb->ptr, buf, len) + len; ++} ++ ++static inline void msgb_push_crc(struct msgbuf *msgb, const u8 *buf, size_t len) ++{ ++ msgb_push_u16(msgb, ssh_crc(buf, len)); ++} ++ ++static inline void msgb_push_frame(struct msgbuf *msgb, u8 ty, u16 len, u8 seq) ++{ ++ struct ssh_frame *frame = (struct ssh_frame *)msgb->ptr; ++ const u8 *const begin = msgb->ptr; ++ ++ if (WARN_ON(msgb->ptr + sizeof(*frame) > msgb->end)) ++ return; ++ ++ frame->type = ty; ++ put_unaligned_le16(len, &frame->len); ++ frame->seq = seq; ++ ++ msgb->ptr += sizeof(*frame); ++ msgb_push_crc(msgb, begin, msgb->ptr - begin); ++} ++ ++static inline void msgb_push_ack(struct msgbuf *msgb, u8 seq) ++{ ++ // SYN ++ msgb_push_syn(msgb); ++ ++ // ACK-type frame + CRC ++ msgb_push_frame(msgb, SSH_FRAME_TYPE_ACK, 0x00, seq); ++ ++ // payload CRC (ACK-type frames do not have a payload) ++ msgb_push_crc(msgb, msgb->ptr, 0); ++} ++ ++static inline void msgb_push_nak(struct msgbuf *msgb) ++{ ++ // SYN ++ msgb_push_syn(msgb); ++ ++ // NAK-type frame + CRC ++ msgb_push_frame(msgb, SSH_FRAME_TYPE_NAK, 0x00, 0x00); ++ ++ // payload CRC (ACK-type frames do not have a payload) ++ msgb_push_crc(msgb, msgb->ptr, 0); ++} ++ ++static inline void msgb_push_cmd(struct msgbuf *msgb, u8 seq, u16 rqid, ++ const struct ssam_request *rqst) ++{ ++ struct ssh_command *cmd; ++ const u8 *cmd_begin; ++ const u8 type = SSH_FRAME_TYPE_DATA_SEQ; ++ ++ // SYN ++ msgb_push_syn(msgb); ++ ++ // command frame + crc ++ msgb_push_frame(msgb, type, sizeof(*cmd) + rqst->length, seq); ++ ++ // frame payload: command struct + payload ++ if (WARN_ON(msgb->ptr + sizeof(*cmd) > msgb->end)) ++ return; ++ ++ cmd_begin = msgb->ptr; ++ cmd = (struct ssh_command *)msgb->ptr; ++ ++ cmd->type = SSH_PLD_TYPE_CMD; ++ cmd->tc = rqst->target_category; ++ cmd->chn_out = rqst->channel; ++ cmd->chn_in = 0x00; ++ cmd->iid = rqst->instance_id; ++ put_unaligned_le16(rqid, &cmd->rqid); ++ cmd->cid = rqst->command_id; ++ ++ msgb->ptr += sizeof(*cmd); ++ ++ // command payload ++ msgb_push_buf(msgb, rqst->payload, rqst->length); ++ ++ // crc for command struct + payload ++ msgb_push_crc(msgb, cmd_begin, msgb->ptr - cmd_begin); ++} ++ ++#endif /* _SSAM_SSH_MSGB_H */ +diff --git a/drivers/misc/surface_sam/ssh_packet_layer.c b/drivers/misc/surface_sam/ssh_packet_layer.c +new file mode 100644 +index 0000000000000..d48297817cec1 +--- /dev/null ++++ b/drivers/misc/surface_sam/ssh_packet_layer.c +@@ -0,0 +1,1753 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++ ++#include "ssh_packet_layer.h" ++#include "ssh_protocol.h" ++ ++#include "ssam_trace.h" ++ + +/* -+ * These report descriptors have been extracted from a Surface Book 2. -+ * They seems to be similar enough to be usable on the Surface Laptop. ++ * To simplify reasoning about the code below, we define a few concepts. The ++ * system below is similar to a state-machine for packets, however, there are ++ * too many states to explicitly write them down. To (somewhat) manage the ++ * states and packets we rely on flags, reference counting, and some simple ++ * concepts. State transitions are triggered by actions. ++ * ++ * >> Actions << ++ * ++ * - submit ++ * - transmission start (process next item in queue) ++ * - transmission finished (guaranteed to never be parallel to transmission ++ * start) ++ * - ACK received ++ * - NAK received (this is equivalent to issuing re-submit for all pending ++ * packets) ++ * - timeout (this is equivalent to re-issuing a submit or canceling) ++ * - cancel (non-pending and pending) ++ * ++ * >> Data Structures, Packet Ownership, General Overview << ++ * ++ * The code below employs two main data structures: The packet queue, containing ++ * all packets scheduled for transmission, and the set of pending packets, ++ * containing all packets awaiting an ACK. ++ * ++ * Shared ownership of a packet is controlled via reference counting. Inside the ++ * transmission system are a total of five packet owners: ++ * ++ * - the packet queue, ++ * - the pending set, ++ * - the transmitter thread, ++ * - the receiver thread (via ACKing), and ++ * - the timeout work item. ++ * ++ * Normal operation is as follows: The initial reference of the packet is ++ * obtained by submitting the packet and queueing it. The receiver thread ++ * takes packets from the queue. By doing this, it does not increment the ++ * refcount but takes over the reference (removing it from the queue). ++ * If the packet is sequenced (i.e. needs to be ACKed by the client), the ++ * transmitter thread sets-up the timeout and adds the packet to the pending set ++ * before starting to transmit it. As the timeout is handled by a reaper task, ++ * no additional reference for it is needed. After the transmit is done, the ++ * reference hold by the transmitter thread is dropped. If the packet is ++ * unsequenced (i.e. does not need an ACK), the packet is completed by the ++ * transmitter thread before dropping that reference. ++ * ++ * On receial of an ACK, the receiver thread removes and obtains the refernce to ++ * the packet from the pending set. On succes, the receiver thread will then ++ * complete the packet and drop its reference. ++ * ++ * On error, the completion callback is immediately run by on thread on which ++ * the error was detected. ++ * ++ * To ensure that a packet eventually leaves the system it is marked as "locked" ++ * directly before it is going to be completed or when it is canceled. Marking a ++ * packet as "locked" has the effect that passing and creating new references ++ * of the packet will be blocked. This means that the packet cannot be added ++ * to the queue, the pending set, and the timeout, or be picked up by the ++ * transmitter thread or receiver thread. To remove a packet from the system it ++ * has to be marked as locked and subsequently all references from the data ++ * structures (queue, pending) have to be removed. References held by threads ++ * will eventually be dropped automatically as their execution progresses. ++ * ++ * Note that the packet completion callback is, in case of success and for a ++ * sequenced packet, guaranteed to run on the receiver thread, thus providing a ++ * way to reliably identify responses to the packet. The packet completion ++ * callback is only run once and it does not indicate that the packet has fully ++ * left the system. In case of re-submission (and with somewhat unlikely ++ * timing), it may be possible that the packet is being re-transmitted while the ++ * completion callback runs. Completion will occur both on success and internal ++ * error, as well as when the packet is canceled. ++ * ++ * >> Flags << ++ * ++ * Flags are used to indicate the state and progression of a packet. Some flags ++ * have stricter guarantees than other: ++ * ++ * - locked ++ * Indicates if the packet is locked. If the packet is locked, passing and/or ++ * creating additional references to the packet is forbidden. The packet thus ++ * may not be queued, dequeued, or removed or added to the pending set. Note ++ * that the packet state flags may still change (e.g. it may be marked as ++ * ACKed, transmitted, ...). ++ * ++ * - completed ++ * Indicates if the packet completion has been run or is about to be run. This ++ * flag is used to ensure that the packet completion callback is only run ++ * once. ++ * ++ * - queued ++ * Indicates if a packet is present in the submission queue or not. This flag ++ * must only be modified with the queue lock held, and must be coherent ++ * presence of the packet in the queue. ++ * ++ * - pending ++ * Indicates if a packet is present in the set of pending packets or not. ++ * This flag must only be modified with the pending lock held, and must be ++ * coherent presence of the packet in the pending set. ++ * ++ * - transmitting ++ * Indicates if the packet is currently transmitting. In case of ++ * re-transmissions, it is only safe to wait on the "transmitted" completion ++ * after this flag has been set. The completion will be set both in success ++ * and error case. ++ * ++ * - transmitted ++ * Indicates if the packet has been transmitted. This flag is not cleared by ++ * the system, thus it indicates the first transmission only. ++ * ++ * - acked ++ * Indicates if the packet has been acknowledged by the client. There are no ++ * other guarantees given. For example, the packet may still be canceled ++ * and/or the completion may be triggered an error even though this bit is ++ * set. Rely on the status provided by completion instead. ++ * ++ * - canceled ++ * Indicates if the packet has been canceled from the outside. There are no ++ * other guarantees given. Specifically, the packet may be completed by ++ * another part of the system before the cancellation attempts to complete it. ++ * ++ * >> General Notes << ++ * ++ * To avoid deadlocks, if both queue and pending locks are required, the pending ++ * lock must be acquired before the queue lock. + */ -+static const u8 vhf_hid_desc[] = { -+ // keyboard descriptor (event command ID 0x03) -+ 0x05, 0x01, /* Usage Page (Desktop), */ -+ 0x09, 0x06, /* Usage (Keyboard), */ -+ 0xA1, 0x01, /* Collection (Application), */ -+ 0x85, 0x01, /* Report ID (1), */ -+ 0x15, 0x00, /* Logical Minimum (0), */ -+ 0x25, 0x01, /* Logical Maximum (1), */ -+ 0x75, 0x01, /* Report Size (1), */ -+ 0x95, 0x08, /* Report Count (8), */ -+ 0x05, 0x07, /* Usage Page (Keyboard), */ -+ 0x19, 0xE0, /* Usage Minimum (KB Leftcontrol), */ -+ 0x29, 0xE7, /* Usage Maximum (KB Right GUI), */ -+ 0x81, 0x02, /* Input (Variable), */ -+ 0x75, 0x08, /* Report Size (8), */ -+ 0x95, 0x0A, /* Report Count (10), */ -+ 0x19, 0x00, /* Usage Minimum (None), */ -+ 0x29, 0x91, /* Usage Maximum (KB LANG2), */ -+ 0x26, 0xFF, 0x00, /* Logical Maximum (255), */ -+ 0x81, 0x00, /* Input, */ -+ 0x05, 0x0C, /* Usage Page (Consumer), */ -+ 0x0A, 0xC0, 0x02, /* Usage (02C0h), */ -+ 0xA1, 0x02, /* Collection (Logical), */ -+ 0x1A, 0xC1, 0x02, /* Usage Minimum (02C1h), */ -+ 0x2A, 0xC6, 0x02, /* Usage Maximum (02C6h), */ -+ 0x95, 0x06, /* Report Count (6), */ -+ 0xB1, 0x03, /* Feature (Constant, Variable), */ -+ 0xC0, /* End Collection, */ -+ 0x05, 0x08, /* Usage Page (LED), */ -+ 0x19, 0x01, /* Usage Minimum (01h), */ -+ 0x29, 0x03, /* Usage Maximum (03h), */ -+ 0x75, 0x01, /* Report Size (1), */ -+ 0x95, 0x03, /* Report Count (3), */ -+ 0x25, 0x01, /* Logical Maximum (1), */ -+ 0x91, 0x02, /* Output (Variable), */ -+ 0x95, 0x05, /* Report Count (5), */ -+ 0x91, 0x01, /* Output (Constant), */ -+ 0xC0, /* End Collection, */ + -+ // media key descriptor (event command ID 0x04) -+ 0x05, 0x0C, /* Usage Page (Consumer), */ -+ 0x09, 0x01, /* Usage (Consumer Control), */ -+ 0xA1, 0x01, /* Collection (Application), */ -+ 0x85, 0x03, /* Report ID (3), */ -+ 0x75, 0x10, /* Report Size (16), */ -+ 0x15, 0x00, /* Logical Minimum (0), */ -+ 0x26, 0xFF, 0x03, /* Logical Maximum (1023), */ -+ 0x19, 0x00, /* Usage Minimum (00h), */ -+ 0x2A, 0xFF, 0x03, /* Usage Maximum (03FFh), */ -+ 0x81, 0x00, /* Input, */ -+ 0xC0, /* End Collection, */ -+}; ++/** ++ * Maximum number transmission attempts per sequenced packet in case of ++ * time-outs. Must be smaller than 16. ++ */ ++#define SSH_PTL_MAX_PACKET_TRIES 3 ++ ++/** ++ * Timeout as ktime_t delta for ACKs. If we have not received an ACK in this ++ * time-frame after starting transmission, the packet will be re-submitted. ++ */ ++#define SSH_PTL_PACKET_TIMEOUT ms_to_ktime(1000) ++ ++/** ++ * Maximum time resolution for timeouts. Currently set to max(2 jiffies, 50ms). ++ * Should be larger than one jiffy to avoid direct re-scheduling of reaper ++ * work_struct. ++ */ ++#define SSH_PTL_PACKET_TIMEOUT_RESOLUTION ms_to_ktime(max(2000 / HZ, 50)) ++ ++/** ++ * Maximum number of sequenced packets concurrently waiting for an ACK. ++ * Packets marked as blocking will not be transmitted while this limit is ++ * reached. ++ */ ++#define SSH_PTL_MAX_PENDING 1 ++ ++#define SSH_PTL_RX_BUF_LEN 4096 ++ ++#define SSH_PTL_RX_FIFO_LEN 4096 + + -+static int vhf_hid_start(struct hid_device *hid) ++#ifdef CONFIG_SURFACE_SAM_SSH_ERROR_INJECTION ++ ++/** ++ * ssh_ptl_should_drop_ack_packet - error injection hook to drop ACK packets ++ * ++ * Useful to test detection and handling of automated re-transmits by the EC. ++ * Specifically of packets that the EC consideres not-ACKed but the driver ++ * already consideres ACKed (due to dropped ACK). In this case, the EC ++ * re-transmits the packet-to-be-ACKed and the driver should detect it as ++ * duplicate/already handled. Note that the driver should still send an ACK ++ * for the re-transmitted packet. ++ */ ++static noinline bool ssh_ptl_should_drop_ack_packet(void) ++{ ++ return false; ++} ++ALLOW_ERROR_INJECTION(ssh_ptl_should_drop_ack_packet, TRUE); ++ ++/** ++ * ssh_ptl_should_drop_nak_packet - error injection hook to drop NAK packets ++ * ++ * Useful to test/force automated (timeout-based) re-transmit by the EC. ++ * Specifically, packets that have not reached the driver completely/with valid ++ * checksums. Only useful in combination with receival of (injected) bad data. ++ */ ++static noinline bool ssh_ptl_should_drop_nak_packet(void) ++{ ++ return false; ++} ++ALLOW_ERROR_INJECTION(ssh_ptl_should_drop_nak_packet, TRUE); ++ ++/** ++ * ssh_ptl_should_drop_dsq_packet - error injection hook to drop sequenced data ++ * packet ++ * ++ * Useful to test re-transmit timeout of the driver. If the data packet has not ++ * been ACKed after a certain time, the driver should re-transmit the packet up ++ * to limited number of times defined in SSH_PTL_MAX_PACKET_TRIES. ++ */ ++static noinline bool ssh_ptl_should_drop_dsq_packet(void) ++{ ++ return false; ++} ++ALLOW_ERROR_INJECTION(ssh_ptl_should_drop_dsq_packet, TRUE); ++ ++/** ++ * ssh_ptl_should_fail_write - error injection hook to make serdev_device_write ++ * fail ++ * ++ * Hook to simulate errors in serdev_device_write when transmitting packets. ++ */ ++static noinline int ssh_ptl_should_fail_write(void) +{ -+ hid_dbg(hid, "%s\n", __func__); + return 0; +} ++ALLOW_ERROR_INJECTION(ssh_ptl_should_fail_write, ERRNO); + -+static void vhf_hid_stop(struct hid_device *hid) ++/** ++ * ssh_ptl_should_corrupt_tx_data - error injection hook to simualte invalid ++ * data being sent to the EC ++ * ++ * Hook to simulate corrupt/invalid data being sent from host (driver) to EC. ++ * Causes the packet data to be actively corrupted by overwriting it with ++ * pre-defined values, such that it becomes invalid, causing the EC to respond ++ * with a NAK packet. Useful to test handling of NAK packets received by the ++ * driver. ++ */ ++static noinline bool ssh_ptl_should_corrupt_tx_data(void) +{ -+ hid_dbg(hid, "%s\n", __func__); ++ return false; ++} ++ALLOW_ERROR_INJECTION(ssh_ptl_should_corrupt_tx_data, TRUE); ++ ++/** ++ * ssh_ptl_should_corrupt_rx_syn - error injection hook to simulate invalid ++ * data being sent by the EC ++ * ++ * Hook to simulate invalid SYN bytes, i.e. an invalid start of messages and ++ * test handling thereof in the driver. ++ */ ++static noinline bool ssh_ptl_should_corrupt_rx_syn(void) ++{ ++ return false; ++} ++ALLOW_ERROR_INJECTION(ssh_ptl_should_corrupt_rx_syn, TRUE); ++ ++/** ++ * ssh_ptl_should_corrupt_rx_data - error injection hook to simulate invalid ++ * data being sent by the EC ++ * ++ * Hook to simulate invalid data/checksum of the message frame and test handling ++ * thereof in the driver. ++ */ ++static noinline bool ssh_ptl_should_corrupt_rx_data(void) ++{ ++ return false; ++} ++ALLOW_ERROR_INJECTION(ssh_ptl_should_corrupt_rx_data, TRUE); ++ ++ ++static inline bool __ssh_ptl_should_drop_ack_packet(struct ssh_packet *packet) ++{ ++ if (likely(!ssh_ptl_should_drop_ack_packet())) ++ return false; ++ ++ trace_ssam_ei_tx_drop_ack_packet(packet); ++ ptl_info(packet->ptl, "packet error injection: dropping ACK packet %p\n", ++ packet); ++ ++ return true; +} + -+static int vhf_hid_open(struct hid_device *hid) ++static inline bool __ssh_ptl_should_drop_nak_packet(struct ssh_packet *packet) +{ -+ hid_dbg(hid, "%s\n", __func__); -+ return 0; ++ if (likely(!ssh_ptl_should_drop_nak_packet())) ++ return false; ++ ++ trace_ssam_ei_tx_drop_nak_packet(packet); ++ ptl_info(packet->ptl, "packet error injection: dropping NAK packet %p\n", ++ packet); ++ ++ return true; +} + -+static void vhf_hid_close(struct hid_device *hid) ++static inline bool __ssh_ptl_should_drop_dsq_packet(struct ssh_packet *packet) +{ -+ hid_dbg(hid, "%s\n", __func__); ++ if (likely(!ssh_ptl_should_drop_dsq_packet())) ++ return false; ++ ++ trace_ssam_ei_tx_drop_dsq_packet(packet); ++ ptl_info(packet->ptl, ++ "packet error injection: dropping sequenced data packet %p\n", ++ packet); ++ ++ return true; +} + -+static int vhf_hid_parse(struct hid_device *hid) ++static bool ssh_ptl_should_drop_packet(struct ssh_packet *packet) +{ -+ return hid_parse_report(hid, (u8 *)vhf_hid_desc, ARRAY_SIZE(vhf_hid_desc)); ++ // ignore packets that don't carry any data (i.e. flush) ++ if (!packet->data.ptr || !packet->data.len) ++ return false; ++ ++ switch (packet->data.ptr[SSH_MSGOFFSET_FRAME(type)]) { ++ case SSH_FRAME_TYPE_ACK: ++ return __ssh_ptl_should_drop_ack_packet(packet); ++ ++ case SSH_FRAME_TYPE_NAK: ++ return __ssh_ptl_should_drop_nak_packet(packet); ++ ++ case SSH_FRAME_TYPE_DATA_SEQ: ++ return __ssh_ptl_should_drop_dsq_packet(packet); ++ ++ default: ++ return false; ++ } +} + -+static int vhf_hid_raw_request(struct hid_device *hid, unsigned char reportnum, -+ u8 *buf, size_t len, unsigned char rtype, -+ int reqtype) ++static int ssh_ptl_write_buf(struct ssh_ptl *ptl, struct ssh_packet *packet, ++ const unsigned char *buf, size_t count) +{ -+ hid_dbg(hid, "%s\n", __func__); -+ return 0; -+} -+ -+static int vhf_hid_output_report(struct hid_device *hid, u8 *buf, size_t len) -+{ -+ hid_dbg(hid, "%s\n", __func__); -+ print_hex_dump_debug("report:", DUMP_PREFIX_OFFSET, 16, 1, buf, len, false); -+ -+ return len; -+} -+ -+static struct hid_ll_driver vhf_hid_ll_driver = { -+ .start = vhf_hid_start, -+ .stop = vhf_hid_stop, -+ .open = vhf_hid_open, -+ .close = vhf_hid_close, -+ .parse = vhf_hid_parse, -+ .raw_request = vhf_hid_raw_request, -+ .output_report = vhf_hid_output_report, -+}; -+ -+ -+static struct hid_device *vhf_create_hid_device(struct platform_device *pdev) -+{ -+ struct hid_device *hid; -+ -+ hid = hid_allocate_device(); -+ if (IS_ERR(hid)) -+ return hid; -+ -+ hid->dev.parent = &pdev->dev; -+ -+ hid->bus = BUS_VIRTUAL; -+ hid->vendor = USB_VENDOR_ID_MICROSOFT; -+ hid->product = USB_DEVICE_ID_MS_VHF; -+ -+ hid->ll_driver = &vhf_hid_ll_driver; -+ -+ sprintf(hid->name, "%s", VHF_INPUT_NAME); -+ -+ return hid; -+} -+ -+static u32 vhf_event_handler(struct ssam_notifier_block *nb, const struct ssam_event *event) -+{ -+ struct vhf_drvdata *drvdata = container_of(nb, struct vhf_drvdata, notif.base); + int status; + -+ if (event->target_category != 0x08) -+ return 0; ++ status = ssh_ptl_should_fail_write(); ++ if (unlikely(status)) { ++ trace_ssam_ei_tx_fail_write(packet, status); ++ ptl_info(packet->ptl, ++ "packet error injection: simulating transmit error %d, packet %p\n", ++ status, packet); + -+ if (event->command_id == 0x03 || event->command_id == 0x04) { -+ status = hid_input_report(drvdata->hid, HID_INPUT_REPORT, (u8 *)&event->data[0], event->length, 1); -+ return ssam_notifier_from_errno(status) | SSAM_NOTIF_HANDLED; ++ return status; + } + -+ return 0; ++ return serdev_device_write_buf(ptl->serdev, buf, count); +} + -+static int surface_sam_vhf_probe(struct platform_device *pdev) ++static void ssh_ptl_tx_inject_invalid_data(struct ssh_packet *packet) +{ -+ struct ssam_controller *ctrl; -+ struct vhf_drvdata *drvdata; -+ struct hid_device *hid; -+ int status; ++ // ignore packets that don't carry any data (i.e. flush) ++ if (!packet->data.ptr || !packet->data.len) ++ return; + -+ // add device link to EC -+ status = ssam_client_bind(&pdev->dev, &ctrl); -+ if (status) -+ return status == -ENXIO ? -EPROBE_DEFER : status; ++ // only allow sequenced data packets to be modified ++ if (packet->data.ptr[SSH_MSGOFFSET_FRAME(type)] != SSH_FRAME_TYPE_DATA_SEQ) ++ return; + -+ drvdata = kzalloc(sizeof(struct vhf_drvdata), GFP_KERNEL); -+ if (!drvdata) ++ if (likely(!ssh_ptl_should_corrupt_tx_data())) ++ return; ++ ++ trace_ssam_ei_tx_corrupt_data(packet); ++ ptl_info(packet->ptl, ++ "packet error injection: simulating invalid transmit data on packet %p\n", ++ packet); ++ ++ /* ++ * NB: The value 0xb3 has been chosen more or less randomly so that it ++ * doesn't have any (major) overlap with the SYN bytes (aa 55) and is ++ * non-trivial (i.e. non-zero, non-0xff). ++ */ ++ memset(packet->data.ptr, 0xb3, packet->data.len); ++} ++ ++static void ssh_ptl_rx_inject_invalid_syn(struct ssh_ptl *ptl, ++ struct ssam_span *data) ++{ ++ struct ssam_span frame; ++ ++ // check if there actually is something to corrupt ++ if (!sshp_find_syn(data, &frame)) ++ return; ++ ++ if (likely(!ssh_ptl_should_corrupt_rx_syn())) ++ return; ++ ++ trace_ssam_ei_rx_corrupt_syn("data_length", data->len); ++ ++ data->ptr[1] = 0xb3; // set second byte of SYN to "random" value ++} ++ ++static void ssh_ptl_rx_inject_invalid_data(struct ssh_ptl *ptl, ++ struct ssam_span *frame) ++{ ++ size_t payload_len, message_len; ++ struct ssh_frame *sshf; ++ ++ // ignore incomplete messages, will get handled once it's complete ++ if (frame->len < SSH_MESSAGE_LENGTH(0)) ++ return; ++ ++ // ignore incomplete messages, part 2 ++ payload_len = get_unaligned_le16(&frame->ptr[SSH_MSGOFFSET_FRAME(len)]); ++ message_len = SSH_MESSAGE_LENGTH(payload_len); ++ if (frame->len < message_len) ++ return; ++ ++ if (likely(!ssh_ptl_should_corrupt_rx_data())) ++ return; ++ ++ sshf = (struct ssh_frame *)&frame->ptr[SSH_MSGOFFSET_FRAME(type)]; ++ trace_ssam_ei_rx_corrupt_data(sshf); ++ ++ /* ++ * Flip bits in first byte of payload checksum. This is basically ++ * equivalent to a payload/frame data error without us having to worry ++ * about (the, arguably pretty small, probability of) accidental ++ * checksum collisions. ++ */ ++ frame->ptr[frame->len - 2] = ~frame->ptr[frame->len - 2]; ++} ++ ++#else /* CONFIG_SURFACE_SAM_SSH_ERROR_INJECTION */ ++ ++static inline bool ssh_ptl_should_drop_packet(struct ssh_packet *packet) ++{ ++ return false; ++} ++ ++static inline int ssh_ptl_write_buf(struct ssh_ptl *ptl, ++ struct ssh_packet *packet, ++ const unsigned char *buf, ++ size_t count) ++{ ++ return serdev_device_write_buf(ptl->serdev, buf, count); ++} ++ ++static inline void ssh_ptl_tx_inject_invalid_data(struct ssh_packet *packet) ++{ ++} ++ ++static inline void ssh_ptl_rx_inject_invalid_syn(struct ssh_ptl *ptl, ++ struct ssam_span *data) ++{ ++} ++ ++static inline void ssh_ptl_rx_inject_invalid_data(struct ssh_ptl *ptl, ++ struct ssam_span *frame) ++{ ++} ++ ++#endif /* CONFIG_SURFACE_SAM_SSH_ERROR_INJECTION */ ++ ++ ++static void __ssh_ptl_packet_release(struct kref *kref) ++{ ++ struct ssh_packet *p = to_ssh_packet(kref, refcnt); ++ ++ trace_ssam_packet_release(p); ++ ++ ptl_dbg_cond(p->ptl, "ptl: releasing packet %p\n", p); ++ p->ops->release(p); ++} ++ ++void ssh_packet_put(struct ssh_packet *packet) ++{ ++ kref_put(&packet->refcnt, __ssh_ptl_packet_release); ++} ++EXPORT_SYMBOL_GPL(ssh_packet_put); ++ ++static inline u8 ssh_packet_get_seq(struct ssh_packet *packet) ++{ ++ return packet->data.ptr[SSH_MSGOFFSET_FRAME(seq)]; ++} ++ ++ ++void ssh_packet_init(struct ssh_packet *packet, ++ const struct ssh_packet_args *args) ++{ ++ kref_init(&packet->refcnt); ++ ++ packet->ptl = NULL; ++ INIT_LIST_HEAD(&packet->queue_node); ++ INIT_LIST_HEAD(&packet->pending_node); ++ ++ packet->state = args->type & SSH_PACKET_FLAGS_TY_MASK; ++ packet->priority = args->priority; ++ packet->timestamp = KTIME_MAX; ++ ++ packet->data.ptr = NULL; ++ packet->data.len = 0; ++ ++ packet->ops = args->ops; ++} ++ ++ ++static struct kmem_cache *ssh_ctrl_packet_cache; ++ ++int ssh_ctrl_packet_cache_init(void) ++{ ++ const unsigned int size = sizeof(struct ssh_packet) + SSH_MSG_LEN_CTRL; ++ const unsigned int align = __alignof__(struct ssh_packet); ++ struct kmem_cache *cache; ++ ++ cache = kmem_cache_create("ssam_ctrl_packet", size, align, 0, NULL); ++ if (!cache) + return -ENOMEM; + -+ hid = vhf_create_hid_device(pdev); -+ if (IS_ERR(hid)) { -+ status = PTR_ERR(hid); -+ goto err_probe_hid; ++ ssh_ctrl_packet_cache = cache; ++ return 0; ++} ++ ++void ssh_ctrl_packet_cache_destroy(void) ++{ ++ kmem_cache_destroy(ssh_ctrl_packet_cache); ++ ssh_ctrl_packet_cache = NULL; ++} ++ ++static int ssh_ctrl_packet_alloc(struct ssh_packet **packet, ++ struct ssam_span *buffer, gfp_t flags) ++{ ++ *packet = kmem_cache_alloc(ssh_ctrl_packet_cache, flags); ++ if (!*packet) ++ return -ENOMEM; ++ ++ buffer->ptr = (u8 *)(*packet + 1); ++ buffer->len = SSH_MSG_LEN_CTRL; ++ ++ trace_ssam_ctrl_packet_alloc(*packet, buffer->len); ++ return 0; ++} ++ ++static void ssh_ctrl_packet_free(struct ssh_packet *p) ++{ ++ trace_ssam_ctrl_packet_free(p); ++ kmem_cache_free(ssh_ctrl_packet_cache, p); ++} ++ ++static const struct ssh_packet_ops ssh_ptl_ctrl_packet_ops = { ++ .complete = NULL, ++ .release = ssh_ctrl_packet_free, ++}; ++ ++ ++static void ssh_ptl_timeout_reaper_mod(struct ssh_ptl *ptl, ktime_t now, ++ ktime_t expires) ++{ ++ unsigned long delta = msecs_to_jiffies(ktime_ms_delta(expires, now)); ++ ktime_t aexp = ktime_add(expires, SSH_PTL_PACKET_TIMEOUT_RESOLUTION); ++ ktime_t old; ++ ++ // re-adjust / schedule reaper if it is above resolution delta ++ old = READ_ONCE(ptl->rtx_timeout.expires); ++ while (ktime_before(aexp, old)) ++ old = cmpxchg64(&ptl->rtx_timeout.expires, old, expires); ++ ++ // if we updated the reaper expiration, modify work timeout ++ if (old == expires) ++ mod_delayed_work(system_wq, &ptl->rtx_timeout.reaper, delta); ++} ++ ++static void ssh_ptl_timeout_start(struct ssh_packet *packet) ++{ ++ struct ssh_ptl *ptl = packet->ptl; ++ ktime_t timestamp = ktime_get_coarse_boottime(); ++ ktime_t timeout = ptl->rtx_timeout.timeout; ++ ++ if (test_bit(SSH_PACKET_SF_LOCKED_BIT, &packet->state)) ++ return; ++ ++ WRITE_ONCE(packet->timestamp, timestamp); ++ smp_mb__after_atomic(); ++ ++ ssh_ptl_timeout_reaper_mod(packet->ptl, timestamp, timestamp + timeout); ++} ++ ++ ++static struct list_head *__ssh_ptl_queue_find_entrypoint(struct ssh_packet *p) ++{ ++ struct list_head *head; ++ u8 priority = READ_ONCE(p->priority); ++ ++ /* ++ * We generally assume that there are less control (ACK/NAK) packets and ++ * re-submitted data packets as there are normal data packets (at least ++ * in situations in which many packets are queued; if there aren't many ++ * packets queued the decision on how to iterate should be basically ++ * irrellevant; the number of control/data packets is more or less ++ * limited via the maximum number of pending packets). Thus, when ++ * inserting a control or re-submitted data packet, (determined by their ++ * priority), we search from front to back. Normal data packets are, ++ * usually queued directly at the tail of the queue, so for those search ++ * from back to front. ++ */ ++ ++ if (priority > SSH_PACKET_PRIORITY_DATA) { ++ list_for_each(head, &p->ptl->queue.head) { ++ p = list_entry(head, struct ssh_packet, queue_node); ++ ++ if (READ_ONCE(p->priority) < priority) ++ break; ++ } ++ } else { ++ list_for_each_prev(head, &p->ptl->queue.head) { ++ p = list_entry(head, struct ssh_packet, queue_node); ++ ++ if (READ_ONCE(p->priority) >= priority) { ++ head = head->next; ++ break; ++ } ++ } + } + -+ status = hid_add_device(hid); -+ if (status) -+ goto err_add_hid; + -+ drvdata->dev = pdev; -+ drvdata->ctrl = ctrl; -+ drvdata->hid = hid; ++ return head; ++} + -+ drvdata->notif.base.priority = 1; -+ drvdata->notif.base.fn = vhf_event_handler; -+ drvdata->notif.event.reg = SSAM_EVENT_REGISTRY_SAM; -+ drvdata->notif.event.id.target_category = SSAM_SSH_TC_KBD; -+ drvdata->notif.event.id.instance = 0; -+ drvdata->notif.event.flags = 0; ++static int ssh_ptl_queue_push(struct ssh_packet *packet) ++{ ++ struct ssh_ptl *ptl = packet->ptl; ++ struct list_head *head; + -+ platform_set_drvdata(pdev, drvdata); ++ spin_lock(&ptl->queue.lock); + -+ status = ssam_notifier_register(ctrl, &drvdata->notif); -+ if (status) -+ goto err_add_hid; ++ if (test_bit(SSH_PTL_SF_SHUTDOWN_BIT, &ptl->state)) { ++ spin_unlock(&ptl->queue.lock); ++ return -ESHUTDOWN; ++ } ++ ++ // avoid further transitions when cancelling/completing ++ if (test_bit(SSH_PACKET_SF_LOCKED_BIT, &packet->state)) { ++ spin_unlock(&ptl->queue.lock); ++ return -EINVAL; ++ } ++ ++ // if this packet has already been queued, do not add it ++ if (test_and_set_bit(SSH_PACKET_SF_QUEUED_BIT, &packet->state)) { ++ spin_unlock(&ptl->queue.lock); ++ return -EALREADY; ++ } ++ ++ head = __ssh_ptl_queue_find_entrypoint(packet); ++ ++ list_add_tail(&ssh_packet_get(packet)->queue_node, &ptl->queue.head); ++ ++ spin_unlock(&ptl->queue.lock); ++ return 0; ++} ++ ++static void ssh_ptl_queue_remove(struct ssh_packet *packet) ++{ ++ struct ssh_ptl *ptl = packet->ptl; ++ bool remove; ++ ++ spin_lock(&ptl->queue.lock); ++ ++ remove = test_and_clear_bit(SSH_PACKET_SF_QUEUED_BIT, &packet->state); ++ if (remove) ++ list_del(&packet->queue_node); ++ ++ spin_unlock(&ptl->queue.lock); ++ ++ if (remove) ++ ssh_packet_put(packet); ++} ++ ++ ++static void ssh_ptl_pending_push(struct ssh_packet *packet) ++{ ++ struct ssh_ptl *ptl = packet->ptl; ++ ++ spin_lock(&ptl->pending.lock); ++ ++ // if we are cancelling/completing this packet, do not add it ++ if (test_bit(SSH_PACKET_SF_LOCKED_BIT, &packet->state)) { ++ spin_unlock(&ptl->pending.lock); ++ return; ++ } ++ ++ // in case it is already pending (e.g. re-submission), do not add it ++ if (test_and_set_bit(SSH_PACKET_SF_PENDING_BIT, &packet->state)) { ++ spin_unlock(&ptl->pending.lock); ++ return; ++ } ++ ++ atomic_inc(&ptl->pending.count); ++ list_add_tail(&ssh_packet_get(packet)->pending_node, &ptl->pending.head); ++ ++ spin_unlock(&ptl->pending.lock); ++} ++ ++static void ssh_ptl_pending_remove(struct ssh_packet *packet) ++{ ++ struct ssh_ptl *ptl = packet->ptl; ++ bool remove; ++ ++ spin_lock(&ptl->pending.lock); ++ ++ remove = test_and_clear_bit(SSH_PACKET_SF_PENDING_BIT, &packet->state); ++ if (remove) { ++ list_del(&packet->pending_node); ++ atomic_dec(&ptl->pending.count); ++ } ++ ++ spin_unlock(&ptl->pending.lock); ++ ++ if (remove) ++ ssh_packet_put(packet); ++} ++ ++ ++static void __ssh_ptl_complete(struct ssh_packet *p, int status) ++{ ++ struct ssh_ptl *ptl = READ_ONCE(p->ptl); ++ ++ trace_ssam_packet_complete(p, status); ++ ++ ptl_dbg_cond(ptl, "ptl: completing packet %p\n", p); ++ if (status && status != -ECANCELED) ++ ptl_dbg_cond(ptl, "ptl: packet error: %d\n", status); ++ ++ if (p->ops->complete) ++ p->ops->complete(p, status); ++} ++ ++static void ssh_ptl_remove_and_complete(struct ssh_packet *p, int status) ++{ ++ /* ++ * A call to this function should in general be preceeded by ++ * set_bit(SSH_PACKET_SF_LOCKED_BIT, &p->flags) to avoid re-adding the ++ * packet to the structures it's going to be removed from. ++ * ++ * The set_bit call does not need explicit memory barriers as the ++ * implicit barrier of the test_and_set_bit call below ensure that the ++ * flag is visible before we actually attempt to remove the packet. ++ */ ++ ++ if (test_and_set_bit(SSH_PACKET_SF_COMPLETED_BIT, &p->state)) ++ return; ++ ++ ssh_ptl_queue_remove(p); ++ ssh_ptl_pending_remove(p); ++ ++ __ssh_ptl_complete(p, status); ++} ++ ++ ++static bool ssh_ptl_tx_can_process(struct ssh_packet *packet) ++{ ++ struct ssh_ptl *ptl = packet->ptl; ++ ++ if (test_bit(SSH_PACKET_TY_FLUSH_BIT, &packet->state)) ++ return !atomic_read(&ptl->pending.count); ++ ++ // we can alwas process non-blocking packets ++ if (!test_bit(SSH_PACKET_TY_BLOCKING_BIT, &packet->state)) ++ return true; ++ ++ // if we are already waiting for this packet, send it again ++ if (test_bit(SSH_PACKET_SF_PENDING_BIT, &packet->state)) ++ return true; ++ ++ // otherwise: check if we have the capacity to send ++ return atomic_read(&ptl->pending.count) < SSH_PTL_MAX_PENDING; ++} ++ ++static struct ssh_packet *ssh_ptl_tx_pop(struct ssh_ptl *ptl) ++{ ++ struct ssh_packet *packet = ERR_PTR(-ENOENT); ++ struct ssh_packet *p, *n; ++ ++ spin_lock(&ptl->queue.lock); ++ list_for_each_entry_safe(p, n, &ptl->queue.head, queue_node) { ++ /* ++ * If we are cancelling or completing this packet, ignore it. ++ * It's going to be removed from this queue shortly. ++ */ ++ if (test_bit(SSH_PACKET_SF_LOCKED_BIT, &p->state)) ++ continue; ++ ++ /* ++ * Packets should be ordered non-blocking/to-be-resent first. ++ * If we cannot process this packet, assume that we can't ++ * process any following packet either and abort. ++ */ ++ if (!ssh_ptl_tx_can_process(p)) { ++ packet = ERR_PTR(-EBUSY); ++ break; ++ } ++ ++ /* ++ * We are allowed to change the state now. Remove it from the ++ * queue and mark it as being transmitted. Note that we cannot ++ * add it to the set of pending packets yet, as queue locks must ++ * always be acquired before packet locks (otherwise we might ++ * run into a deadlock). ++ */ ++ ++ list_del(&p->queue_node); ++ ++ /* ++ * Ensure that the "queued" bit gets cleared after setting the ++ * "transmitting" bit to guaranteee non-zero flags. ++ */ ++ set_bit(SSH_PACKET_SF_TRANSMITTING_BIT, &p->state); ++ smp_mb__before_atomic(); ++ clear_bit(SSH_PACKET_SF_QUEUED_BIT, &p->state); ++ ++ packet = p; ++ break; ++ } ++ spin_unlock(&ptl->queue.lock); ++ ++ return packet; ++} ++ ++static struct ssh_packet *ssh_ptl_tx_next(struct ssh_ptl *ptl) ++{ ++ struct ssh_packet *p; ++ ++ p = ssh_ptl_tx_pop(ptl); ++ if (IS_ERR(p)) ++ return p; ++ ++ if (test_bit(SSH_PACKET_TY_SEQUENCED_BIT, &p->state)) { ++ ptl_dbg(ptl, "ptl: transmitting sequenced packet %p\n", p); ++ ssh_ptl_pending_push(p); ++ ssh_ptl_timeout_start(p); ++ } else { ++ ptl_dbg(ptl, "ptl: transmitting non-sequenced packet %p\n", p); ++ } ++ ++ /* ++ * Update number of tries. This directly influences the priority in case ++ * the packet is re-submitted (e.g. via timeout/NAK). Note that this is ++ * the only place where we update the priority in-flight. As this runs ++ * only on the tx-thread, this read-modify-write procedure is safe. ++ */ ++ WRITE_ONCE(p->priority, READ_ONCE(p->priority) + 1); ++ ++ return p; ++} ++ ++static void ssh_ptl_tx_compl_success(struct ssh_packet *packet) ++{ ++ struct ssh_ptl *ptl = packet->ptl; ++ ++ ptl_dbg(ptl, "ptl: successfully transmitted packet %p\n", packet); ++ ++ /* ++ * Transition to state to "transmitted". Ensure that the flags never get ++ * zero with barrier. ++ */ ++ set_bit(SSH_PACKET_SF_TRANSMITTED_BIT, &packet->state); ++ smp_mb__before_atomic(); ++ clear_bit(SSH_PACKET_SF_TRANSMITTING_BIT, &packet->state); ++ ++ // if the packet is unsequenced, we're done: lock and complete ++ if (!test_bit(SSH_PACKET_TY_SEQUENCED_BIT, &packet->state)) { ++ set_bit(SSH_PACKET_SF_LOCKED_BIT, &packet->state); ++ ssh_ptl_remove_and_complete(packet, 0); ++ } ++ ++ /* ++ * Notify that a packet transmission has finished. In general we're only ++ * waiting for one packet (if any), so wake_up_all should be fine. ++ */ ++ wake_up_all(&ptl->tx.packet_wq); ++} ++ ++static void ssh_ptl_tx_compl_error(struct ssh_packet *packet, int status) ++{ ++ /* ++ * Transmission failure: Lock the packet and try to complete it. Ensure ++ * that the flags never get zero with barrier. ++ */ ++ set_bit(SSH_PACKET_SF_LOCKED_BIT, &packet->state); ++ smp_mb__before_atomic(); ++ clear_bit(SSH_PACKET_SF_TRANSMITTING_BIT, &packet->state); ++ ++ ptl_err(packet->ptl, "ptl: transmission error: %d\n", status); ++ ptl_dbg(packet->ptl, "ptl: failed to transmit packet: %p\n", packet); ++ ++ ssh_ptl_remove_and_complete(packet, status); ++ ++ /* ++ * Notify that a packet transmission has finished. In general we're only ++ * waiting for one packet (if any), so wake_up_all should be fine. ++ */ ++ wake_up_all(&packet->ptl->tx.packet_wq); ++} ++ ++static void ssh_ptl_tx_threadfn_wait(struct ssh_ptl *ptl) ++{ ++ wait_event_interruptible(ptl->tx.thread_wq, ++ READ_ONCE(ptl->tx.thread_signal) || kthread_should_stop()); ++ WRITE_ONCE(ptl->tx.thread_signal, false); ++} ++ ++static int ssh_ptl_tx_threadfn(void *data) ++{ ++ struct ssh_ptl *ptl = data; ++ ++ while (!kthread_should_stop()) { ++ unsigned char *buf; ++ bool drop = false; ++ size_t len = 0; ++ int status = 0; ++ ++ // if we don't have a packet, get the next and add it to pending ++ if (IS_ERR_OR_NULL(ptl->tx.packet)) { ++ ptl->tx.packet = ssh_ptl_tx_next(ptl); ++ ptl->tx.offset = 0; ++ ++ // if no packet is available, we are done ++ if (IS_ERR(ptl->tx.packet)) { ++ ssh_ptl_tx_threadfn_wait(ptl); ++ continue; ++ } ++ } ++ ++ // error injection: drop packet to simulate transmission problem ++ if (ptl->tx.offset == 0) ++ drop = ssh_ptl_should_drop_packet(ptl->tx.packet); ++ ++ // error injection: simulate invalid packet data ++ if (ptl->tx.offset == 0 && !drop) ++ ssh_ptl_tx_inject_invalid_data(ptl->tx.packet); ++ ++ // flush-packets don't have any data ++ if (likely(ptl->tx.packet->data.ptr && !drop)) { ++ buf = ptl->tx.packet->data.ptr + ptl->tx.offset; ++ len = ptl->tx.packet->data.len - ptl->tx.offset; ++ ++ ptl_dbg(ptl, "tx: sending data (length: %zu)\n", len); ++ print_hex_dump_debug("tx: ", DUMP_PREFIX_OFFSET, 16, 1, ++ buf, len, false); ++ ++ status = ssh_ptl_write_buf(ptl, ptl->tx.packet, buf, len); ++ } ++ ++ if (status < 0) { ++ // complete packet with error ++ ssh_ptl_tx_compl_error(ptl->tx.packet, status); ++ ssh_packet_put(ptl->tx.packet); ++ ptl->tx.packet = NULL; ++ ++ } else if (status == len) { ++ // complete packet and/or mark as transmitted ++ ssh_ptl_tx_compl_success(ptl->tx.packet); ++ ssh_packet_put(ptl->tx.packet); ++ ptl->tx.packet = NULL; ++ ++ } else { // need more buffer space ++ ptl->tx.offset += status; ++ ssh_ptl_tx_threadfn_wait(ptl); ++ } ++ } ++ ++ // cancel active packet before we actually stop ++ if (!IS_ERR_OR_NULL(ptl->tx.packet)) { ++ ssh_ptl_tx_compl_error(ptl->tx.packet, -ESHUTDOWN); ++ ssh_packet_put(ptl->tx.packet); ++ ptl->tx.packet = NULL; ++ } + + return 0; ++} ++ ++void ssh_ptl_tx_wakeup(struct ssh_ptl *ptl, bool force) ++{ ++ if (test_bit(SSH_PTL_SF_SHUTDOWN_BIT, &ptl->state)) ++ return; ++ ++ if (force || atomic_read(&ptl->pending.count) < SSH_PTL_MAX_PENDING) { ++ WRITE_ONCE(ptl->tx.thread_signal, true); ++ smp_mb__after_atomic(); ++ wake_up(&ptl->tx.thread_wq); ++ } ++} ++ ++int ssh_ptl_tx_start(struct ssh_ptl *ptl) ++{ ++ ptl->tx.thread = kthread_run(ssh_ptl_tx_threadfn, ptl, "surface-sh-tx"); ++ if (IS_ERR(ptl->tx.thread)) ++ return PTR_ERR(ptl->tx.thread); ++ ++ return 0; ++} ++ ++static int ssh_ptl_tx_stop(struct ssh_ptl *ptl) ++{ ++ int status = 0; ++ ++ if (ptl->tx.thread) { ++ status = kthread_stop(ptl->tx.thread); ++ ptl->tx.thread = NULL; ++ } + -+err_add_hid: -+ hid_destroy_device(hid); -+ platform_set_drvdata(pdev, NULL); -+err_probe_hid: -+ kfree(drvdata); + return status; +} + -+static int surface_sam_vhf_remove(struct platform_device *pdev) ++ ++static struct ssh_packet *ssh_ptl_ack_pop(struct ssh_ptl *ptl, u8 seq_id) +{ -+ struct vhf_drvdata *drvdata = platform_get_drvdata(pdev); ++ struct ssh_packet *packet = ERR_PTR(-ENOENT); ++ struct ssh_packet *p, *n; + -+ ssam_notifier_unregister(drvdata->ctrl, &drvdata->notif); -+ hid_destroy_device(drvdata->hid); -+ kfree(drvdata); ++ spin_lock(&ptl->pending.lock); ++ list_for_each_entry_safe(p, n, &ptl->pending.head, pending_node) { ++ /* ++ * We generally expect packets to be in order, so first packet ++ * to be added to pending is first to be sent, is first to be ++ * ACKed. ++ */ ++ if (unlikely(ssh_packet_get_seq(p) != seq_id)) ++ continue; + -+ platform_set_drvdata(pdev, NULL); ++ /* ++ * In case we receive an ACK while handling a transmission error ++ * completion. The packet will be removed shortly. ++ */ ++ if (unlikely(test_bit(SSH_PACKET_SF_LOCKED_BIT, &p->state))) { ++ packet = ERR_PTR(-EPERM); ++ break; ++ } ++ ++ /* ++ * Mark packet as ACKed and remove it from pending. Ensure that ++ * the flags never get zero with barrier. ++ */ ++ set_bit(SSH_PACKET_SF_ACKED_BIT, &p->state); ++ smp_mb__before_atomic(); ++ clear_bit(SSH_PACKET_SF_PENDING_BIT, &p->state); ++ ++ atomic_dec(&ptl->pending.count); ++ list_del(&p->pending_node); ++ packet = p; ++ ++ break; ++ } ++ spin_unlock(&ptl->pending.lock); ++ ++ return packet; ++} ++ ++static void ssh_ptl_wait_until_transmitted(struct ssh_packet *packet) ++{ ++ wait_event(packet->ptl->tx.packet_wq, ++ test_bit(SSH_PACKET_SF_TRANSMITTED_BIT, &packet->state) ++ || test_bit(SSH_PACKET_SF_LOCKED_BIT, &packet->state)); ++} ++ ++static void ssh_ptl_acknowledge(struct ssh_ptl *ptl, u8 seq) ++{ ++ struct ssh_packet *p; ++ int status = 0; ++ ++ p = ssh_ptl_ack_pop(ptl, seq); ++ if (IS_ERR(p)) { ++ if (PTR_ERR(p) == -ENOENT) { ++ /* ++ * The packet has not been found in the set of pending ++ * packets. ++ */ ++ ptl_warn(ptl, "ptl: received ACK for non-pending" ++ " packet\n"); ++ } else { ++ /* ++ * The packet is pending, but we are not allowed to take ++ * it because it has been locked. ++ */ ++ } ++ return; ++ } ++ ++ ptl_dbg(ptl, "ptl: received ACK for packet %p\n", p); ++ ++ /* ++ * It is possible that the packet has been transmitted, but the state ++ * has not been updated from "transmitting" to "transmitted" yet. ++ * In that case, we need to wait for this transition to occur in order ++ * to determine between success or failure. ++ */ ++ if (test_bit(SSH_PACKET_SF_TRANSMITTING_BIT, &p->state)) ++ ssh_ptl_wait_until_transmitted(p); ++ ++ /* ++ * The packet will already be locked in case of a transmission error or ++ * cancellation. Let the transmitter or cancellation issuer complete the ++ * packet. ++ */ ++ if (unlikely(test_and_set_bit(SSH_PACKET_SF_LOCKED_BIT, &p->state))) { ++ ssh_packet_put(p); ++ return; ++ } ++ ++ if (unlikely(!test_bit(SSH_PACKET_SF_TRANSMITTED_BIT, &p->state))) { ++ ptl_err(ptl, "ptl: received ACK before packet had been fully" ++ " transmitted\n"); ++ status = -EREMOTEIO; ++ } ++ ++ ssh_ptl_remove_and_complete(p, status); ++ ssh_packet_put(p); ++ ++ ssh_ptl_tx_wakeup(ptl, false); ++} ++ ++ ++int ssh_ptl_submit(struct ssh_ptl *ptl, struct ssh_packet *p) ++{ ++ struct ssh_ptl *ptl_old; ++ int status; ++ ++ trace_ssam_packet_submit(p); ++ ++ // validate packet fields ++ if (test_bit(SSH_PACKET_TY_FLUSH_BIT, &p->state)) { ++ if (p->data.ptr || test_bit(SSH_PACKET_TY_SEQUENCED_BIT, &p->state)) ++ return -EINVAL; ++ } else if (!p->data.ptr) { ++ return -EINVAL; ++ } ++ ++ /* ++ * The ptl reference only gets set on or before the first submission. ++ * After the first submission, it has to be read-only. ++ */ ++ ptl_old = READ_ONCE(p->ptl); ++ if (ptl_old == NULL) ++ WRITE_ONCE(p->ptl, ptl); ++ else if (ptl_old != ptl) ++ return -EALREADY; ++ ++ status = ssh_ptl_queue_push(p); ++ if (status) ++ return status; ++ ++ ssh_ptl_tx_wakeup(ptl, !test_bit(SSH_PACKET_TY_BLOCKING_BIT, &p->state)); ++ return 0; ++} ++ ++static void __ssh_ptl_resubmit(struct ssh_packet *packet) ++{ ++ struct list_head *head; ++ ++ trace_ssam_packet_resubmit(packet); ++ ++ spin_lock(&packet->ptl->queue.lock); ++ ++ // if this packet has already been queued, do not add it ++ if (test_and_set_bit(SSH_PACKET_SF_QUEUED_BIT, &packet->state)) { ++ spin_unlock(&packet->ptl->queue.lock); ++ return; ++ } ++ ++ // find first node with lower priority ++ head = __ssh_ptl_queue_find_entrypoint(packet); ++ ++ WRITE_ONCE(packet->timestamp, KTIME_MAX); ++ smp_mb__after_atomic(); ++ ++ // add packet ++ list_add_tail(&ssh_packet_get(packet)->queue_node, head); ++ ++ spin_unlock(&packet->ptl->queue.lock); ++} ++ ++static void ssh_ptl_resubmit_pending(struct ssh_ptl *ptl) ++{ ++ struct ssh_packet *p; ++ bool resub = false; ++ u8 try; ++ ++ /* ++ * Note: We deliberately do not remove/attempt to cancel and complete ++ * packets that are out of tires in this function. The packet will be ++ * eventually canceled and completed by the timeout. Removing the packet ++ * here could lead to overly eager cancelation if the packet has not ++ * been re-transmitted yet but the tries-counter already updated (i.e ++ * ssh_ptl_tx_next removed the packet from the queue and updated the ++ * counter, but re-transmission for the last try has not actually ++ * started yet). ++ */ ++ ++ spin_lock(&ptl->pending.lock); ++ ++ // re-queue all pending packets ++ list_for_each_entry(p, &ptl->pending.head, pending_node) { ++ // avoid further transitions if locked ++ if (test_bit(SSH_PACKET_SF_LOCKED_BIT, &p->state)) ++ continue; ++ ++ // do not re-schedule if packet is out of tries ++ try = ssh_packet_priority_get_try(READ_ONCE(p->priority)); ++ if (try >= SSH_PTL_MAX_PACKET_TRIES) ++ continue; ++ ++ resub = true; ++ __ssh_ptl_resubmit(p); ++ } ++ ++ spin_unlock(&ptl->pending.lock); ++ ++ ssh_ptl_tx_wakeup(ptl, resub); ++} ++ ++void ssh_ptl_cancel(struct ssh_packet *p) ++{ ++ if (test_and_set_bit(SSH_PACKET_SF_CANCELED_BIT, &p->state)) ++ return; ++ ++ trace_ssam_packet_cancel(p); ++ ++ /* ++ * Lock packet and commit with memory barrier. If this packet has ++ * already been locked, it's going to be removed and completed by ++ * another party, which should have precedence. ++ */ ++ if (test_and_set_bit(SSH_PACKET_SF_LOCKED_BIT, &p->state)) ++ return; ++ ++ /* ++ * By marking the packet as locked and employing the implicit memory ++ * barrier of test_and_set_bit, we have guaranteed that, at this point, ++ * the packet cannot be added to the queue any more. ++ * ++ * In case the packet has never been submitted, packet->ptl is NULL. If ++ * the packet is currently being submitted, packet->ptl may be NULL or ++ * non-NULL. Due marking the packet as locked above and committing with ++ * the memory barrier, we have guaranteed that, if packet->ptl is NULL, ++ * the packet will never be added to the queue. If packet->ptl is ++ * non-NULL, we don't have any guarantees. ++ */ ++ ++ if (READ_ONCE(p->ptl)) { ++ ssh_ptl_remove_and_complete(p, -ECANCELED); ++ ssh_ptl_tx_wakeup(p->ptl, false); ++ } else if (!test_and_set_bit(SSH_PACKET_SF_COMPLETED_BIT, &p->state)) { ++ __ssh_ptl_complete(p, -ECANCELED); ++ } ++} ++ ++ ++static ktime_t ssh_packet_get_expiration(struct ssh_packet *p, ktime_t timeout) ++{ ++ ktime_t timestamp = READ_ONCE(p->timestamp); ++ ++ if (timestamp != KTIME_MAX) ++ return ktime_add(timestamp, timeout); ++ else ++ return KTIME_MAX; ++} ++ ++static void ssh_ptl_timeout_reap(struct work_struct *work) ++{ ++ struct ssh_ptl *ptl = to_ssh_ptl(work, rtx_timeout.reaper.work); ++ struct ssh_packet *p, *n; ++ LIST_HEAD(claimed); ++ ktime_t now = ktime_get_coarse_boottime(); ++ ktime_t timeout = ptl->rtx_timeout.timeout; ++ ktime_t next = KTIME_MAX; ++ bool resub = false; ++ ++ trace_ssam_ptl_timeout_reap("pending", atomic_read(&ptl->pending.count)); ++ ++ /* ++ * Mark reaper as "not pending". This is done before checking any ++ * packets to avoid lost-update type problems. ++ */ ++ WRITE_ONCE(ptl->rtx_timeout.expires, KTIME_MAX); ++ smp_mb__after_atomic(); ++ ++ spin_lock(&ptl->pending.lock); ++ ++ list_for_each_entry_safe(p, n, &ptl->pending.head, pending_node) { ++ ktime_t expires = ssh_packet_get_expiration(p, timeout); ++ u8 try; ++ ++ /* ++ * Check if the timeout hasn't expired yet. Find out next ++ * expiration date to be handled after this run. ++ */ ++ if (ktime_after(expires, now)) { ++ next = ktime_before(expires, next) ? expires : next; ++ continue; ++ } ++ ++ // avoid further transitions if locked ++ if (test_bit(SSH_PACKET_SF_LOCKED_BIT, &p->state)) ++ continue; ++ ++ trace_ssam_packet_timeout(p); ++ ++ // check if we still have some tries left ++ try = ssh_packet_priority_get_try(READ_ONCE(p->priority)); ++ if (likely(try < SSH_PTL_MAX_PACKET_TRIES)) { ++ resub = true; ++ __ssh_ptl_resubmit(p); ++ continue; ++ } ++ ++ // no more tries left: cancel the packet ++ ++ // if someone else has locked the packet already, don't use it ++ if (test_and_set_bit(SSH_PACKET_SF_LOCKED_BIT, &p->state)) ++ continue; ++ ++ /* ++ * We have now marked the packet as locked. Thus it cannot be ++ * added to the pending list again after we've removed it here. ++ * We can therefore re-use the pending_node of this packet ++ * temporarily. ++ */ ++ ++ clear_bit(SSH_PACKET_SF_PENDING_BIT, &p->state); ++ ++ atomic_dec(&ptl->pending.count); ++ list_del(&p->pending_node); ++ ++ list_add_tail(&p->pending_node, &claimed); ++ } ++ ++ spin_unlock(&ptl->pending.lock); ++ ++ // cancel and complete the packet ++ list_for_each_entry_safe(p, n, &claimed, pending_node) { ++ if (!test_and_set_bit(SSH_PACKET_SF_COMPLETED_BIT, &p->state)) { ++ ssh_ptl_queue_remove(p); ++ __ssh_ptl_complete(p, -ETIMEDOUT); ++ } ++ ++ // drop the reference we've obtained by removing it from pending ++ list_del(&p->pending_node); ++ ssh_packet_put(p); ++ } ++ ++ // ensure that reaper doesn't run again immediately ++ next = max(next, ktime_add(now, SSH_PTL_PACKET_TIMEOUT_RESOLUTION)); ++ if (next != KTIME_MAX) ++ ssh_ptl_timeout_reaper_mod(ptl, now, next); ++ ++ // force-wakeup to properly handle re-transmits if we've re-submitted ++ ssh_ptl_tx_wakeup(ptl, resub); ++} ++ ++ ++static bool ssh_ptl_rx_retransmit_check(struct ssh_ptl *ptl, u8 seq) ++{ ++ int i; ++ ++ // check if SEQ has been seen recently (i.e. packet was re-transmitted) ++ for (i = 0; i < ARRAY_SIZE(ptl->rx.blocked.seqs); i++) { ++ if (likely(ptl->rx.blocked.seqs[i] != seq)) ++ continue; ++ ++ ptl_dbg(ptl, "ptl: ignoring repeated data packet\n"); ++ return true; ++ } ++ ++ // update list of blocked seuence IDs ++ ptl->rx.blocked.seqs[ptl->rx.blocked.offset] = seq; ++ ptl->rx.blocked.offset = (ptl->rx.blocked.offset + 1) ++ % ARRAY_SIZE(ptl->rx.blocked.seqs); ++ ++ return false; ++} ++ ++static void ssh_ptl_rx_dataframe(struct ssh_ptl *ptl, ++ const struct ssh_frame *frame, ++ const struct ssam_span *payload) ++{ ++ if (ssh_ptl_rx_retransmit_check(ptl, frame->seq)) ++ return; ++ ++ ptl->ops.data_received(ptl, payload); ++} ++ ++static void ssh_ptl_send_ack(struct ssh_ptl *ptl, u8 seq) ++{ ++ struct ssh_packet_args args; ++ struct ssh_packet *packet; ++ struct ssam_span buf; ++ struct msgbuf msgb; ++ int status; ++ ++ status = ssh_ctrl_packet_alloc(&packet, &buf, GFP_KERNEL); ++ if (status) { ++ ptl_err(ptl, "ptl: failed to allocate ACK packet\n"); ++ return; ++ } ++ ++ args.type = 0; ++ args.priority = SSH_PACKET_PRIORITY(ACK, 0); ++ args.ops = &ssh_ptl_ctrl_packet_ops; ++ ssh_packet_init(packet, &args); ++ ++ msgb_init(&msgb, buf.ptr, buf.len); ++ msgb_push_ack(&msgb, seq); ++ ssh_packet_set_data(packet, msgb.begin, msgb_bytes_used(&msgb)); ++ ++ ssh_ptl_submit(ptl, packet); ++ ssh_packet_put(packet); ++} ++ ++static void ssh_ptl_send_nak(struct ssh_ptl *ptl) ++{ ++ struct ssh_packet_args args; ++ struct ssh_packet *packet; ++ struct ssam_span buf; ++ struct msgbuf msgb; ++ int status; ++ ++ status = ssh_ctrl_packet_alloc(&packet, &buf, GFP_KERNEL); ++ if (status) { ++ ptl_err(ptl, "ptl: failed to allocate NAK packet\n"); ++ return; ++ } ++ ++ args.type = 0; ++ args.priority = SSH_PACKET_PRIORITY(NAK, 0); ++ args.ops = &ssh_ptl_ctrl_packet_ops; ++ ssh_packet_init(packet, &args); ++ ++ msgb_init(&msgb, buf.ptr, buf.len); ++ msgb_push_nak(&msgb); ++ ssh_packet_set_data(packet, msgb.begin, msgb_bytes_used(&msgb)); ++ ++ ssh_ptl_submit(ptl, packet); ++ ssh_packet_put(packet); ++} ++ ++static size_t ssh_ptl_rx_eval(struct ssh_ptl *ptl, struct ssam_span *source) ++{ ++ struct ssh_frame *frame; ++ struct ssam_span payload; ++ struct ssam_span aligned; ++ bool syn_found; ++ int status; ++ ++ // error injection: modify data to simulate corrupt SYN bytes ++ ssh_ptl_rx_inject_invalid_syn(ptl, source); ++ ++ // find SYN ++ syn_found = sshp_find_syn(source, &aligned); ++ ++ if (unlikely(aligned.ptr - source->ptr) > 0) { ++ ptl_warn(ptl, "rx: parser: invalid start of frame, skipping\n"); ++ ++ /* ++ * Notes: ++ * - This might send multiple NAKs in case the communication ++ * starts with an invalid SYN and is broken down into multiple ++ * pieces. This should generally be handled fine, we just ++ * might receive duplicate data in this case, which is ++ * detected when handling data frames. ++ * - This path will also be executed on invalid CRCs: When an ++ * invalid CRC is encountered, the code below will skip data ++ * until direclty after the SYN. This causes the search for ++ * the next SYN, which is generally not placed directly after ++ * the last one. ++ */ ++ ssh_ptl_send_nak(ptl); ++ } ++ ++ if (unlikely(!syn_found)) ++ return aligned.ptr - source->ptr; ++ ++ // error injection: modify data to simulate corruption ++ ssh_ptl_rx_inject_invalid_data(ptl, &aligned); ++ ++ // parse and validate frame ++ status = sshp_parse_frame(&ptl->serdev->dev, &aligned, &frame, &payload, ++ SSH_PTL_RX_BUF_LEN); ++ if (status) // invalid frame: skip to next syn ++ return aligned.ptr - source->ptr + sizeof(u16); ++ if (!frame) // not enough data ++ return aligned.ptr - source->ptr; ++ ++ trace_ssam_rx_frame_received(frame); ++ ++ switch (frame->type) { ++ case SSH_FRAME_TYPE_ACK: ++ ssh_ptl_acknowledge(ptl, frame->seq); ++ break; ++ ++ case SSH_FRAME_TYPE_NAK: ++ ssh_ptl_resubmit_pending(ptl); ++ break; ++ ++ case SSH_FRAME_TYPE_DATA_SEQ: ++ ssh_ptl_send_ack(ptl, frame->seq); ++ /* fallthrough */ ++ ++ case SSH_FRAME_TYPE_DATA_NSQ: ++ ssh_ptl_rx_dataframe(ptl, frame, &payload); ++ break; ++ ++ default: ++ ptl_warn(ptl, "ptl: received frame with unknown type 0x%02x\n", ++ frame->type); ++ break; ++ } ++ ++ return aligned.ptr - source->ptr + SSH_MESSAGE_LENGTH(frame->len); ++} ++ ++static int ssh_ptl_rx_threadfn(void *data) ++{ ++ struct ssh_ptl *ptl = data; ++ ++ while (true) { ++ struct ssam_span span; ++ size_t offs = 0; ++ size_t n; ++ ++ wait_event_interruptible(ptl->rx.wq, ++ !kfifo_is_empty(&ptl->rx.fifo) ++ || kthread_should_stop()); ++ if (kthread_should_stop()) ++ break; ++ ++ // copy from fifo to evaluation buffer ++ n = sshp_buf_read_from_fifo(&ptl->rx.buf, &ptl->rx.fifo); ++ ++ ptl_dbg(ptl, "rx: received data (size: %zu)\n", n); ++ print_hex_dump_debug("rx: ", DUMP_PREFIX_OFFSET, 16, 1, ++ ptl->rx.buf.ptr + ptl->rx.buf.len - n, ++ n, false); ++ ++ // parse until we need more bytes or buffer is empty ++ while (offs < ptl->rx.buf.len) { ++ sshp_buf_span_from(&ptl->rx.buf, offs, &span); ++ n = ssh_ptl_rx_eval(ptl, &span); ++ if (n == 0) ++ break; // need more bytes ++ ++ offs += n; ++ } ++ ++ // throw away the evaluated parts ++ sshp_buf_drop(&ptl->rx.buf, offs); ++ } ++ ++ return 0; ++} ++ ++static inline void ssh_ptl_rx_wakeup(struct ssh_ptl *ptl) ++{ ++ wake_up(&ptl->rx.wq); ++} ++ ++int ssh_ptl_rx_start(struct ssh_ptl *ptl) ++{ ++ if (ptl->rx.thread) ++ return 0; ++ ++ ptl->rx.thread = kthread_run(ssh_ptl_rx_threadfn, ptl, "surface-sh-rx"); ++ if (IS_ERR(ptl->rx.thread)) ++ return PTR_ERR(ptl->rx.thread); ++ ++ return 0; ++} ++ ++static int ssh_ptl_rx_stop(struct ssh_ptl *ptl) ++{ ++ int status = 0; ++ ++ if (ptl->rx.thread) { ++ status = kthread_stop(ptl->rx.thread); ++ ptl->rx.thread = NULL; ++ } ++ ++ return status; ++} ++ ++int ssh_ptl_rx_rcvbuf(struct ssh_ptl *ptl, const u8 *buf, size_t n) ++{ ++ int used; ++ ++ if (test_bit(SSH_PTL_SF_SHUTDOWN_BIT, &ptl->state)) ++ return -ESHUTDOWN; ++ ++ used = kfifo_in(&ptl->rx.fifo, buf, n); ++ if (used) ++ ssh_ptl_rx_wakeup(ptl); ++ ++ return used; ++} ++ ++ ++/** ++ * ssh_ptl_shutdown - shut down the packet transmission layer ++ * @ptl: packet transmission layer ++ * ++ * Shuts down the packet transmission layer, removing and canceling all queued ++ * and pending packets. Packets canceled by this operation will be completed ++ * with -ESHUTDOWN as status. ++ * ++ * As a result of this function, the transmission layer will be marked as shut ++ * down. Submission of packets after the transmission layer has been shut down ++ * will fail with -ESHUTDOWN. ++ */ ++void ssh_ptl_shutdown(struct ssh_ptl *ptl) ++{ ++ LIST_HEAD(complete_q); ++ LIST_HEAD(complete_p); ++ struct ssh_packet *p, *n; ++ int status; ++ ++ // ensure that no new packets (including ACK/NAK) can be submitted ++ set_bit(SSH_PTL_SF_SHUTDOWN_BIT, &ptl->state); ++ smp_mb__after_atomic(); ++ ++ status = ssh_ptl_rx_stop(ptl); ++ if (status) ++ ptl_err(ptl, "ptl: failed to stop receiver thread\n"); ++ ++ status = ssh_ptl_tx_stop(ptl); ++ if (status) ++ ptl_err(ptl, "ptl: failed to stop transmitter thread\n"); ++ ++ cancel_delayed_work_sync(&ptl->rtx_timeout.reaper); ++ ++ /* ++ * At this point, all threads have been stopped. This means that the ++ * only references to packets from inside the system are in the queue ++ * and pending set. ++ * ++ * Note: We still need locks here because someone could still be ++ * cancelling packets. ++ * ++ * Note 2: We can re-use queue_node (or pending_node) if we mark the ++ * packet as locked an then remove it from the queue (or pending set ++ * respecitvely). Marking the packet as locked avoids re-queueing ++ * (which should already be prevented by having stopped the treads...) ++ * and not setting QUEUED_BIT (or PENDING_BIT) prevents removal from a ++ * new list via other threads (e.g. canellation). ++ * ++ * Note 3: There may be overlap between complete_p and complete_q. ++ * This is handled via test_and_set_bit on the "completed" flag ++ * (also handles cancelation). ++ */ ++ ++ // mark queued packets as locked and move them to complete_q ++ spin_lock(&ptl->queue.lock); ++ list_for_each_entry_safe(p, n, &ptl->queue.head, queue_node) { ++ set_bit(SSH_PACKET_SF_LOCKED_BIT, &p->state); ++ smp_mb__before_atomic(); ++ clear_bit(SSH_PACKET_SF_QUEUED_BIT, &p->state); ++ ++ list_del(&p->queue_node); ++ list_add_tail(&p->queue_node, &complete_q); ++ } ++ spin_unlock(&ptl->queue.lock); ++ ++ // mark pending packets as locked and move them to complete_p ++ spin_lock(&ptl->pending.lock); ++ list_for_each_entry_safe(p, n, &ptl->pending.head, pending_node) { ++ set_bit(SSH_PACKET_SF_LOCKED_BIT, &p->state); ++ smp_mb__before_atomic(); ++ clear_bit(SSH_PACKET_SF_PENDING_BIT, &p->state); ++ ++ list_del(&p->pending_node); ++ list_add_tail(&p->pending_node, &complete_q); ++ } ++ atomic_set(&ptl->pending.count, 0); ++ spin_unlock(&ptl->pending.lock); ++ ++ // complete and drop packets on complete_q ++ list_for_each_entry(p, &complete_q, queue_node) { ++ if (!test_and_set_bit(SSH_PACKET_SF_COMPLETED_BIT, &p->state)) ++ __ssh_ptl_complete(p, -ESHUTDOWN); ++ ++ ssh_packet_put(p); ++ } ++ ++ // complete and drop packets on complete_p ++ list_for_each_entry(p, &complete_p, pending_node) { ++ if (!test_and_set_bit(SSH_PACKET_SF_COMPLETED_BIT, &p->state)) ++ __ssh_ptl_complete(p, -ESHUTDOWN); ++ ++ ssh_packet_put(p); ++ } ++ ++ /* ++ * At this point we have guaranteed that the system doesn't reference ++ * any packets any more. ++ */ ++} ++ ++int ssh_ptl_init(struct ssh_ptl *ptl, struct serdev_device *serdev, ++ struct ssh_ptl_ops *ops) ++{ ++ int i, status; ++ ++ ptl->serdev = serdev; ++ ptl->state = 0; ++ ++ spin_lock_init(&ptl->queue.lock); ++ INIT_LIST_HEAD(&ptl->queue.head); ++ ++ spin_lock_init(&ptl->pending.lock); ++ INIT_LIST_HEAD(&ptl->pending.head); ++ atomic_set_release(&ptl->pending.count, 0); ++ ++ ptl->tx.thread = NULL; ++ ptl->tx.thread_signal = false; ++ ptl->tx.packet = NULL; ++ ptl->tx.offset = 0; ++ init_waitqueue_head(&ptl->tx.thread_wq); ++ init_waitqueue_head(&ptl->tx.packet_wq); ++ ++ ptl->rx.thread = NULL; ++ init_waitqueue_head(&ptl->rx.wq); ++ ++ ptl->rtx_timeout.timeout = SSH_PTL_PACKET_TIMEOUT; ++ ptl->rtx_timeout.expires = KTIME_MAX; ++ INIT_DELAYED_WORK(&ptl->rtx_timeout.reaper, ssh_ptl_timeout_reap); ++ ++ ptl->ops = *ops; ++ ++ // initialize list of recent/blocked SEQs with invalid sequence IDs ++ for (i = 0; i < ARRAY_SIZE(ptl->rx.blocked.seqs); i++) ++ ptl->rx.blocked.seqs[i] = 0xFFFF; ++ ptl->rx.blocked.offset = 0; ++ ++ status = kfifo_alloc(&ptl->rx.fifo, SSH_PTL_RX_FIFO_LEN, GFP_KERNEL); ++ if (status) ++ return status; ++ ++ status = sshp_buf_alloc(&ptl->rx.buf, SSH_PTL_RX_BUF_LEN, GFP_KERNEL); ++ if (status) ++ kfifo_free(&ptl->rx.fifo); ++ ++ return status; ++} ++ ++void ssh_ptl_destroy(struct ssh_ptl *ptl) ++{ ++ kfifo_free(&ptl->rx.fifo); ++ sshp_buf_free(&ptl->rx.buf); ++} +diff --git a/drivers/misc/surface_sam/ssh_packet_layer.h b/drivers/misc/surface_sam/ssh_packet_layer.h +new file mode 100644 +index 0000000000000..e819243eb3fe4 +--- /dev/null ++++ b/drivers/misc/surface_sam/ssh_packet_layer.h +@@ -0,0 +1,125 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++ ++#ifndef _SSAM_SSH_PACKET_LAYER_H ++#define _SSAM_SSH_PACKET_LAYER_H ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++ ++#include "ssh_msgb.h" ++#include "ssh_parser.h" ++ ++ ++enum ssh_ptl_state_flags { ++ SSH_PTL_SF_SHUTDOWN_BIT, ++}; ++ ++struct ssh_ptl_ops { ++ void (*data_received)(struct ssh_ptl *p, const struct ssam_span *data); ++}; ++ ++struct ssh_ptl { ++ struct serdev_device *serdev; ++ unsigned long state; ++ ++ struct { ++ spinlock_t lock; ++ struct list_head head; ++ } queue; ++ ++ struct { ++ spinlock_t lock; ++ struct list_head head; ++ atomic_t count; ++ } pending; ++ ++ struct { ++ bool thread_signal; ++ struct task_struct *thread; ++ struct wait_queue_head thread_wq; ++ struct wait_queue_head packet_wq; ++ struct ssh_packet *packet; ++ size_t offset; ++ } tx; ++ ++ struct { ++ struct task_struct *thread; ++ struct wait_queue_head wq; ++ struct kfifo fifo; ++ struct sshp_buf buf; ++ ++ struct { ++ u16 seqs[8]; ++ u16 offset; ++ } blocked; ++ } rx; ++ ++ struct { ++ ktime_t timeout; ++ ktime_t expires; ++ struct delayed_work reaper; ++ } rtx_timeout; ++ ++ struct ssh_ptl_ops ops; ++}; ++ ++struct ssh_packet_args { ++ unsigned long type; ++ u8 priority; ++ const struct ssh_packet_ops *ops; ++}; ++ ++ ++#define __ssam_prcond(func, p, fmt, ...) \ ++ do { \ ++ if ((p)) \ ++ func((p), fmt, ##__VA_ARGS__); \ ++ } while (0); ++ ++#define ptl_dbg(p, fmt, ...) dev_dbg(&(p)->serdev->dev, fmt, ##__VA_ARGS__) ++#define ptl_info(p, fmt, ...) dev_info(&(p)->serdev->dev, fmt, ##__VA_ARGS__) ++#define ptl_warn(p, fmt, ...) dev_warn(&(p)->serdev->dev, fmt, ##__VA_ARGS__) ++#define ptl_err(p, fmt, ...) dev_err(&(p)->serdev->dev, fmt, ##__VA_ARGS__) ++#define ptl_dbg_cond(p, fmt, ...) __ssam_prcond(ptl_dbg, p, fmt, ##__VA_ARGS__) ++ ++#define to_ssh_ptl(ptr, member) \ ++ container_of(ptr, struct ssh_ptl, member) ++ ++ ++int ssh_ptl_init(struct ssh_ptl *ptl, struct serdev_device *serdev, ++ struct ssh_ptl_ops *ops); ++ ++void ssh_ptl_destroy(struct ssh_ptl *ptl); ++ ++static inline struct device *ssh_ptl_get_device(struct ssh_ptl *ptl) ++{ ++ return ptl->serdev ? &ptl->serdev->dev : NULL; ++} ++ ++int ssh_ptl_tx_start(struct ssh_ptl *ptl); ++int ssh_ptl_rx_start(struct ssh_ptl *ptl); ++void ssh_ptl_shutdown(struct ssh_ptl *ptl); ++ ++int ssh_ptl_submit(struct ssh_ptl *ptl, struct ssh_packet *p); ++void ssh_ptl_cancel(struct ssh_packet *p); ++ ++int ssh_ptl_rx_rcvbuf(struct ssh_ptl *ptl, const u8 *buf, size_t n); ++void ssh_ptl_tx_wakeup(struct ssh_ptl *ptl, bool force); ++ ++void ssh_packet_init(struct ssh_packet *packet, ++ const struct ssh_packet_args *args); ++ ++int ssh_ctrl_packet_cache_init(void); ++void ssh_ctrl_packet_cache_destroy(void); ++ ++#endif /* _SSAM_SSH_PACKET_LAYER_H */ +diff --git a/drivers/misc/surface_sam/ssh_parser.c b/drivers/misc/surface_sam/ssh_parser.c +new file mode 100644 +index 0000000000000..365e26b57838c +--- /dev/null ++++ b/drivers/misc/surface_sam/ssh_parser.c +@@ -0,0 +1,132 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++ ++#include ++ ++#include "ssh_parser.h" ++#include "ssh_protocol.h" ++ ++ ++static inline bool sshp_validate_crc(const struct ssam_span *src, const u8 *crc) ++{ ++ u16 actual = ssh_crc(src->ptr, src->len); ++ u16 expected = get_unaligned_le16(crc); ++ ++ return actual == expected; ++} ++ ++static inline bool sshp_starts_with_syn(const struct ssam_span *src) ++{ ++ return src->len >= 2 && get_unaligned_le16(src->ptr) == SSH_MSG_SYN; ++} ++ ++bool sshp_find_syn(const struct ssam_span *src, struct ssam_span *rem) ++{ ++ size_t i; ++ ++ for (i = 0; i < src->len - 1; i++) { ++ if (likely(get_unaligned_le16(src->ptr + i) == SSH_MSG_SYN)) { ++ rem->ptr = src->ptr + i; ++ rem->len = src->len - i; ++ return true; ++ } ++ } ++ ++ if (unlikely(src->ptr[src->len - 1] == (SSH_MSG_SYN & 0xff))) { ++ rem->ptr = src->ptr + src->len - 1; ++ rem->len = 1; ++ return false; ++ } else { ++ rem->ptr = src->ptr + src->len; ++ rem->len = 0; ++ return false; ++ } ++} ++ ++int sshp_parse_frame(const struct device *dev, const struct ssam_span *source, ++ struct ssh_frame **frame, struct ssam_span *payload, ++ size_t maxlen) ++{ ++ struct ssam_span sf; ++ struct ssam_span sp; ++ ++ // initialize output ++ *frame = NULL; ++ payload->ptr = NULL; ++ payload->len = 0; ++ ++ if (!sshp_starts_with_syn(source)) { ++ dev_warn(dev, "rx: parser: invalid start of frame\n"); ++ return -ENOMSG; ++ } ++ ++ // check for minumum packet length ++ if (unlikely(source->len < SSH_MESSAGE_LENGTH(0))) { ++ dev_dbg(dev, "rx: parser: not enough data for frame\n"); ++ return 0; ++ } ++ ++ // pin down frame ++ sf.ptr = source->ptr + sizeof(u16); ++ sf.len = sizeof(struct ssh_frame); ++ ++ // validate frame CRC ++ if (unlikely(!sshp_validate_crc(&sf, sf.ptr + sf.len))) { ++ dev_warn(dev, "rx: parser: invalid frame CRC\n"); ++ return -EBADMSG; ++ } ++ ++ // ensure packet does not exceed maximum length ++ if (unlikely(((struct ssh_frame *)sf.ptr)->len > maxlen)) { ++ dev_warn(dev, "rx: parser: frame too large: %u bytes\n", ++ ((struct ssh_frame *)sf.ptr)->len); ++ return -EMSGSIZE; ++ } ++ ++ // pin down payload ++ sp.ptr = sf.ptr + sf.len + sizeof(u16); ++ sp.len = get_unaligned_le16(&((struct ssh_frame *)sf.ptr)->len); ++ ++ // check for frame + payload length ++ if (source->len < SSH_MESSAGE_LENGTH(sp.len)) { ++ dev_dbg(dev, "rx: parser: not enough data for payload\n"); ++ return 0; ++ } ++ ++ // validate payload crc ++ if (unlikely(!sshp_validate_crc(&sp, sp.ptr + sp.len))) { ++ dev_warn(dev, "rx: parser: invalid payload CRC\n"); ++ return -EBADMSG; ++ } ++ ++ *frame = (struct ssh_frame *)sf.ptr; ++ *payload = sp; ++ ++ dev_dbg(dev, "rx: parser: valid frame found (type: 0x%02x, len: %u)\n", ++ (*frame)->type, (*frame)->len); ++ ++ return 0; ++} ++ ++int sshp_parse_command(const struct device *dev, const struct ssam_span *source, ++ struct ssh_command **command, ++ struct ssam_span *command_data) ++{ ++ // check for minimum length ++ if (unlikely(source->len < sizeof(struct ssh_command))) { ++ *command = NULL; ++ command_data->ptr = NULL; ++ command_data->len = 0; ++ ++ dev_err(dev, "rx: parser: command payload is too short\n"); ++ return -ENOMSG; ++ } ++ ++ *command = (struct ssh_command *)source->ptr; ++ command_data->ptr = source->ptr + sizeof(struct ssh_command); ++ command_data->len = source->len - sizeof(struct ssh_command); ++ ++ dev_dbg(dev, "rx: parser: valid command found (tc: 0x%02x," ++ " cid: 0x%02x)\n", (*command)->tc, (*command)->cid); ++ ++ return 0; ++} +diff --git a/drivers/misc/surface_sam/ssh_parser.h b/drivers/misc/surface_sam/ssh_parser.h +new file mode 100644 +index 0000000000000..27a684de4503b +--- /dev/null ++++ b/drivers/misc/surface_sam/ssh_parser.h +@@ -0,0 +1,83 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++ ++#ifndef _SURFACE_SAM_SSH_PARSER_H ++#define _SURFACE_SAM_SSH_PARSER_H ++ ++#include ++#include ++#include ++ ++#include ++ ++ ++struct sshp_buf { ++ u8 *ptr; ++ size_t len; ++ size_t cap; ++}; ++ ++ ++static inline void sshp_buf_init(struct sshp_buf *buf, u8 *ptr, size_t cap) ++{ ++ buf->ptr = ptr; ++ buf->len = 0; ++ buf->cap = cap; ++} ++ ++static inline int sshp_buf_alloc(struct sshp_buf *buf, size_t cap, gfp_t flags) ++{ ++ u8 *ptr; ++ ++ ptr = kzalloc(cap, flags); ++ if (!ptr) ++ return -ENOMEM; ++ ++ sshp_buf_init(buf, ptr, cap); ++ return 0; ++ ++} ++ ++static inline void sshp_buf_free(struct sshp_buf *buf) ++{ ++ kfree(buf->ptr); ++ buf->ptr = NULL; ++ buf->len = 0; ++ buf->cap = 0; ++} ++ ++static inline void sshp_buf_drop(struct sshp_buf *buf, size_t n) ++{ ++ memmove(buf->ptr, buf->ptr + n, buf->len - n); ++ buf->len -= n; ++} ++ ++static inline size_t sshp_buf_read_from_fifo(struct sshp_buf *buf, ++ struct kfifo *fifo) ++{ ++ size_t n; ++ ++ n = kfifo_out(fifo, buf->ptr + buf->len, buf->cap - buf->len); ++ buf->len += n; ++ ++ return n; ++} ++ ++static inline void sshp_buf_span_from(struct sshp_buf *buf, size_t offset, ++ struct ssam_span *span) ++{ ++ span->ptr = buf->ptr + offset; ++ span->len = buf->len - offset; ++} ++ ++ ++bool sshp_find_syn(const struct ssam_span *src, struct ssam_span *rem); ++ ++int sshp_parse_frame(const struct device *dev, const struct ssam_span *source, ++ struct ssh_frame **frame, struct ssam_span *payload, ++ size_t maxlen); ++ ++int sshp_parse_command(const struct device *dev, const struct ssam_span *source, ++ struct ssh_command **command, ++ struct ssam_span *command_data); ++ ++#endif /* _SURFACE_SAM_SSH_PARSER_h */ +diff --git a/drivers/misc/surface_sam/ssh_protocol.h b/drivers/misc/surface_sam/ssh_protocol.h +new file mode 100644 +index 0000000000000..21bcbc71f4441 +--- /dev/null ++++ b/drivers/misc/surface_sam/ssh_protocol.h +@@ -0,0 +1,65 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++ ++#ifndef _SSAM_SSH_PROTOCOL_H ++#define _SSAM_SSH_PROTOCOL_H ++ ++#include ++#include ++#include ++ ++ ++/* ++ * The number of reserved event IDs, used for registering an SSH event ++ * handler. Valid event IDs are numbers below or equal to this value, with ++ * exception of zero, which is not an event ID. Thus, this is also the ++ * absolute maximum number of event handlers that can be registered. ++ */ ++#define SSH_NUM_EVENTS 34 ++ ++/* ++ * The number of communication channels used in the protocol. ++ */ ++#define SSH_NUM_CHANNELS 2 ++ ++/** ++ * SSH message syncrhonization (SYN) bytes. ++ */ ++#define SSH_MSG_SYN ((u16)0x55aa) ++ ++ ++static inline u16 ssh_crc(const u8 *buf, size_t len) ++{ ++ return crc_ccitt_false(0xffff, buf, len); ++} ++ ++static inline u16 ssh_rqid_next_valid(u16 rqid) ++{ ++ return rqid > 0 ? rqid + 1u : rqid + SSH_NUM_EVENTS + 1u; ++} ++ ++static inline u16 ssh_rqid_to_event(u16 rqid) ++{ ++ return rqid - 1u; ++} ++ ++static inline bool ssh_rqid_is_event(u16 rqid) ++{ ++ return ssh_rqid_to_event(rqid) < SSH_NUM_EVENTS; ++} ++ ++static inline int ssh_tc_to_rqid(u8 tc) ++{ ++ return tc; ++} ++ ++static inline u8 ssh_channel_to_index(u8 channel) ++{ ++ return channel - 1u; ++} ++ ++static inline bool ssh_channel_is_valid(u8 channel) ++{ ++ return ssh_channel_to_index(channel) < SSH_NUM_CHANNELS; ++} ++ ++#endif /* _SSAM_SSH_PROTOCOL_H */ +diff --git a/drivers/misc/surface_sam/ssh_request_layer.c b/drivers/misc/surface_sam/ssh_request_layer.c +new file mode 100644 +index 0000000000000..419fbd0aa4c50 +--- /dev/null ++++ b/drivers/misc/surface_sam/ssh_request_layer.c +@@ -0,0 +1,1074 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++ ++#include "ssh_packet_layer.h" ++#include "ssh_request_layer.h" ++ ++#include "ssam_trace.h" ++ ++ ++#define SSH_RTL_REQUEST_TIMEOUT ms_to_ktime(3000) ++#define SSH_RTL_REQUEST_TIMEOUT_RESOLUTION ms_to_ktime(max(2000 / HZ, 50)) ++ ++#define SSH_RTL_MAX_PENDING 3 ++ ++ ++#ifdef CONFIG_SURFACE_SAM_SSH_ERROR_INJECTION ++ ++/** ++ * ssh_rtl_should_drop_response - error injection hook to drop request responses ++ * ++ * Useful to cause request transmission timeouts in the driver by dropping the ++ * response to a request. ++ */ ++static noinline bool ssh_rtl_should_drop_response(void) ++{ ++ return false; ++} ++ALLOW_ERROR_INJECTION(ssh_rtl_should_drop_response, TRUE); ++ ++#else ++ ++static inline bool ssh_rtl_should_drop_response(void) ++{ ++ return false; ++} ++ ++#endif ++ ++ ++static inline u16 ssh_request_get_rqid(struct ssh_request *rqst) ++{ ++ return get_unaligned_le16(rqst->packet.data.ptr ++ + SSH_MSGOFFSET_COMMAND(rqid)); ++} ++ ++static inline u32 ssh_request_get_rqid_safe(struct ssh_request *rqst) ++{ ++ if (!rqst->packet.data.ptr) ++ return -1; ++ ++ return ssh_request_get_rqid(rqst); ++} ++ ++ ++static void ssh_rtl_queue_remove(struct ssh_request *rqst) ++{ ++ struct ssh_rtl *rtl = ssh_request_rtl(rqst); ++ bool remove; ++ ++ spin_lock(&rtl->queue.lock); ++ ++ remove = test_and_clear_bit(SSH_REQUEST_SF_QUEUED_BIT, &rqst->state); ++ if (remove) ++ list_del(&rqst->node); ++ ++ spin_unlock(&rtl->queue.lock); ++ ++ if (remove) ++ ssh_request_put(rqst); ++} ++ ++static bool ssh_rtl_queue_empty(struct ssh_rtl *rtl) ++{ ++ bool empty; ++ ++ spin_lock(&rtl->queue.lock); ++ empty = list_empty(&rtl->queue.head); ++ spin_unlock(&rtl->queue.lock); ++ ++ return empty; ++} ++ ++ ++static void ssh_rtl_pending_remove(struct ssh_request *rqst) ++{ ++ struct ssh_rtl *rtl = ssh_request_rtl(rqst); ++ bool remove; ++ ++ spin_lock(&rtl->pending.lock); ++ ++ remove = test_and_clear_bit(SSH_REQUEST_SF_PENDING_BIT, &rqst->state); ++ if (remove) { ++ atomic_dec(&rtl->pending.count); ++ list_del(&rqst->node); ++ } ++ ++ spin_unlock(&rtl->pending.lock); ++ ++ if (remove) ++ ssh_request_put(rqst); ++} ++ ++static int ssh_rtl_tx_pending_push(struct ssh_request *rqst) ++{ ++ struct ssh_rtl *rtl = ssh_request_rtl(rqst); ++ ++ spin_lock(&rtl->pending.lock); ++ ++ if (test_bit(SSH_REQUEST_SF_LOCKED_BIT, &rqst->state)) { ++ spin_unlock(&rtl->pending.lock); ++ return -EINVAL; ++ } ++ ++ if (test_and_set_bit(SSH_REQUEST_SF_PENDING_BIT, &rqst->state)) { ++ spin_unlock(&rtl->pending.lock); ++ return -EALREADY; ++ } ++ ++ atomic_inc(&rtl->pending.count); ++ list_add_tail(&ssh_request_get(rqst)->node, &rtl->pending.head); ++ ++ spin_unlock(&rtl->pending.lock); + return 0; +} + + -+static const struct acpi_device_id surface_sam_vhf_match[] = { -+ { "MSHW0096" }, -+ { }, -+}; -+MODULE_DEVICE_TABLE(acpi, surface_sam_vhf_match); ++static void ssh_rtl_complete_with_status(struct ssh_request *rqst, int status) ++{ ++ struct ssh_rtl *rtl = ssh_request_rtl(rqst); + -+static struct platform_driver surface_sam_vhf = { -+ .probe = surface_sam_vhf_probe, -+ .remove = surface_sam_vhf_remove, -+ .driver = { -+ .name = "surface_sam_vhf", -+ .acpi_match_table = surface_sam_vhf_match, -+ .probe_type = PROBE_PREFER_ASYNCHRONOUS, -+ }, -+}; -+module_platform_driver(surface_sam_vhf); ++ trace_ssam_request_complete(rqst, status); + -+MODULE_AUTHOR("Maximilian Luz "); -+MODULE_DESCRIPTION("Virtual HID Framework Driver for 5th Generation Surface Devices"); -+MODULE_LICENSE("GPL"); ++ // rtl/ptl may not be set if we're cancelling before submitting ++ rtl_dbg_cond(rtl, "rtl: completing request (rqid: 0x%04x," ++ " status: %d)\n", ssh_request_get_rqid_safe(rqst), status); ++ ++ if (status && status != -ECANCELED) ++ rtl_dbg_cond(rtl, "rtl: request error: %d\n", status); ++ ++ rqst->ops->complete(rqst, NULL, NULL, status); ++} ++ ++static void ssh_rtl_complete_with_rsp(struct ssh_request *rqst, ++ const struct ssh_command *cmd, ++ const struct ssam_span *data) ++{ ++ struct ssh_rtl *rtl = ssh_request_rtl(rqst); ++ ++ trace_ssam_request_complete(rqst, 0); ++ ++ rtl_dbg(rtl, "rtl: completing request with response" ++ " (rqid: 0x%04x)\n", ssh_request_get_rqid(rqst)); ++ ++ rqst->ops->complete(rqst, cmd, data, 0); ++} ++ ++ ++static bool ssh_rtl_tx_can_process(struct ssh_request *rqst) ++{ ++ struct ssh_rtl *rtl = ssh_request_rtl(rqst); ++ ++ if (test_bit(SSH_REQUEST_TY_FLUSH_BIT, &rqst->state)) ++ return !atomic_read(&rtl->pending.count); ++ ++ return atomic_read(&rtl->pending.count) < SSH_RTL_MAX_PENDING; ++} ++ ++static struct ssh_request *ssh_rtl_tx_next(struct ssh_rtl *rtl) ++{ ++ struct ssh_request *rqst = ERR_PTR(-ENOENT); ++ struct ssh_request *p, *n; ++ ++ spin_lock(&rtl->queue.lock); ++ ++ // find first non-locked request and remove it ++ list_for_each_entry_safe(p, n, &rtl->queue.head, node) { ++ if (unlikely(test_bit(SSH_REQUEST_SF_LOCKED_BIT, &p->state))) ++ continue; ++ ++ if (!ssh_rtl_tx_can_process(p)) { ++ rqst = ERR_PTR(-EBUSY); ++ break; ++ } ++ ++ /* ++ * Remove from queue and mark as transmitting. Ensure that the ++ * state does not get zero via memory barrier. ++ */ ++ set_bit(SSH_REQUEST_SF_TRANSMITTING_BIT, &p->state); ++ smp_mb__before_atomic(); ++ clear_bit(SSH_REQUEST_SF_QUEUED_BIT, &p->state); ++ ++ list_del(&p->node); ++ ++ rqst = p; ++ break; ++ } ++ ++ spin_unlock(&rtl->queue.lock); ++ return rqst; ++} ++ ++static int ssh_rtl_tx_try_process_one(struct ssh_rtl *rtl) ++{ ++ struct ssh_request *rqst; ++ int status; ++ ++ // get and prepare next request for transmit ++ rqst = ssh_rtl_tx_next(rtl); ++ if (IS_ERR(rqst)) ++ return PTR_ERR(rqst); ++ ++ // add to/mark as pending ++ status = ssh_rtl_tx_pending_push(rqst); ++ if (status) { ++ ssh_request_put(rqst); ++ return -EAGAIN; ++ } ++ ++ // submit packet ++ status = ssh_ptl_submit(&rtl->ptl, &rqst->packet); ++ if (status == -ESHUTDOWN) { ++ /* ++ * Packet has been refused due to the packet layer shutting ++ * down. Complete it here. ++ */ ++ set_bit(SSH_REQUEST_SF_LOCKED_BIT, &rqst->state); ++ smp_mb__after_atomic(); ++ ++ ssh_rtl_pending_remove(rqst); ++ ssh_rtl_complete_with_status(rqst, -ESHUTDOWN); ++ ++ ssh_request_put(rqst); ++ return -ESHUTDOWN; ++ ++ } else if (status) { ++ /* ++ * If submitting the packet failed and the packet layer isn't ++ * shutting down, the packet has either been submmitted/queued ++ * before (-EALREADY, which cannot happen as we have guaranteed ++ * that requests cannot be re-submitted), or the packet was ++ * marked as locked (-EINVAL). To mark the packet locked at this ++ * stage, the request, and thus the packets itself, had to have ++ * been canceled. Simply drop the reference. Cancellation itself ++ * will remove it from the set of pending requests. ++ */ ++ ++ WARN_ON(status != -EINVAL); ++ ++ ssh_request_put(rqst); ++ return -EAGAIN; ++ } ++ ++ ssh_request_put(rqst); ++ return 0; ++} ++ ++static bool ssh_rtl_tx_schedule(struct ssh_rtl *rtl) ++{ ++ if (atomic_read(&rtl->pending.count) >= SSH_RTL_MAX_PENDING) ++ return false; ++ ++ if (ssh_rtl_queue_empty(rtl)) ++ return false; ++ ++ return schedule_work(&rtl->tx.work); ++} ++ ++static void ssh_rtl_tx_work_fn(struct work_struct *work) ++{ ++ struct ssh_rtl *rtl = to_ssh_rtl(work, tx.work); ++ int i, status; ++ ++ /* ++ * Try to be nice and not block the workqueue: Run a maximum of 10 ++ * tries, then re-submit if necessary. This should not be neccesary, ++ * for normal execution, but guarantee it anyway. ++ */ ++ for (i = 0; i < 10; i++) { ++ status = ssh_rtl_tx_try_process_one(rtl); ++ if (status == -ENOENT || status == -EBUSY) ++ return; // no more requests to process ++ ++ if (status == -ESHUTDOWN) { ++ /* ++ * Packet system shutting down. No new packets can be ++ * transmitted. Return silently, the party initiating ++ * the shutdown should handle the rest. ++ */ ++ return; ++ } ++ ++ WARN_ON(status != 0 && status != -EAGAIN); ++ } ++ ++ // out of tries, reschedule ++ ssh_rtl_tx_schedule(rtl); ++} ++ ++ ++int ssh_rtl_submit(struct ssh_rtl *rtl, struct ssh_request *rqst) ++{ ++ trace_ssam_request_submit(rqst); ++ ++ /* ++ * Ensure that requests expecting a response are sequenced. If this ++ * invariant ever changes, see the comment in ssh_rtl_complete on what ++ * is required to be changed in the code. ++ */ ++ if (test_bit(SSH_REQUEST_TY_HAS_RESPONSE_BIT, &rqst->state)) ++ if (!test_bit(SSH_PACKET_TY_SEQUENCED_BIT, &rqst->packet.state)) ++ return -EINVAL; ++ ++ // try to set ptl and check if this request has already been submitted ++ if (cmpxchg(&rqst->packet.ptl, NULL, &rtl->ptl) != NULL) ++ return -EALREADY; ++ ++ spin_lock(&rtl->queue.lock); ++ ++ if (test_bit(SSH_RTL_SF_SHUTDOWN_BIT, &rtl->state)) { ++ spin_unlock(&rtl->queue.lock); ++ return -ESHUTDOWN; ++ } ++ ++ if (test_bit(SSH_REQUEST_SF_LOCKED_BIT, &rqst->state)) { ++ spin_unlock(&rtl->queue.lock); ++ return -EINVAL; ++ } ++ ++ set_bit(SSH_REQUEST_SF_QUEUED_BIT, &rqst->state); ++ list_add_tail(&ssh_request_get(rqst)->node, &rtl->queue.head); ++ ++ spin_unlock(&rtl->queue.lock); ++ ++ ssh_rtl_tx_schedule(rtl); ++ return 0; ++} ++ ++static void ssh_rtl_timeout_reaper_mod(struct ssh_rtl *rtl, ktime_t now, ++ ktime_t expires) ++{ ++ unsigned long delta = msecs_to_jiffies(ktime_ms_delta(expires, now)); ++ ktime_t aexp = ktime_add(expires, SSH_RTL_REQUEST_TIMEOUT_RESOLUTION); ++ ktime_t old; ++ ++ // re-adjust / schedule reaper if it is above resolution delta ++ old = READ_ONCE(rtl->rtx_timeout.expires); ++ while (ktime_before(aexp, old)) ++ old = cmpxchg64(&rtl->rtx_timeout.expires, old, expires); ++ ++ // if we updated the reaper expiration, modify work timeout ++ if (old == expires) ++ mod_delayed_work(system_wq, &rtl->rtx_timeout.reaper, delta); ++} ++ ++static void ssh_rtl_timeout_start(struct ssh_request *rqst) ++{ ++ struct ssh_rtl *rtl = ssh_request_rtl(rqst); ++ ktime_t timestamp = ktime_get_coarse_boottime(); ++ ktime_t timeout = rtl->rtx_timeout.timeout; ++ ++ if (test_bit(SSH_REQUEST_SF_LOCKED_BIT, &rqst->state)) ++ return; ++ ++ WRITE_ONCE(rqst->timestamp, timestamp); ++ smp_mb__after_atomic(); ++ ++ ssh_rtl_timeout_reaper_mod(rtl, timestamp, timestamp + timeout); ++} ++ ++ ++static void ssh_rtl_complete(struct ssh_rtl *rtl, ++ const struct ssh_command *command, ++ const struct ssam_span *command_data) ++{ ++ struct ssh_request *r = NULL; ++ struct ssh_request *p, *n; ++ u16 rqid = get_unaligned_le16(&command->rqid); ++ ++ trace_ssam_rx_response_received(command, command_data->len); ++ ++ /* ++ * Get request from pending based on request ID and mark it as response ++ * received and locked. ++ */ ++ spin_lock(&rtl->pending.lock); ++ list_for_each_entry_safe(p, n, &rtl->pending.head, node) { ++ // we generally expect requests to be processed in order ++ if (unlikely(ssh_request_get_rqid(p) != rqid)) ++ continue; ++ ++ // simulate response timeout ++ if (ssh_rtl_should_drop_response()) { ++ spin_unlock(&rtl->pending.lock); ++ ++ trace_ssam_ei_rx_drop_response(p); ++ rtl_info(rtl, "request error injection: " ++ "dropping response for request %p\n", ++ &p->packet); ++ return; ++ } ++ ++ /* ++ * Mark as "response received" and "locked" as we're going to ++ * complete it. Ensure that the state doesn't get zero by ++ * employing a memory barrier. ++ */ ++ set_bit(SSH_REQUEST_SF_LOCKED_BIT, &p->state); ++ set_bit(SSH_REQUEST_SF_RSPRCVD_BIT, &p->state); ++ smp_mb__before_atomic(); ++ clear_bit(SSH_REQUEST_SF_PENDING_BIT, &p->state); ++ ++ atomic_dec(&rtl->pending.count); ++ list_del(&p->node); ++ ++ r = p; ++ break; ++ } ++ spin_unlock(&rtl->pending.lock); ++ ++ if (!r) { ++ rtl_warn(rtl, "rtl: dropping unexpected command message" ++ " (rqid = 0x%04x)\n", rqid); ++ return; ++ } ++ ++ // if the request hasn't been completed yet, we will do this now ++ if (test_and_set_bit(SSH_REQUEST_SF_COMPLETED_BIT, &r->state)) { ++ ssh_request_put(r); ++ ssh_rtl_tx_schedule(rtl); ++ return; ++ } ++ ++ /* ++ * Make sure the request has been transmitted. In case of a sequenced ++ * request, we are guaranteed that the completion callback will run on ++ * the receiver thread directly when the ACK for the packet has been ++ * received. Similarly, this function is guaranteed to run on the ++ * receiver thread. Thus we are guaranteed that if the packet has been ++ * successfully transmitted and received an ACK, the transmitted flag ++ * has been set and is visible here. ++ * ++ * We are currently not handling unsequenced packets here, as those ++ * should never expect a response as ensured in ssh_rtl_submit. If this ++ * ever changes, one would have to test for ++ * ++ * (r->state & (transmitting | transmitted)) ++ * ++ * on unsequenced packets to determine if they could have been ++ * transmitted. There are no synchronization guarantees as in the ++ * sequenced case, since, in this case, the callback function will not ++ * run on the same thread. Thus an exact determination is impossible. ++ */ ++ if (!test_bit(SSH_REQUEST_SF_TRANSMITTED_BIT, &r->state)) { ++ rtl_err(rtl, "rtl: received response before ACK for request" ++ " (rqid = 0x%04x)\n", rqid); ++ ++ /* ++ * NB: Timeout has already been canceled, request already been ++ * removed from pending and marked as locked and completed. As ++ * we receive a "false" response, the packet might still be ++ * queued though. ++ */ ++ ssh_rtl_queue_remove(r); ++ ++ ssh_rtl_complete_with_status(r, -EREMOTEIO); ++ ssh_request_put(r); ++ ++ ssh_rtl_tx_schedule(rtl); ++ return; ++ } ++ ++ /* ++ * NB: Timeout has already been canceled, request already been ++ * removed from pending and marked as locked and completed. The request ++ * can also not be queued any more, as it has been marked as ++ * transmitting and later transmitted. Thus no need to remove it from ++ * anywhere. ++ */ ++ ++ ssh_rtl_complete_with_rsp(r, command, command_data); ++ ssh_request_put(r); ++ ++ ssh_rtl_tx_schedule(rtl); ++} ++ ++ ++static bool ssh_rtl_cancel_nonpending(struct ssh_request *r) ++{ ++ struct ssh_rtl *rtl; ++ unsigned long state, fixed; ++ bool remove; ++ ++ /* ++ * Handle unsubmitted request: Try to mark the packet as locked, ++ * expecting the state to be zero (i.e. unsubmitted). Note that, if ++ * setting the state worked, we might still be adding the packet to the ++ * queue in a currently executing submit call. In that case, however, ++ * ptl reference must have been set previously, as locked is checked ++ * after setting ptl. Thus only if we successfully lock this request and ++ * ptl is NULL, we have successfully removed the request. ++ * Otherwise we need to try and grab it from the queue. ++ * ++ * Note that if the CMPXCHG fails, we are guaranteed that ptl has ++ * been set and is non-NULL, as states can only be nonzero after this ++ * has been set. Also note that we need to fetch the static (type) flags ++ * to ensure that they don't cause the cmpxchg to fail. ++ */ ++ fixed = READ_ONCE(r->state) & SSH_REQUEST_FLAGS_TY_MASK; ++ state = cmpxchg(&r->state, fixed, SSH_REQUEST_SF_LOCKED_BIT); ++ if (!state && !READ_ONCE(r->packet.ptl)) { ++ if (test_and_set_bit(SSH_REQUEST_SF_COMPLETED_BIT, &r->state)) ++ return true; ++ ++ ssh_rtl_complete_with_status(r, -ECANCELED); ++ return true; ++ } ++ ++ rtl = ssh_request_rtl(r); ++ spin_lock(&rtl->queue.lock); ++ ++ /* ++ * Note: 1) Requests cannot be re-submitted. 2) If a request is queued, ++ * it cannot be "transmitting"/"pending" yet. Thus, if we successfully ++ * remove the the request here, we have removed all its occurences in ++ * the system. ++ */ ++ ++ remove = test_and_clear_bit(SSH_REQUEST_SF_QUEUED_BIT, &r->state); ++ if (!remove) { ++ spin_unlock(&rtl->queue.lock); ++ return false; ++ } ++ ++ set_bit(SSH_REQUEST_SF_LOCKED_BIT, &r->state); ++ list_del(&r->node); ++ ++ spin_unlock(&rtl->queue.lock); ++ ++ ssh_request_put(r); // drop reference obtained from queue ++ ++ if (test_and_set_bit(SSH_REQUEST_SF_COMPLETED_BIT, &r->state)) ++ return true; ++ ++ ssh_rtl_complete_with_status(r, -ECANCELED); ++ return true; ++} ++ ++static bool ssh_rtl_cancel_pending(struct ssh_request *r) ++{ ++ // if the packet is already locked, it's going to be removed shortly ++ if (test_and_set_bit(SSH_REQUEST_SF_LOCKED_BIT, &r->state)) ++ return true; ++ ++ /* ++ * Now that we have locked the packet, we have guaranteed that it can't ++ * be added to the system any more. If rtl is zero, the locked ++ * check in ssh_rtl_submit has not been run and any submission, ++ * currently in progress or called later, won't add the packet. Thus we ++ * can directly complete it. ++ */ ++ if (!ssh_request_rtl(r)) { ++ if (test_and_set_bit(SSH_REQUEST_SF_COMPLETED_BIT, &r->state)) ++ return true; ++ ++ ssh_rtl_complete_with_status(r, -ECANCELED); ++ return true; ++ } ++ ++ /* ++ * Try to cancel the packet. If the packet has not been completed yet, ++ * this will subsequently (and synchronously) call the completion ++ * callback of the packet, which will complete the request. ++ */ ++ ssh_ptl_cancel(&r->packet); ++ ++ /* ++ * If the packet has been completed with success, i.e. has not been ++ * canceled by the above call, the request may not have been completed ++ * yet (may be waiting for a response). Check if we need to do this ++ * here. ++ */ ++ if (test_and_set_bit(SSH_REQUEST_SF_COMPLETED_BIT, &r->state)) ++ return true; ++ ++ ssh_rtl_queue_remove(r); ++ ssh_rtl_pending_remove(r); ++ ssh_rtl_complete_with_status(r, -ECANCELED); ++ ++ return true; ++} ++ ++bool ssh_rtl_cancel(struct ssh_request *rqst, bool pending) ++{ ++ struct ssh_rtl *rtl; ++ bool canceled; ++ ++ if (test_and_set_bit(SSH_REQUEST_SF_CANCELED_BIT, &rqst->state)) ++ return true; ++ ++ trace_ssam_request_cancel(rqst); ++ ++ if (pending) ++ canceled = ssh_rtl_cancel_pending(rqst); ++ else ++ canceled = ssh_rtl_cancel_nonpending(rqst); ++ ++ // note: rtl may be NULL if request has not been submitted yet ++ rtl = ssh_request_rtl(rqst); ++ if (canceled && rtl) ++ ssh_rtl_tx_schedule(rtl); ++ ++ return canceled; ++} ++ ++ ++static void ssh_rtl_packet_callback(struct ssh_packet *p, int status) ++{ ++ struct ssh_request *r = to_ssh_request(p, packet); ++ ++ if (unlikely(status)) { ++ set_bit(SSH_REQUEST_SF_LOCKED_BIT, &r->state); ++ ++ if (test_and_set_bit(SSH_REQUEST_SF_COMPLETED_BIT, &r->state)) ++ return; ++ ++ /* ++ * The packet may get cancelled even though it has not been ++ * submitted yet. The request may still be queued. Check the ++ * queue and remove it if necessary. As the timeout would have ++ * been started in this function on success, there's no need to ++ * cancel it here. ++ */ ++ ssh_rtl_queue_remove(r); ++ ssh_rtl_pending_remove(r); ++ ssh_rtl_complete_with_status(r, status); ++ ++ ssh_rtl_tx_schedule(ssh_request_rtl(r)); ++ return; ++ } ++ ++ /* ++ * Mark as transmitted, ensure that state doesn't get zero by inserting ++ * a memory barrier. ++ */ ++ set_bit(SSH_REQUEST_SF_TRANSMITTED_BIT, &r->state); ++ smp_mb__before_atomic(); ++ clear_bit(SSH_REQUEST_SF_TRANSMITTING_BIT, &r->state); ++ ++ // if we expect a response, we just need to start the timeout ++ if (test_bit(SSH_REQUEST_TY_HAS_RESPONSE_BIT, &r->state)) { ++ ssh_rtl_timeout_start(r); ++ return; ++ } ++ ++ /* ++ * If we don't expect a response, lock, remove, and complete the ++ * request. Note that, at this point, the request is guaranteed to have ++ * left the queue and no timeout has been started. Thus we only need to ++ * remove it from pending. If the request has already been completed (it ++ * may have been canceled) return. ++ */ ++ ++ set_bit(SSH_REQUEST_SF_LOCKED_BIT, &r->state); ++ if (test_and_set_bit(SSH_REQUEST_SF_COMPLETED_BIT, &r->state)) ++ return; ++ ++ ssh_rtl_pending_remove(r); ++ ssh_rtl_complete_with_status(r, 0); ++ ++ ssh_rtl_tx_schedule(ssh_request_rtl(r)); ++} ++ ++ ++static ktime_t ssh_request_get_expiration(struct ssh_request *r, ktime_t timeo) ++{ ++ ktime_t timestamp = READ_ONCE(r->timestamp); ++ ++ if (timestamp != KTIME_MAX) ++ return ktime_add(timestamp, timeo); ++ else ++ return KTIME_MAX; ++} ++ ++static void ssh_rtl_timeout_reap(struct work_struct *work) ++{ ++ struct ssh_rtl *rtl = to_ssh_rtl(work, rtx_timeout.reaper.work); ++ struct ssh_request *r, *n; ++ LIST_HEAD(claimed); ++ ktime_t now = ktime_get_coarse_boottime(); ++ ktime_t timeout = rtl->rtx_timeout.timeout; ++ ktime_t next = KTIME_MAX; ++ ++ trace_ssam_rtl_timeout_reap("pending", atomic_read(&rtl->pending.count)); ++ ++ /* ++ * Mark reaper as "not pending". This is done before checking any ++ * requests to avoid lost-update type problems. ++ */ ++ WRITE_ONCE(rtl->rtx_timeout.expires, KTIME_MAX); ++ smp_mb__after_atomic(); ++ ++ spin_lock(&rtl->pending.lock); ++ list_for_each_entry_safe(r, n, &rtl->pending.head, node) { ++ ktime_t expires = ssh_request_get_expiration(r, timeout); ++ ++ /* ++ * Check if the timeout hasn't expired yet. Find out next ++ * expiration date to be handled after this run. ++ */ ++ if (ktime_after(expires, now)) { ++ next = ktime_before(expires, next) ? expires : next; ++ continue; ++ } ++ ++ // avoid further transitions if locked ++ if (test_and_set_bit(SSH_REQUEST_SF_LOCKED_BIT, &r->state)) ++ continue; ++ ++ /* ++ * We have now marked the packet as locked. Thus it cannot be ++ * added to the pending or queued lists again after we've ++ * removed it here. We can therefore re-use the node of this ++ * packet temporarily. ++ */ ++ ++ clear_bit(SSH_REQUEST_SF_PENDING_BIT, &r->state); ++ ++ atomic_dec(&rtl->pending.count); ++ list_del(&r->node); ++ ++ list_add_tail(&r->node, &claimed); ++ } ++ spin_unlock(&rtl->pending.lock); ++ ++ // cancel and complete the request ++ list_for_each_entry_safe(r, n, &claimed, node) { ++ trace_ssam_request_timeout(r); ++ ++ /* ++ * At this point we've removed the packet from pending. This ++ * means that we've obtained the last (only) reference of the ++ * system to it. Thus we can just complete it. ++ */ ++ if (!test_and_set_bit(SSH_REQUEST_SF_COMPLETED_BIT, &r->state)) ++ ssh_rtl_complete_with_status(r, -ETIMEDOUT); ++ ++ // drop the reference we've obtained by removing it from pending ++ list_del(&r->node); ++ ssh_request_put(r); ++ } ++ ++ // ensure that reaper doesn't run again immediately ++ next = max(next, ktime_add(now, SSH_RTL_REQUEST_TIMEOUT_RESOLUTION)); ++ if (next != KTIME_MAX) ++ ssh_rtl_timeout_reaper_mod(rtl, now, next); ++ ++ ssh_rtl_tx_schedule(rtl); ++} ++ ++ ++static void ssh_rtl_rx_event(struct ssh_rtl *rtl, const struct ssh_command *cmd, ++ const struct ssam_span *data) ++{ ++ trace_ssam_rx_event_received(cmd, data->len); ++ ++ rtl_dbg(rtl, "rtl: handling event (rqid: 0x%04x)\n", ++ get_unaligned_le16(&cmd->rqid)); ++ ++ rtl->ops.handle_event(rtl, cmd, data); ++} ++ ++static void ssh_rtl_rx_command(struct ssh_ptl *p, const struct ssam_span *data) ++{ ++ struct ssh_rtl *rtl = to_ssh_rtl(p, ptl); ++ struct device *dev = &p->serdev->dev; ++ struct ssh_command *command; ++ struct ssam_span command_data; ++ ++ if (sshp_parse_command(dev, data, &command, &command_data)) ++ return; ++ ++ if (ssh_rqid_is_event(get_unaligned_le16(&command->rqid))) ++ ssh_rtl_rx_event(rtl, command, &command_data); ++ else ++ ssh_rtl_complete(rtl, command, &command_data); ++} ++ ++static void ssh_rtl_rx_data(struct ssh_ptl *p, const struct ssam_span *data) ++{ ++ switch (data->ptr[0]) { ++ case SSH_PLD_TYPE_CMD: ++ ssh_rtl_rx_command(p, data); ++ break; ++ ++ default: ++ ptl_err(p, "rtl: rx: unknown frame payload type" ++ " (type: 0x%02x)\n", data->ptr[0]); ++ break; ++ } ++} ++ ++ ++bool ssh_rtl_tx_flush(struct ssh_rtl *rtl) ++{ ++ return flush_work(&rtl->tx.work); ++} ++ ++int ssh_rtl_rx_start(struct ssh_rtl *rtl) ++{ ++ return ssh_ptl_rx_start(&rtl->ptl); ++} ++ ++int ssh_rtl_tx_start(struct ssh_rtl *rtl) ++{ ++ int status; ++ bool sched; ++ ++ status = ssh_ptl_tx_start(&rtl->ptl); ++ if (status) ++ return status; ++ ++ /* ++ * If the packet layer has been shut down and restarted without shutting ++ * down the request layer, there may still be requests queued and not ++ * handled. ++ */ ++ spin_lock(&rtl->queue.lock); ++ sched = !list_empty(&rtl->queue.head); ++ spin_unlock(&rtl->queue.lock); ++ ++ if (sched) ++ ssh_rtl_tx_schedule(rtl); ++ ++ return 0; ++} ++ ++int ssh_rtl_init(struct ssh_rtl *rtl, struct serdev_device *serdev, ++ const struct ssh_rtl_ops *ops) ++{ ++ struct ssh_ptl_ops ptl_ops; ++ int status; ++ ++ ptl_ops.data_received = ssh_rtl_rx_data; ++ ++ status = ssh_ptl_init(&rtl->ptl, serdev, &ptl_ops); ++ if (status) ++ return status; ++ ++ spin_lock_init(&rtl->queue.lock); ++ INIT_LIST_HEAD(&rtl->queue.head); ++ ++ spin_lock_init(&rtl->pending.lock); ++ INIT_LIST_HEAD(&rtl->pending.head); ++ atomic_set_release(&rtl->pending.count, 0); ++ ++ INIT_WORK(&rtl->tx.work, ssh_rtl_tx_work_fn); ++ ++ rtl->rtx_timeout.timeout = SSH_RTL_REQUEST_TIMEOUT; ++ rtl->rtx_timeout.expires = KTIME_MAX; ++ INIT_DELAYED_WORK(&rtl->rtx_timeout.reaper, ssh_rtl_timeout_reap); ++ ++ rtl->ops = *ops; ++ ++ return 0; ++} ++ ++void ssh_rtl_destroy(struct ssh_rtl *rtl) ++{ ++ ssh_ptl_destroy(&rtl->ptl); ++} ++ ++ ++static void ssh_rtl_packet_release(struct ssh_packet *p) ++{ ++ struct ssh_request *rqst = to_ssh_request(p, packet); ++ rqst->ops->release(rqst); ++} ++ ++static const struct ssh_packet_ops ssh_rtl_packet_ops = { ++ .complete = ssh_rtl_packet_callback, ++ .release = ssh_rtl_packet_release, ++}; ++ ++void ssh_request_init(struct ssh_request *rqst, enum ssam_request_flags flags, ++ const struct ssh_request_ops *ops) ++{ ++ struct ssh_packet_args packet_args; ++ ++ packet_args.type = BIT(SSH_PACKET_TY_BLOCKING_BIT); ++ if (!(flags & SSAM_REQUEST_UNSEQUENCED)) ++ packet_args.type |= BIT(SSH_PACKET_TY_SEQUENCED_BIT); ++ ++ packet_args.priority = SSH_PACKET_PRIORITY(DATA, 0); ++ packet_args.ops = &ssh_rtl_packet_ops; ++ ++ ssh_packet_init(&rqst->packet, &packet_args); ++ INIT_LIST_HEAD(&rqst->node); ++ ++ rqst->state = 0; ++ if (flags & SSAM_REQUEST_HAS_RESPONSE) ++ rqst->state |= BIT(SSH_REQUEST_TY_HAS_RESPONSE_BIT); ++ ++ rqst->timestamp = KTIME_MAX; ++ rqst->ops = ops; ++} ++ ++ ++struct ssh_flush_request { ++ struct ssh_request base; ++ struct completion completion; ++ int status; ++}; ++ ++static void ssh_rtl_flush_request_complete(struct ssh_request *r, ++ const struct ssh_command *cmd, ++ const struct ssam_span *data, ++ int status) ++{ ++ struct ssh_flush_request *rqst; ++ ++ rqst = container_of(r, struct ssh_flush_request, base); ++ rqst->status = status; ++} ++ ++static void ssh_rtl_flush_request_release(struct ssh_request *r) ++{ ++ struct ssh_flush_request *rqst; ++ ++ rqst = container_of(r, struct ssh_flush_request, base); ++ complete_all(&rqst->completion); ++} ++ ++static const struct ssh_request_ops ssh_rtl_flush_request_ops = { ++ .complete = ssh_rtl_flush_request_complete, ++ .release = ssh_rtl_flush_request_release, ++}; ++ ++/** ++ * ssh_rtl_flush - flush the request transmission layer ++ * @rtl: request transmission layer ++ * @timeout: timeout for the flush operation in jiffies ++ * ++ * Queue a special flush request and wait for its completion. This request ++ * will be completed after all other currently queued and pending requests ++ * have been completed. Instead of a normal data packet, this request submits ++ * a special flush packet, meaning that upon completion, also the underlying ++ * packet transmission layer has been flushed. ++ * ++ * Flushing the request layer gurarantees that all previously submitted ++ * requests have been fully completed before this call returns. Additinally, ++ * flushing blocks execution of all later submitted requests until the flush ++ * has been completed. ++ * ++ * If the caller ensures that no new requests are submitted after a call to ++ * this function, the request transmission layer is guaranteed to have no ++ * remaining requests when this call returns. The same guarantee does not hold ++ * for the packet layer, on which control packets may still be queued after ++ * this call. See the documentation of ssh_ptl_flush for more details on ++ * packet layer flushing. ++ * ++ * Return: Zero on success, -ETIMEDOUT if the flush timed out and has been ++ * canceled as a result of the timeout, or -ESHUTDOWN if the packet and/or ++ * request transmission layer has been shut down before this call. May also ++ * return -EINTR if the underlying packet transmission has been interrupted. ++ */ ++int ssh_rtl_flush(struct ssh_rtl *rtl, unsigned long timeout) ++{ ++ const unsigned init_flags = SSAM_REQUEST_UNSEQUENCED; ++ struct ssh_flush_request rqst; ++ int status; ++ ++ ssh_request_init(&rqst.base, init_flags, &ssh_rtl_flush_request_ops); ++ rqst.base.packet.state |= BIT(SSH_PACKET_TY_FLUSH_BIT); ++ rqst.base.packet.priority = SSH_PACKET_PRIORITY(FLUSH, 0); ++ rqst.base.state |= BIT(SSH_REQUEST_TY_FLUSH_BIT); ++ ++ init_completion(&rqst.completion); ++ ++ status = ssh_rtl_submit(rtl, &rqst.base); ++ if (status) ++ return status; ++ ++ ssh_request_put(&rqst.base); ++ ++ if (wait_for_completion_timeout(&rqst.completion, timeout)) ++ return 0; ++ ++ ssh_rtl_cancel(&rqst.base, true); ++ wait_for_completion(&rqst.completion); ++ ++ WARN_ON(rqst.status != 0 && rqst.status != -ECANCELED ++ && rqst.status != -ESHUTDOWN && rqst.status != -EINTR); ++ ++ return rqst.status == -ECANCELED ? -ETIMEDOUT : status; ++} ++ ++ ++void ssh_rtl_shutdown(struct ssh_rtl *rtl) ++{ ++ struct ssh_request *r, *n; ++ LIST_HEAD(claimed); ++ int pending; ++ ++ set_bit(SSH_RTL_SF_SHUTDOWN_BIT, &rtl->state); ++ smp_mb__after_atomic(); ++ ++ // remove requests from queue ++ spin_lock(&rtl->queue.lock); ++ list_for_each_entry_safe(r, n, &rtl->queue.head, node) { ++ set_bit(SSH_REQUEST_SF_LOCKED_BIT, &r->state); ++ smp_mb__before_atomic(); ++ clear_bit(SSH_REQUEST_SF_QUEUED_BIT, &r->state); ++ ++ list_del(&r->node); ++ list_add_tail(&r->node, &claimed); ++ } ++ spin_unlock(&rtl->queue.lock); ++ ++ /* ++ * We have now guaranteed that the queue is empty and no more new ++ * requests can be submitted (i.e. it will stay empty). This means that ++ * calling ssh_rtl_tx_schedule will not schedule tx.work any more. So we ++ * can simply call cancel_work_sync on tx.work here and when that ++ * returns, we've locked it down. This also means that after this call, ++ * we don't submit any more packets to the underlying packet layer, so ++ * we can also shut that down. ++ */ ++ ++ cancel_work_sync(&rtl->tx.work); ++ ssh_ptl_shutdown(&rtl->ptl); ++ cancel_delayed_work_sync(&rtl->rtx_timeout.reaper); ++ ++ /* ++ * Shutting down the packet layer should also have caneled all requests. ++ * Thus the pending set should be empty. Attempt to handle this ++ * gracefully anyways, even though this should be dead code. ++ */ ++ ++ pending = atomic_read(&rtl->pending.count); ++ if (WARN_ON(pending)) { ++ spin_lock(&rtl->pending.lock); ++ list_for_each_entry_safe(r, n, &rtl->pending.head, node) { ++ set_bit(SSH_REQUEST_SF_LOCKED_BIT, &r->state); ++ smp_mb__before_atomic(); ++ clear_bit(SSH_REQUEST_SF_PENDING_BIT, &r->state); ++ ++ list_del(&r->node); ++ list_add_tail(&r->node, &claimed); ++ } ++ spin_unlock(&rtl->pending.lock); ++ } ++ ++ // finally cancel and complete requests ++ list_for_each_entry_safe(r, n, &claimed, node) { ++ // test_and_set because we still might compete with cancellation ++ if (!test_and_set_bit(SSH_REQUEST_SF_COMPLETED_BIT, &r->state)) ++ ssh_rtl_complete_with_status(r, -ESHUTDOWN); ++ ++ // drop the reference we've obtained by removing it from list ++ list_del(&r->node); ++ ssh_request_put(r); ++ } ++} +diff --git a/drivers/misc/surface_sam/ssh_request_layer.h b/drivers/misc/surface_sam/ssh_request_layer.h +new file mode 100644 +index 0000000000000..adcbd32b06819 +--- /dev/null ++++ b/drivers/misc/surface_sam/ssh_request_layer.h +@@ -0,0 +1,91 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++ ++#ifndef _SSAM_SSH_REQUEST_LAYER_H ++#define _SSAM_SSH_REQUEST_LAYER_H ++ ++#include ++#include ++#include ++#include ++#include ++ ++#include ++ ++#include "ssh_packet_layer.h" ++ ++ ++enum ssh_rtl_state_flags { ++ SSH_RTL_SF_SHUTDOWN_BIT, ++}; ++ ++struct ssh_rtl_ops { ++ void (*handle_event)(struct ssh_rtl *rtl, const struct ssh_command *cmd, ++ const struct ssam_span *data); ++}; ++ ++struct ssh_rtl { ++ struct ssh_ptl ptl; ++ unsigned long state; ++ ++ struct { ++ spinlock_t lock; ++ struct list_head head; ++ } queue; ++ ++ struct { ++ spinlock_t lock; ++ struct list_head head; ++ atomic_t count; ++ } pending; ++ ++ struct { ++ struct work_struct work; ++ } tx; ++ ++ struct { ++ ktime_t timeout; ++ ktime_t expires; ++ struct delayed_work reaper; ++ } rtx_timeout; ++ ++ struct ssh_rtl_ops ops; ++}; ++ ++#define rtl_dbg(r, fmt, ...) ptl_dbg(&(r)->ptl, fmt, ##__VA_ARGS__) ++#define rtl_info(p, fmt, ...) ptl_info(&(p)->ptl, fmt, ##__VA_ARGS__) ++#define rtl_warn(r, fmt, ...) ptl_warn(&(r)->ptl, fmt, ##__VA_ARGS__) ++#define rtl_err(r, fmt, ...) ptl_err(&(r)->ptl, fmt, ##__VA_ARGS__) ++#define rtl_dbg_cond(r, fmt, ...) __ssam_prcond(rtl_dbg, r, fmt, ##__VA_ARGS__) ++ ++#define to_ssh_rtl(ptr, member) \ ++ container_of(ptr, struct ssh_rtl, member) ++ ++static inline struct device *ssh_rtl_get_device(struct ssh_rtl *rtl) ++{ ++ return ssh_ptl_get_device(&rtl->ptl); ++} ++ ++static inline struct ssh_rtl *ssh_request_rtl(struct ssh_request *rqst) ++{ ++ struct ssh_ptl *ptl = READ_ONCE(rqst->packet.ptl); ++ return likely(ptl) ? to_ssh_rtl(ptl, ptl) : NULL; ++} ++ ++int ssh_rtl_submit(struct ssh_rtl *rtl, struct ssh_request *rqst); ++bool ssh_rtl_cancel(struct ssh_request *rqst, bool pending); ++ ++int ssh_rtl_init(struct ssh_rtl *rtl, struct serdev_device *serdev, ++ const struct ssh_rtl_ops *ops); ++ ++bool ssh_rtl_tx_flush(struct ssh_rtl *rtl); ++int ssh_rtl_rx_start(struct ssh_rtl *rtl); ++int ssh_rtl_tx_start(struct ssh_rtl *rtl); ++ ++int ssh_rtl_flush(struct ssh_rtl *rtl, unsigned long timeout); ++void ssh_rtl_shutdown(struct ssh_rtl *rtl); ++void ssh_rtl_destroy(struct ssh_rtl *rtl); ++ ++void ssh_request_init(struct ssh_request *rqst, enum ssam_request_flags flags, ++ const struct ssh_request_ops *ops); ++ ++#endif /* _SSAM_SSH_REQUEST_LAYER_H */ diff --git a/drivers/tty/serdev/core.c b/drivers/tty/serdev/core.c -index c66a04d24f1d3..6b48fdfb20059 100644 +index c66a04d24f1d3..1b18d12d217f8 100644 --- a/drivers/tty/serdev/core.c +++ b/drivers/tty/serdev/core.c @@ -496,16 +496,97 @@ static int of_serdev_register_devices(struct serdev_controller *ctrl) @@ -12720,7 +12779,7 @@ index c66a04d24f1d3..6b48fdfb20059 100644 +} + +static int acpi_serdev_do_lookup(struct acpi_device *adev, -+ struct acpi_serdev_lookup *lookup) ++ struct acpi_serdev_lookup *lookup) +{ + struct list_head resource_list; + int ret; @@ -12787,7 +12846,7 @@ index c66a04d24f1d3..6b48fdfb20059 100644 { struct serdev_controller *ctrl = data; struct acpi_device *adev; -@@ -541,26 +622,31 @@ static acpi_status acpi_serdev_add_device(acpi_handle handle, u32 level, +@@ -541,26 +622,32 @@ static acpi_status acpi_serdev_add_device(acpi_handle handle, u32 level, if (acpi_bus_get_device(handle, &adev)) return AE_OK; @@ -12804,6 +12863,7 @@ index c66a04d24f1d3..6b48fdfb20059 100644 return acpi_serdev_register_device(ctrl, adev); } ++ static int acpi_serdev_register_devices(struct serdev_controller *ctrl) { acpi_status status; @@ -12824,6 +12884,904 @@ index c66a04d24f1d3..6b48fdfb20059 100644 if (!ctrl->serdev) return -ENODEV; +diff --git a/include/linux/surface_aggregator_module.h b/include/linux/surface_aggregator_module.h +new file mode 100644 +index 0000000000000..81e54e5c8f9e9 +--- /dev/null ++++ b/include/linux/surface_aggregator_module.h +@@ -0,0 +1,892 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* ++ * Interface for Surface System Aggregator Module (SSAM) via Surface Serial ++ * Hub (SSH). ++ * ++ * The Surface Serial Hub (SSH) is the main communication hub for ++ * communication between host and the Surface/System Aggregator Module (SSAM), ++ * an embedded controller on newer Microsoft Surface devices (Book 2, Pro 5, ++ * Laptops, and later). Also referred to as SAM-over-SSH. Older devices (Book ++ * 1, Pro 4) use SAM-over-HID (via I2C), which this driver does not support. ++ */ ++ ++#ifndef _SURFACE_AGGREGATOR_MODULE_H ++#define _SURFACE_AGGREGATOR_MODULE_H ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++ ++/* -- Data structures for SAM-over-SSH communication. ----------------------- */ ++ ++/** ++ * enum ssh_frame_type - Frame types for SSH frames. ++ * @SSH_FRAME_TYPE_DATA_SEQ: Indicates a data frame, followed by a payload with ++ * the length specified in the ssh_frame.len field. This ++ * frame is sequenced, meaning that an ACK is required. ++ * @SSH_FRAME_TYPE_DATA_NSQ: Same as SSH_FRAME_TYPE_DATA_SEQ, but unsequenced, ++ * meaning that the message does not have to be ACKed. ++ * @SSH_FRAME_TYPE_ACK: Indicates an ACK message. ++ * @SSH_FRAME_TYPE_NAK: Indicates an error response for previously sent ++ * frame. In general, this means that the frame and/or ++ * payload is malformed, e.g. a CRC is wrong. For command- ++ * type payloads, this can also mean that the command is ++ * invalid. ++ */ ++enum ssh_frame_type { ++ SSH_FRAME_TYPE_DATA_SEQ = 0x80, ++ SSH_FRAME_TYPE_DATA_NSQ = 0x00, ++ SSH_FRAME_TYPE_ACK = 0x40, ++ SSH_FRAME_TYPE_NAK = 0x04, ++}; ++ ++/** ++ * struct ssh_frame - SSH communication frame. ++ * @type: The type of the frame. See &enum ssh_frame_type. ++ * @len: The length of the frame payload directly following the CRC for this ++ * frame. Does not include the final CRC for that payload. ++ * @seq: The sequence number for this message/exchange. ++ */ ++struct ssh_frame { ++ u8 type; ++ __le16 len; ++ u8 seq; ++} __packed; ++ ++/* ++ * Maximum SSH frame payload length in bytes. This is the physical maximum ++ * length of the protocol. Implementations may set a more constrained limit. ++ */ ++#define SSH_FRAME_MAX_PAYLOAD_SIZE U16_MAX ++ ++/** ++ * enum ssh_payload_type - Type indicator for the SSH payload. ++ * @SSH_PLD_TYPE_CMD: The payload is a command structure with optional command ++ * payload. ++ */ ++enum ssh_payload_type { ++ SSH_PLD_TYPE_CMD = 0x80, ++}; ++ ++/** ++ * struct ssh_command - Payload of a command-type frame. ++ * @type: The type of the payload. See &enum ssh_payload_type. Should be ++ * SSH_PLD_TYPE_CMD for this struct. ++ * @tc: Command target category. ++ * @chn_out: Output channel. Should be zero if this an incoming (EC to host) ++ * message. ++ * @chn_in: Input channel. Should be zero if this is an outgoing (hos to EC) ++ * message. ++ * @iid: Instance ID. ++ * @rqid: Request ID. Used to match requests with responses and differentiate ++ * between responses and events. ++ * @cid: Command ID. ++ */ ++struct ssh_command { ++ u8 type; ++ u8 tc; ++ u8 chn_out; ++ u8 chn_in; ++ u8 iid; ++ __le16 rqid; ++ u8 cid; ++} __packed; ++ ++/* ++ * Maximum SSH command payload length in bytes. This is the physical maximum ++ * length of the protocol. Implementations may set a more constrained limit. ++ */ ++#define SSH_COMMAND_MAX_PAYLOAD_SIZE \ ++ (SSH_FRAME_MAX_PAYLOAD_SIZE - sizeof(struct ssh_command)) ++ ++/** ++ * struct ssh_notification_params - Command payload to enable/disable SSH ++ * notifications. ++ * @target_category: The target category for which notifications should be ++ * enabled/disabled. ++ * @flags: Flags determining how notifications are being sent. ++ * @request_id: The request ID that is used to send these notifications. ++ * @instance_id: The specific instance in the given target category for ++ * which notifications should be enabled. ++ */ ++struct ssh_notification_params { ++ u8 target_category; ++ u8 flags; ++ __le16 request_id; ++ u8 instance_id; ++} __packed; ++ ++/** ++ * Base-length of a SSH message. This is the minimum number of bytes required ++ * to form a message. The actual message length is SSH_MSG_LEN_BASE plus the ++ * length of the frame payload. ++ */ ++#define SSH_MSG_LEN_BASE (sizeof(struct ssh_frame) + 3ull * sizeof(u16)) ++ ++/** ++ * Length of a SSH control message. ++ */ ++#define SSH_MSG_LEN_CTRL SSH_MSG_LEN_BASE ++ ++/** ++ * Length of a SSH message with payload of specified size. ++ */ ++#define SSH_MESSAGE_LENGTH(payload_size) (SSH_MSG_LEN_BASE + payload_size) ++ ++/** ++ * Length of a SSH command message with command payload of specified size. ++ */ ++#define SSH_COMMAND_MESSAGE_LENGTH(payload_size) \ ++ SSH_MESSAGE_LENGTH(sizeof(struct ssh_command) + payload_size) ++ ++/** ++ * Offset of the specified struct ssh_frame field in the raw SSH message data. ++ */ ++#define SSH_MSGOFFSET_FRAME(field) \ ++ (sizeof(u16) + offsetof(struct ssh_frame, field)) ++ ++/** ++ * Offset of the specified struct ssh_command field in the raw SSH message data. ++ */ ++#define SSH_MSGOFFSET_COMMAND(field) \ ++ (2ull * sizeof(u16) + sizeof(struct ssh_frame) \ ++ + offsetof(struct ssh_command, field)) ++ ++/** ++ * struct ssam_span - reference to a buffer region ++ * @ptr: pointer to the buffer region ++ * @len: length of the buffer region ++ * ++ * A reference to a (non-owned) buffer segment, consisting of pointer and ++ * length. Use of this struct indicates non-owned data, i.e. data of which the ++ * life-time is managed (i.e. it is allocated/freed) via another pointer. ++ */ ++struct ssam_span { ++ u8 *ptr; ++ size_t len; ++}; ++ ++ ++/* -- Packet transport layer (ptl). ----------------------------------------- */ ++ ++enum ssh_packet_priority { ++ SSH_PACKET_PRIORITY_FLUSH = 0, ++ SSH_PACKET_PRIORITY_DATA = 0, ++ SSH_PACKET_PRIORITY_NAK = 1 << 4, ++ SSH_PACKET_PRIORITY_ACK = 2 << 4, ++}; ++ ++#define SSH_PACKET_PRIORITY(base, try) \ ++ ((SSH_PACKET_PRIORITY_##base) | ((try) & 0x0f)) ++ ++#define ssh_packet_priority_get_try(p) ((p) & 0x0f) ++ ++ ++enum ssh_packet_flags { ++ SSH_PACKET_SF_LOCKED_BIT, ++ SSH_PACKET_SF_QUEUED_BIT, ++ SSH_PACKET_SF_PENDING_BIT, ++ SSH_PACKET_SF_TRANSMITTING_BIT, ++ SSH_PACKET_SF_TRANSMITTED_BIT, ++ SSH_PACKET_SF_ACKED_BIT, ++ SSH_PACKET_SF_CANCELED_BIT, ++ SSH_PACKET_SF_COMPLETED_BIT, ++ ++ SSH_PACKET_TY_FLUSH_BIT, ++ SSH_PACKET_TY_SEQUENCED_BIT, ++ SSH_PACKET_TY_BLOCKING_BIT, ++ ++ SSH_PACKET_FLAGS_SF_MASK = ++ BIT(SSH_PACKET_SF_LOCKED_BIT) ++ | BIT(SSH_PACKET_SF_QUEUED_BIT) ++ | BIT(SSH_PACKET_SF_PENDING_BIT) ++ | BIT(SSH_PACKET_SF_TRANSMITTING_BIT) ++ | BIT(SSH_PACKET_SF_TRANSMITTED_BIT) ++ | BIT(SSH_PACKET_SF_ACKED_BIT) ++ | BIT(SSH_PACKET_SF_CANCELED_BIT) ++ | BIT(SSH_PACKET_SF_COMPLETED_BIT), ++ ++ SSH_PACKET_FLAGS_TY_MASK = ++ BIT(SSH_PACKET_TY_FLUSH_BIT) ++ | BIT(SSH_PACKET_TY_SEQUENCED_BIT) ++ | BIT(SSH_PACKET_TY_BLOCKING_BIT), ++}; ++ ++ ++struct ssh_ptl; ++struct ssh_packet; ++ ++struct ssh_packet_ops { ++ void (*release)(struct ssh_packet *p); ++ void (*complete)(struct ssh_packet *p, int status); ++}; ++ ++struct ssh_packet { ++ struct ssh_ptl *ptl; ++ struct kref refcnt; ++ ++ u8 priority; ++ ++ struct { ++ size_t len; ++ u8 *ptr; ++ } data; ++ ++ unsigned long state; ++ ktime_t timestamp; ++ ++ struct list_head queue_node; ++ struct list_head pending_node; ++ ++ const struct ssh_packet_ops *ops; ++}; ++ ++#define to_ssh_packet(ptr, member) \ ++ container_of(ptr, struct ssh_packet, member) ++ ++ ++static inline struct ssh_packet *ssh_packet_get(struct ssh_packet *packet) ++{ ++ kref_get(&packet->refcnt); ++ return packet; ++} ++ ++void ssh_packet_put(struct ssh_packet *p); ++ ++static inline void ssh_packet_set_data(struct ssh_packet *p, u8 *ptr, size_t len) ++{ ++ p->data.ptr = ptr; ++ p->data.len = len; ++} ++ ++ ++/* -- Request transport layer (rtl). ---------------------------------------- */ ++ ++enum ssh_request_flags { ++ SSH_REQUEST_SF_LOCKED_BIT, ++ SSH_REQUEST_SF_QUEUED_BIT, ++ SSH_REQUEST_SF_PENDING_BIT, ++ SSH_REQUEST_SF_TRANSMITTING_BIT, ++ SSH_REQUEST_SF_TRANSMITTED_BIT, ++ SSH_REQUEST_SF_RSPRCVD_BIT, ++ SSH_REQUEST_SF_CANCELED_BIT, ++ SSH_REQUEST_SF_COMPLETED_BIT, ++ ++ SSH_REQUEST_TY_FLUSH_BIT, ++ SSH_REQUEST_TY_HAS_RESPONSE_BIT, ++ ++ SSH_REQUEST_FLAGS_SF_MASK = ++ BIT(SSH_REQUEST_SF_LOCKED_BIT) ++ | BIT(SSH_REQUEST_SF_QUEUED_BIT) ++ | BIT(SSH_REQUEST_SF_PENDING_BIT) ++ | BIT(SSH_REQUEST_SF_TRANSMITTING_BIT) ++ | BIT(SSH_REQUEST_SF_TRANSMITTED_BIT) ++ | BIT(SSH_REQUEST_SF_RSPRCVD_BIT) ++ | BIT(SSH_REQUEST_SF_CANCELED_BIT) ++ | BIT(SSH_REQUEST_SF_COMPLETED_BIT), ++ ++ SSH_REQUEST_FLAGS_TY_MASK = ++ BIT(SSH_REQUEST_TY_FLUSH_BIT) ++ | BIT(SSH_REQUEST_TY_HAS_RESPONSE_BIT), ++}; ++ ++ ++struct ssh_rtl; ++struct ssh_request; ++ ++struct ssh_request_ops { ++ void (*release)(struct ssh_request *rqst); ++ void (*complete)(struct ssh_request *rqst, ++ const struct ssh_command *cmd, ++ const struct ssam_span *data, int status); ++}; ++ ++struct ssh_request { ++ struct ssh_packet packet; ++ struct list_head node; ++ ++ unsigned long state; ++ ktime_t timestamp; ++ ++ const struct ssh_request_ops *ops; ++}; ++ ++#define to_ssh_request(ptr, member) \ ++ container_of(ptr, struct ssh_request, member) ++ ++ ++static inline struct ssh_request *ssh_request_get(struct ssh_request *r) ++{ ++ ssh_packet_get(&r->packet); ++ return r; ++} ++ ++static inline void ssh_request_put(struct ssh_request *r) ++{ ++ ssh_packet_put(&r->packet); ++} ++ ++static inline void ssh_request_set_data(struct ssh_request *r, u8 *ptr, size_t len) ++{ ++ ssh_packet_set_data(&r->packet, ptr, len); ++} ++ ++ ++/* -- Main data types and definitions --------------------------------------- */ ++ ++enum ssam_ssh_tc { ++ SSAM_SSH_TC_SAM = 0x01, // generic system functionality, real-time clock ++ SSAM_SSH_TC_BAT = 0x02, // battery/power subsystem ++ SSAM_SSH_TC_TMP = 0x03, // thermal subsystem ++ SSAM_SSH_TC_PMC = 0x04, ++ SSAM_SSH_TC_FAN = 0x05, ++ SSAM_SSH_TC_PoM = 0x06, ++ SSAM_SSH_TC_DBG = 0x07, ++ SSAM_SSH_TC_KBD = 0x08, // legacy keyboard (Laptop 1/2) ++ SSAM_SSH_TC_FWU = 0x09, ++ SSAM_SSH_TC_UNI = 0x0a, ++ SSAM_SSH_TC_LPC = 0x0b, ++ SSAM_SSH_TC_TCL = 0x0c, ++ SSAM_SSH_TC_SFL = 0x0d, ++ SSAM_SSH_TC_KIP = 0x0e, ++ SSAM_SSH_TC_EXT = 0x0f, ++ SSAM_SSH_TC_BLD = 0x10, ++ SSAM_SSH_TC_BAS = 0x11, // detachment system (Surface Book 2/3) ++ SSAM_SSH_TC_SEN = 0x12, ++ SSAM_SSH_TC_SRQ = 0x13, ++ SSAM_SSH_TC_MCU = 0x14, ++ SSAM_SSH_TC_HID = 0x15, // generic HID input subsystem ++ SSAM_SSH_TC_TCH = 0x16, ++ SSAM_SSH_TC_BKL = 0x17, ++ SSAM_SSH_TC_TAM = 0x18, ++ SSAM_SSH_TC_ACC = 0x19, ++ SSAM_SSH_TC_UFI = 0x1a, ++ SSAM_SSH_TC_USC = 0x1b, ++ SSAM_SSH_TC_PEN = 0x1c, ++ SSAM_SSH_TC_VID = 0x1d, ++ SSAM_SSH_TC_AUD = 0x1e, ++ SSAM_SSH_TC_SMC = 0x1f, ++ SSAM_SSH_TC_KPD = 0x20, ++ SSAM_SSH_TC_REG = 0x21, ++ ++ SSAM_SSH_TC__HUB = 0x00, // not an actual ID, used in for hubs ++}; ++ ++struct ssam_controller; ++ ++/** ++ * struct ssam_event_flags - Flags for enabling/disabling SAM-over-SSH events ++ * @SSAM_EVENT_SEQUENCED: The event will be sent via a sequenced data frame. ++ */ ++enum ssam_event_flags { ++ SSAM_EVENT_SEQUENCED = BIT(0), ++}; ++ ++struct ssam_event { ++ u8 target_category; ++ u8 command_id; ++ u8 instance_id; ++ u8 channel; ++ u16 length; ++ u8 data[0]; ++}; ++ ++enum ssam_request_flags { ++ SSAM_REQUEST_HAS_RESPONSE = BIT(0), ++ SSAM_REQUEST_UNSEQUENCED = BIT(1), ++}; ++ ++struct ssam_request { ++ u8 target_category; ++ u8 command_id; ++ u8 instance_id; ++ u8 channel; ++ u16 flags; ++ u16 length; ++ const u8 *payload; ++}; ++ ++struct ssam_response { ++ size_t capacity; ++ size_t length; ++ u8 *pointer; ++}; ++ ++ ++struct ssam_controller *ssam_get_controller(void); ++int ssam_client_link(struct ssam_controller *ctrl, struct device *client); ++int ssam_client_bind(struct device *client, struct ssam_controller **ctrl); ++ ++struct device *ssam_controller_device(struct ssam_controller *c); ++ ++struct ssam_controller *ssam_controller_get(struct ssam_controller *c); ++void ssam_controller_put(struct ssam_controller *c); ++ ++void ssam_controller_statelock(struct ssam_controller *c); ++void ssam_controller_stateunlock(struct ssam_controller *c); ++ ++ssize_t ssam_request_write_data(struct ssam_span *buf, ++ struct ssam_controller *ctrl, ++ struct ssam_request *spec); ++ ++ ++/* -- Synchronous request interface. ---------------------------------------- */ ++ ++struct ssam_request_sync { ++ struct ssh_request base; ++ struct completion comp; ++ struct ssam_response *resp; ++ int status; ++}; ++ ++int ssam_request_sync_alloc(size_t payload_len, gfp_t flags, ++ struct ssam_request_sync **rqst, ++ struct ssam_span *buffer); ++ ++void ssam_request_sync_init(struct ssam_request_sync *rqst, ++ enum ssam_request_flags flags); ++ ++static inline void ssam_request_sync_set_data(struct ssam_request_sync *rqst, ++ u8 *ptr, size_t len) ++{ ++ ssh_request_set_data(&rqst->base, ptr, len); ++} ++ ++static inline void ssam_request_sync_set_resp(struct ssam_request_sync *rqst, ++ struct ssam_response *resp) ++{ ++ rqst->resp = resp; ++} ++ ++int ssam_request_sync_submit(struct ssam_controller *ctrl, ++ struct ssam_request_sync *rqst); ++ ++static inline int ssam_request_sync_wait(struct ssam_request_sync *rqst) ++{ ++ wait_for_completion(&rqst->comp); ++ return rqst->status; ++} ++ ++int ssam_request_sync(struct ssam_controller *ctrl, struct ssam_request *spec, ++ struct ssam_response *rsp); ++ ++int ssam_request_sync_with_buffer(struct ssam_controller *ctrl, ++ struct ssam_request *spec, ++ struct ssam_response *rsp, ++ struct ssam_span *buf); ++ ++ ++#define ssam_request_sync_onstack(ctrl, rqst, rsp, payload_len) \ ++ ({ \ ++ u8 __data[SSH_COMMAND_MESSAGE_LENGTH(payload_len)]; \ ++ struct ssam_span __buf = { &__data[0], ARRAY_SIZE(__data) }; \ ++ int __status; \ ++ \ ++ /* ensure input does not overflow buffer */ \ ++ if ((rqst)->length <= payload_len) { \ ++ __status = ssam_request_sync_with_buffer( \ ++ ctrl, rqst, rsp, &__buf); \ ++ } else { \ ++ __status = -EINVAL; \ ++ } \ ++ \ ++ __status; \ ++ }) ++ ++ ++struct ssam_request_spec { ++ u8 target_category; ++ u8 command_id; ++ u8 instance_id; ++ u8 channel; ++ u8 flags; ++}; ++ ++struct ssam_request_spec_md { ++ u8 target_category; ++ u8 command_id; ++ u8 flags; ++}; ++ ++#define SSAM_DEFINE_SYNC_REQUEST_N(name, spec...) \ ++ int name(struct ssam_controller *ctrl) \ ++ { \ ++ struct ssam_request_spec s = (struct ssam_request_spec)spec; \ ++ struct ssam_request rqst; \ ++ \ ++ rqst.target_category = s.target_category; \ ++ rqst.command_id = s.command_id; \ ++ rqst.instance_id = s.instance_id; \ ++ rqst.channel = s.channel; \ ++ rqst.flags = s.flags; \ ++ rqst.length = 0; \ ++ rqst.payload = NULL; \ ++ \ ++ return ssam_request_sync_onstack(ctrl, &rqst, NULL, 0); \ ++ } ++ ++#define SSAM_DEFINE_SYNC_REQUEST_W(name, wtype, spec...) \ ++ int name(struct ssam_controller *ctrl, const wtype *in) \ ++ { \ ++ struct ssam_request_spec s = (struct ssam_request_spec)spec; \ ++ struct ssam_request rqst; \ ++ \ ++ rqst.target_category = s.target_category; \ ++ rqst.command_id = s.command_id; \ ++ rqst.instance_id = s.instance_id; \ ++ rqst.channel = s.channel; \ ++ rqst.flags = s.flags; \ ++ rqst.length = sizeof(wtype); \ ++ rqst.payload = (u8 *)in; \ ++ \ ++ return ssam_request_sync_onstack(ctrl, &rqst, NULL, \ ++ sizeof(wtype)); \ ++ } ++ ++#define SSAM_DEFINE_SYNC_REQUEST_R(name, rtype, spec...) \ ++ int name(struct ssam_controller *ctrl, rtype *out) \ ++ { \ ++ struct ssam_request_spec s = (struct ssam_request_spec)spec; \ ++ struct ssam_request rqst; \ ++ struct ssam_response rsp; \ ++ int status; \ ++ \ ++ rqst.target_category = s.target_category; \ ++ rqst.command_id = s.command_id; \ ++ rqst.instance_id = s.instance_id; \ ++ rqst.channel = s.channel; \ ++ rqst.flags = s.flags | SSAM_REQUEST_HAS_RESPONSE; \ ++ rqst.length = 0; \ ++ rqst.payload = NULL; \ ++ \ ++ rsp.capacity = sizeof(rtype); \ ++ rsp.length = 0; \ ++ rsp.pointer = (u8 *)out; \ ++ \ ++ status = ssam_request_sync_onstack(ctrl, &rqst, &rsp, 0); \ ++ if (status) \ ++ return status; \ ++ \ ++ if (rsp.length != sizeof(rtype)) { \ ++ struct device *dev = ssam_controller_device(ctrl); \ ++ dev_err(dev, "rqst: invalid response length, expected %zu, got %zu" \ ++ " (tc: 0x%02x, cid: 0x%02x)", sizeof(rtype), \ ++ rsp.length, rqst.target_category, \ ++ rqst.command_id); \ ++ return -EIO; \ ++ } \ ++ \ ++ return 0; \ ++ } ++ ++#define SSAM_DEFINE_SYNC_REQUEST_MD_W(name, wtype, spec...) \ ++ int name(struct ssam_controller *ctrl, u8 chn, u8 iid, const wtype *in) \ ++ { \ ++ struct ssam_request_spec_md s \ ++ = (struct ssam_request_spec_md)spec; \ ++ struct ssam_request rqst; \ ++ \ ++ rqst.target_category = s.target_category; \ ++ rqst.command_id = s.command_id; \ ++ rqst.instance_id = iid; \ ++ rqst.channel = chn; \ ++ rqst.flags = s.flags; \ ++ rqst.length = sizeof(wtype); \ ++ rqst.payload = (u8 *)in; \ ++ \ ++ return ssam_request_sync_onstack(ctrl, &rqst, NULL, \ ++ sizeof(wtype)); \ ++ } ++ ++#define SSAM_DEFINE_SYNC_REQUEST_MD_R(name, rtype, spec...) \ ++ int name(struct ssam_controller *ctrl, u8 chn, u8 iid, rtype *out) \ ++ { \ ++ struct ssam_request_spec_md s \ ++ = (struct ssam_request_spec_md)spec; \ ++ struct ssam_request rqst; \ ++ struct ssam_response rsp; \ ++ int status; \ ++ \ ++ rqst.target_category = s.target_category; \ ++ rqst.command_id = s.command_id; \ ++ rqst.instance_id = iid; \ ++ rqst.channel = chn; \ ++ rqst.flags = s.flags | SSAM_REQUEST_HAS_RESPONSE; \ ++ rqst.length = 0; \ ++ rqst.payload = NULL; \ ++ \ ++ rsp.capacity = sizeof(rtype); \ ++ rsp.length = 0; \ ++ rsp.pointer = (u8 *)out; \ ++ \ ++ status = ssam_request_sync_onstack(ctrl, &rqst, &rsp, 0); \ ++ if (status) \ ++ return status; \ ++ \ ++ if (rsp.length != sizeof(rtype)) { \ ++ struct device *dev = ssam_controller_device(ctrl); \ ++ dev_err(dev, "rqst: invalid response length, expected %zu, got %zu" \ ++ " (tc: 0x%02x, cid: 0x%02x)", sizeof(rtype), \ ++ rsp.length, rqst.target_category, \ ++ rqst.command_id); \ ++ return -EIO; \ ++ } \ ++ \ ++ return 0; \ ++ } ++ ++ ++/* -- Event notifier/callbacks. --------------------------------------------- */ ++ ++#define SSAM_NOTIF_STATE_SHIFT 2 ++#define SSAM_NOTIF_STATE_MASK ((1 << SSAM_NOTIF_STATE_SHIFT) - 1) ++ ++#define SSAM_NOTIF_HANDLED BIT(0) ++#define SSAM_NOTIF_STOP BIT(1) ++ ++ ++struct ssam_notifier_block; ++ ++typedef u32 (*ssam_notifier_fn_t)(struct ssam_notifier_block *nb, ++ const struct ssam_event *event); ++ ++struct ssam_notifier_block { ++ struct ssam_notifier_block __rcu *next; ++ ssam_notifier_fn_t fn; ++ int priority; ++}; ++ ++ ++static inline u32 ssam_notifier_from_errno(int err) ++{ ++ if (WARN_ON(err > 0) || err == 0) ++ return 0; ++ else ++ return ((-err) << SSAM_NOTIF_STATE_SHIFT) | SSAM_NOTIF_STOP; ++} ++ ++static inline int ssam_notifier_to_errno(u32 ret) ++{ ++ return -(ret >> SSAM_NOTIF_STATE_SHIFT); ++} ++ ++ ++/* -- Event/notification registry. ------------------------------------------ */ ++ ++struct ssam_event_registry { ++ u8 target_category; ++ u8 channel; ++ u8 cid_enable; ++ u8 cid_disable; ++}; ++ ++struct ssam_event_id { ++ u8 target_category; ++ u8 instance; ++}; ++ ++ ++#define SSAM_EVENT_REGISTRY(tc, chn, cid_en, cid_dis) \ ++ ((struct ssam_event_registry) { \ ++ .target_category = (tc), \ ++ .channel = (chn), \ ++ .cid_enable = (cid_en), \ ++ .cid_disable = (cid_dis), \ ++ }) ++ ++#define SSAM_EVENT_ID(tc, iid) \ ++ ((struct ssam_event_id) { \ ++ .target_category = (tc), \ ++ .instance = (iid), \ ++ }) ++ ++ ++#define SSAM_EVENT_REGISTRY_SAM \ ++ SSAM_EVENT_REGISTRY(SSAM_SSH_TC_SAM, 0x01, 0x0b, 0x0c) ++ ++#define SSAM_EVENT_REGISTRY_KIP \ ++ SSAM_EVENT_REGISTRY(SSAM_SSH_TC_KIP, 0x02, 0x27, 0x28) ++ ++#define SSAM_EVENT_REGISTRY_REG \ ++ SSAM_EVENT_REGISTRY(SSAM_SSH_TC_REG, 0x02, 0x01, 0x02) ++ ++ ++struct ssam_event_notifier { ++ struct ssam_notifier_block base; ++ ++ struct { ++ struct ssam_event_registry reg; ++ struct ssam_event_id id; ++ u8 flags; ++ } event; ++}; ++ ++int ssam_notifier_register(struct ssam_controller *ctrl, ++ struct ssam_event_notifier *n); ++ ++int ssam_notifier_unregister(struct ssam_controller *ctrl, ++ struct ssam_event_notifier *n); ++ ++ ++/* -- Surface System Aggregator Module Bus. --------------------------------- */ ++ ++struct ssam_device_uid { ++ u8 category; ++ u8 channel; ++ u8 instance; ++ u8 function; ++}; ++ ++#define SSAM_DUID(__cat, __chn, __iid, __fun) \ ++ ((struct ssam_device_uid) { \ ++ .category = SSAM_SSH_TC_##__cat, \ ++ .channel = (__chn), \ ++ .instance = (__iid), \ ++ .function = (__fun) \ ++ }) ++ ++#define SSAM_DUID_NULL ((struct ssam_device_uid) { 0 }) ++ ++#define SSAM_ANY_CHN 0xffff ++#define SSAM_ANY_IID 0xffff ++#define SSAM_ANY_FUN 0xffff ++ ++#define SSAM_DEVICE(__cat, __chn, __iid, __fun) \ ++ .match_flags = (((__chn) != SSAM_ANY_CHN) ? SSAM_MATCH_CHANNEL : 0) \ ++ | (((__iid) != SSAM_ANY_IID) ? SSAM_MATCH_INSTANCE : 0) \ ++ | (((__fun) != SSAM_ANY_FUN) ? SSAM_MATCH_FUNCTION : 0), \ ++ .category = SSAM_SSH_TC_##__cat, \ ++ .channel = ((__chn) != SSAM_ANY_CHN) ? (__chn) : 0, \ ++ .instance = ((__iid) != SSAM_ANY_IID) ? (__iid) : 0, \ ++ .function = ((__fun) != SSAM_ANY_FUN) ? (__fun) : 0 \ ++ ++static inline bool ssam_device_uid_equal(const struct ssam_device_uid u1, ++ const struct ssam_device_uid u2) ++{ ++ return memcmp(&u1, &u2, sizeof(struct ssam_device_uid)) == 0; ++} ++ ++static inline bool ssam_device_uid_is_null(const struct ssam_device_uid uid) ++{ ++ return ssam_device_uid_equal(uid, (struct ssam_device_uid){}); ++} ++ ++ ++struct ssam_device { ++ struct device dev; ++ struct ssam_controller *ctrl; ++ ++ struct ssam_device_uid uid; ++}; ++ ++struct ssam_device_driver { ++ struct device_driver driver; ++ ++ const struct ssam_device_id *match_table; ++ ++ int (*probe)(struct ssam_device *); ++ void (*remove)(struct ssam_device *); ++}; ++ ++extern struct bus_type ssam_bus_type; ++extern const struct device_type ssam_device_type; ++ ++ ++static inline bool is_ssam_device(struct device *device) ++{ ++ return device->type == &ssam_device_type; ++} ++ ++static inline struct ssam_device *to_ssam_device(struct device *d) ++{ ++ return container_of(d, struct ssam_device, dev); ++} ++ ++static inline ++struct ssam_device_driver *to_ssam_device_driver(struct device_driver *d) ++{ ++ return container_of(d, struct ssam_device_driver, driver); ++} ++ ++ ++const struct ssam_device_id *ssam_device_id_match( ++ const struct ssam_device_id *table, ++ const struct ssam_device_uid uid); ++ ++const struct ssam_device_id *ssam_device_get_match( ++ const struct ssam_device *dev); ++ ++const void *ssam_device_get_match_data(const struct ssam_device *dev); ++ ++struct ssam_device *ssam_device_alloc(struct ssam_controller *ctrl, ++ struct ssam_device_uid uid); ++ ++int ssam_device_add(struct ssam_device *sdev); ++void ssam_device_remove(struct ssam_device *sdev); ++ ++static inline void ssam_device_get(struct ssam_device *sdev) ++{ ++ get_device(&sdev->dev); ++} ++ ++static inline void ssam_device_put(struct ssam_device *sdev) ++{ ++ put_device(&sdev->dev); ++} ++ ++static inline void *ssam_device_get_drvdata(struct ssam_device *sdev) ++{ ++ return dev_get_drvdata(&sdev->dev); ++} ++ ++static inline void ssam_device_set_drvdata(struct ssam_device *sdev, void *data) ++{ ++ dev_set_drvdata(&sdev->dev, data); ++} ++ ++ ++int __ssam_device_driver_register(struct ssam_device_driver *d, struct module *o); ++void ssam_device_driver_unregister(struct ssam_device_driver *d); ++ ++#define ssam_device_driver_register(drv) \ ++ __ssam_device_driver_register(drv, THIS_MODULE) ++ ++#define module_ssam_device_driver(__drv) \ ++ module_driver(__drv, ssam_device_driver_register, \ ++ ssam_device_driver_unregister) ++ ++ ++/* -- Helpers for client-device requests. ----------------------------------- */ ++ ++#define SSAM_DEFINE_SYNC_REQUEST_CL_W(name, wtype, spec...) \ ++ SSAM_DEFINE_SYNC_REQUEST_MD_W(__raw_##name, wtype, spec) \ ++ int name(struct ssam_device *sdev, const wtype *in) \ ++ { \ ++ return __raw_##name(sdev->ctrl, sdev->uid.channel, \ ++ sdev->uid.instance, in); \ ++ } ++ ++#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 *out) \ ++ { \ ++ return __raw_##name(sdev->ctrl, sdev->uid.channel, \ ++ sdev->uid.instance, out); \ ++ } ++ ++ ++static inline bool ssam_event_matches_device(struct ssam_device_uid uid, ++ const struct ssam_event *event) ++{ ++ return uid.category == event->target_category ++ && uid.channel == event->channel ++ && uid.instance == event->instance_id; ++} ++ ++#endif /* _SURFACE_AGGREGATOR_MODULE_H */ -- 2.28.0 diff --git a/patches/4.19/0006-surface-sam-over-hid.patch b/patches/4.19/0009-surface-sam-over-hid.patch similarity index 94% rename from patches/4.19/0006-surface-sam-over-hid.patch rename to patches/4.19/0009-surface-sam-over-hid.patch index 6f0cd8ae6..d399e4af8 100644 --- a/patches/4.19/0006-surface-sam-over-hid.patch +++ b/patches/4.19/0009-surface-sam-over-hid.patch @@ -1,7 +1,7 @@ -From dff041886f4e1e942fcabb40a2fc1a6e9556374f Mon Sep 17 00:00:00 2001 +From 6ee10706e5bf1737d7757a14f3ed97fab6fa269c Mon Sep 17 00:00:00 2001 From: Maximilian Luz Date: Sat, 25 Jul 2020 17:19:53 +0200 -Subject: [PATCH 6/9] surface-sam-over-hid +Subject: [PATCH 09/10] surface-sam-over-hid --- drivers/i2c/i2c-core-acpi.c | 35 +++++++++++++++++++++++++++++++++++ diff --git a/patches/4.19/0010-surface-gpe.patch b/patches/4.19/0010-surface-gpe.patch new file mode 100644 index 000000000..3da06066a --- /dev/null +++ b/patches/4.19/0010-surface-gpe.patch @@ -0,0 +1,355 @@ +From 77983168ece982bcb6aa6940d778f408d0005b6c Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Sun, 16 Aug 2020 23:39:56 +0200 +Subject: [PATCH 10/10] surface-gpe + +--- + drivers/platform/x86/Kconfig | 9 + + drivers/platform/x86/Makefile | 1 + + drivers/platform/x86/surface_gpe.c | 302 +++++++++++++++++++++++++++++ + 3 files changed, 312 insertions(+) + create mode 100644 drivers/platform/x86/surface_gpe.c + +diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig +index 2ad19dc64a4af..a95121eae8965 100644 +--- a/drivers/platform/x86/Kconfig ++++ b/drivers/platform/x86/Kconfig +@@ -1167,6 +1167,15 @@ config SURFACE_3_POWER_OPREGION + Select this option to enable support for ACPI operation + region of the Surface 3 battery platform driver. + ++config SURFACE_GPE ++ tristate "Surface GPE/Lid Driver" ++ depends on ACPI ++ help ++ This driver marks the GPEs related to the ACPI lid device found on ++ Microsoft Surface devices as wakeup sources and prepares them ++ accordingly. It is required on those devices to allow wake-ups from ++ suspend by opening the lid. ++ + config INTEL_PUNIT_IPC + tristate "Intel P-Unit IPC Driver" + ---help--- +diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile +index 2ea90039a3e49..49238e9d4abf6 100644 +--- a/drivers/platform/x86/Makefile ++++ b/drivers/platform/x86/Makefile +@@ -82,6 +82,7 @@ obj-$(CONFIG_TOUCHSCREEN_DMI) += touchscreen_dmi.o + obj-$(CONFIG_SURFACE_PRO3_BUTTON) += surfacepro3_button.o + obj-$(CONFIG_SURFACE_3_BUTTON) += surface3_button.o + obj-$(CONFIG_SURFACE_3_POWER_OPREGION) += surface3_power.o ++obj-$(CONFIG_SURFACE_GPE) += surface_gpe.o + obj-$(CONFIG_INTEL_PUNIT_IPC) += intel_punit_ipc.o + obj-$(CONFIG_INTEL_BXTWC_PMIC_TMU) += intel_bxtwc_tmu.o + obj-$(CONFIG_INTEL_TELEMETRY) += intel_telemetry_core.o \ +diff --git a/drivers/platform/x86/surface_gpe.c b/drivers/platform/x86/surface_gpe.c +new file mode 100644 +index 0000000000000..fee4cde12f05e +--- /dev/null ++++ b/drivers/platform/x86/surface_gpe.c +@@ -0,0 +1,302 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Pseudo device and driver to enable wakeup from suspend via the lid by ++ * properly configuring the respective GPEs on Microsoft Surface devices. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++ ++ ++struct surface_lid_device { ++ u32 gpe_number; ++}; ++ ++static const struct surface_lid_device lid_device_l17 = { ++ .gpe_number = 0x17, ++}; ++ ++static const struct surface_lid_device lid_device_l4D = { ++ .gpe_number = 0x4D, ++}; ++ ++static const struct surface_lid_device lid_device_l4F = { ++ .gpe_number = 0x4F, ++}; ++ ++static const struct surface_lid_device lid_device_l57 = { ++ .gpe_number = 0x57, ++}; ++ ++ ++// Note: When changing this don't forget to change the MODULE_ALIAS below. ++static const struct dmi_system_id dmi_lid_device_table[] = { ++ { ++ .ident = "Surface Pro 4", ++ .matches = { ++ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), ++ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 4"), ++ }, ++ .driver_data = (void *)&lid_device_l17, ++ }, ++ { ++ .ident = "Surface Pro 5", ++ .matches = { ++ /* ++ * We match for SKU here due to generic product name ++ * "Surface Pro". ++ */ ++ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), ++ DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1796"), ++ }, ++ .driver_data = (void *)&lid_device_l4F, ++ }, ++ { ++ .ident = "Surface Pro 5 (LTE)", ++ .matches = { ++ /* ++ * We match for SKU here due to generic product name ++ * "Surface Pro" ++ */ ++ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), ++ DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1807"), ++ }, ++ .driver_data = (void *)&lid_device_l4F, ++ }, ++ { ++ .ident = "Surface Pro 6", ++ .matches = { ++ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), ++ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 6"), ++ }, ++ .driver_data = (void *)&lid_device_l4F, ++ }, ++ { ++ .ident = "Surface Pro 7", ++ .matches = { ++ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), ++ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 7"), ++ }, ++ .driver_data = (void *)&lid_device_l4D, ++ }, ++ { ++ .ident = "Surface Book 1", ++ .matches = { ++ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), ++ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book"), ++ }, ++ .driver_data = (void *)&lid_device_l17, ++ }, ++ { ++ .ident = "Surface Book 2", ++ .matches = { ++ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), ++ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book 2"), ++ }, ++ .driver_data = (void *)&lid_device_l17, ++ }, ++ { ++ .ident = "Surface Book 3", ++ .matches = { ++ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), ++ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book 3"), ++ }, ++ .driver_data = (void *)&lid_device_l4D, ++ }, ++ { ++ .ident = "Surface Laptop 1", ++ .matches = { ++ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), ++ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop"), ++ }, ++ .driver_data = (void *)&lid_device_l57, ++ }, ++ { ++ .ident = "Surface Laptop 2", ++ .matches = { ++ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), ++ DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop 2"), ++ }, ++ .driver_data = (void *)&lid_device_l57, ++ }, ++ { ++ .ident = "Surface Laptop 3 (Intel 13\")", ++ .matches = { ++ /* ++ * We match for SKU here due to different vairants: The ++ * AMD (15") version does not rely on GPEs. ++ */ ++ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), ++ DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Laptop_3_1867:1868"), ++ }, ++ .driver_data = (void *)&lid_device_l4D, ++ }, ++ { } ++}; ++ ++ ++static int surface_lid_enable_wakeup(struct device *dev, ++ const struct surface_lid_device *lid, ++ bool enable) ++{ ++ int action = enable ? ACPI_GPE_ENABLE : ACPI_GPE_DISABLE; ++ acpi_status status; ++ ++ status = acpi_set_gpe_wake_mask(NULL, lid->gpe_number, action); ++ if (status) { ++ dev_err(dev, "failed to set GPE wake mask: %d\n", status); ++ return -EINVAL; ++ } ++ ++ return 0; ++} ++ ++ ++static int surface_gpe_suspend(struct device *dev) ++{ ++ const struct surface_lid_device *lid; ++ ++ lid = dev_get_platdata(dev); ++ return surface_lid_enable_wakeup(dev, lid, true); ++} ++ ++static int surface_gpe_resume(struct device *dev) ++{ ++ const struct surface_lid_device *lid; ++ ++ lid = dev_get_platdata(dev); ++ return surface_lid_enable_wakeup(dev, lid, false); ++} ++ ++static SIMPLE_DEV_PM_OPS(surface_gpe_pm, surface_gpe_suspend, surface_gpe_resume); ++ ++ ++static int surface_gpe_probe(struct platform_device *pdev) ++{ ++ const struct surface_lid_device *lid; ++ int status; ++ ++ lid = dev_get_platdata(&pdev->dev); ++ if (!lid) ++ return -ENODEV; ++ ++ status = acpi_mark_gpe_for_wake(NULL, lid->gpe_number); ++ if (status) { ++ dev_err(&pdev->dev, "failed to mark GPE for wake: %d\n", status); ++ return -EINVAL; ++ } ++ ++ status = acpi_enable_gpe(NULL, lid->gpe_number); ++ if (status) { ++ dev_err(&pdev->dev, "failed to enable GPE: %d\n", status); ++ return -EINVAL; ++ } ++ ++ status = surface_lid_enable_wakeup(&pdev->dev, lid, false); ++ if (status) { ++ acpi_disable_gpe(NULL, lid->gpe_number); ++ return status; ++ } ++ ++ return 0; ++} ++ ++static int surface_gpe_remove(struct platform_device *pdev) ++{ ++ struct surface_lid_device *lid = dev_get_platdata(&pdev->dev); ++ ++ /* restore default behavior without this module */ ++ surface_lid_enable_wakeup(&pdev->dev, lid, false); ++ acpi_disable_gpe(NULL, lid->gpe_number); ++ ++ return 0; ++} ++ ++static struct platform_driver surface_gpe_driver = { ++ .probe = surface_gpe_probe, ++ .remove = surface_gpe_remove, ++ .driver = { ++ .name = "surface_gpe", ++ .pm = &surface_gpe_pm, ++ .probe_type = PROBE_PREFER_ASYNCHRONOUS, ++ }, ++}; ++ ++ ++static struct platform_device *surface_gpe_device; ++ ++static int __init surface_gpe_init(void) ++{ ++ const struct dmi_system_id *match; ++ const struct surface_lid_device *lid; ++ ++ struct platform_device *pdev; ++ int status; ++ ++ surface_gpe_device = NULL; ++ ++ match = dmi_first_match(dmi_lid_device_table); ++ if (!match) { ++ pr_info(KBUILD_MODNAME": no device detected, exiting\n"); ++ return 0; ++ } ++ ++ lid = match->driver_data; ++ ++ status = platform_driver_register(&surface_gpe_driver); ++ if (status) ++ return status; ++ ++ pdev = platform_device_alloc("surface_gpe", PLATFORM_DEVID_NONE); ++ if (!pdev) { ++ platform_driver_unregister(&surface_gpe_driver); ++ return -ENOMEM; ++ } ++ ++ status = platform_device_add_data(pdev, lid, sizeof(*lid)); ++ if (status) { ++ platform_device_put(pdev); ++ platform_driver_unregister(&surface_gpe_driver); ++ return status; ++ } ++ ++ status = platform_device_add(pdev); ++ if (status) { ++ platform_device_put(pdev); ++ platform_driver_unregister(&surface_gpe_driver); ++ return status; ++ } ++ ++ surface_gpe_device = pdev; ++ return 0; ++} ++ ++static void __exit surface_gpe_exit(void) ++{ ++ if (!surface_gpe_device) ++ return; ++ ++ platform_device_unregister(surface_gpe_device); ++ platform_driver_unregister(&surface_gpe_driver); ++} ++ ++module_init(surface_gpe_init); ++module_exit(surface_gpe_exit); ++ ++MODULE_AUTHOR("Maximilian Luz "); ++MODULE_DESCRIPTION("Surface GPE/Lid Driver"); ++MODULE_LICENSE("GPL"); ++ ++MODULE_ALIAS("dmi:*:svnMicrosoftCorporation:pnSurfacePro:*"); ++MODULE_ALIAS("dmi:*:svnMicrosoftCorporation:pnSurfacePro4:*"); ++MODULE_ALIAS("dmi:*:svnMicrosoftCorporation:pnSurfacePro6:*"); ++MODULE_ALIAS("dmi:*:svnMicrosoftCorporation:pnSurfacePro7:*"); ++MODULE_ALIAS("dmi:*:svnMicrosoftCorporation:pnSurfaceBook:*"); ++MODULE_ALIAS("dmi:*:svnMicrosoftCorporation:pnSurfaceBook2:*"); ++MODULE_ALIAS("dmi:*:svnMicrosoftCorporation:pnSurfaceBook3:*"); ++MODULE_ALIAS("dmi:*:svnMicrosoftCorporation:pnSurfaceLaptop:*"); ++MODULE_ALIAS("dmi:*:svnMicrosoftCorporation:pnSurfaceLaptop2:*"); ++MODULE_ALIAS("dmi:*:svnMicrosoftCorporation:pnSurfaceLaptop3:*"); +-- +2.28.0 + diff --git a/pkg/arch/kernel-lts/0005-surface-sam.patch b/pkg/arch/kernel-lts/0005-surface-sam.patch deleted file mode 120000 index ae67c0e2d..000000000 --- a/pkg/arch/kernel-lts/0005-surface-sam.patch +++ /dev/null @@ -1 +0,0 @@ -../../../patches/4.19/0005-surface-sam.patch \ No newline at end of file diff --git a/pkg/arch/kernel-lts/0005-suspend.patch b/pkg/arch/kernel-lts/0005-suspend.patch new file mode 120000 index 000000000..23ac5443e --- /dev/null +++ b/pkg/arch/kernel-lts/0005-suspend.patch @@ -0,0 +1 @@ +../../../patches/4.19/0005-suspend.patch \ No newline at end of file diff --git a/pkg/arch/kernel-lts/0006-ipts.patch b/pkg/arch/kernel-lts/0006-ipts.patch new file mode 120000 index 000000000..201ed09b3 --- /dev/null +++ b/pkg/arch/kernel-lts/0006-ipts.patch @@ -0,0 +1 @@ +../../../patches/4.19/0006-ipts.patch \ No newline at end of file diff --git a/pkg/arch/kernel-lts/0006-surface-sam-over-hid.patch b/pkg/arch/kernel-lts/0006-surface-sam-over-hid.patch deleted file mode 120000 index 2e647f385..000000000 --- a/pkg/arch/kernel-lts/0006-surface-sam-over-hid.patch +++ /dev/null @@ -1 +0,0 @@ -../../../patches/4.19/0006-surface-sam-over-hid.patch \ No newline at end of file diff --git a/pkg/arch/kernel-lts/0007-suspend.patch b/pkg/arch/kernel-lts/0007-suspend.patch deleted file mode 120000 index 88cdf3251..000000000 --- a/pkg/arch/kernel-lts/0007-suspend.patch +++ /dev/null @@ -1 +0,0 @@ -../../../patches/4.19/0007-suspend.patch \ No newline at end of file diff --git a/pkg/arch/kernel-lts/0007-wifi.patch b/pkg/arch/kernel-lts/0007-wifi.patch new file mode 120000 index 000000000..aa01faf09 --- /dev/null +++ b/pkg/arch/kernel-lts/0007-wifi.patch @@ -0,0 +1 @@ +../../../patches/4.19/0007-wifi.patch \ No newline at end of file diff --git a/pkg/arch/kernel-lts/0008-ipts.patch b/pkg/arch/kernel-lts/0008-ipts.patch deleted file mode 120000 index 15a04b316..000000000 --- a/pkg/arch/kernel-lts/0008-ipts.patch +++ /dev/null @@ -1 +0,0 @@ -../../../patches/4.19/0008-ipts.patch \ No newline at end of file diff --git a/pkg/arch/kernel-lts/0008-surface-sam.patch b/pkg/arch/kernel-lts/0008-surface-sam.patch new file mode 120000 index 000000000..ecaca8ac4 --- /dev/null +++ b/pkg/arch/kernel-lts/0008-surface-sam.patch @@ -0,0 +1 @@ +../../../patches/4.19/0008-surface-sam.patch \ No newline at end of file diff --git a/pkg/arch/kernel-lts/0009-surface-sam-over-hid.patch b/pkg/arch/kernel-lts/0009-surface-sam-over-hid.patch new file mode 120000 index 000000000..ebd5102b0 --- /dev/null +++ b/pkg/arch/kernel-lts/0009-surface-sam-over-hid.patch @@ -0,0 +1 @@ +../../../patches/4.19/0009-surface-sam-over-hid.patch \ No newline at end of file diff --git a/pkg/arch/kernel-lts/0009-wifi.patch b/pkg/arch/kernel-lts/0009-wifi.patch deleted file mode 120000 index f248e61c2..000000000 --- a/pkg/arch/kernel-lts/0009-wifi.patch +++ /dev/null @@ -1 +0,0 @@ -../../../patches/4.19/0009-wifi.patch \ No newline at end of file diff --git a/pkg/arch/kernel-lts/0010-surface-gpe.patch b/pkg/arch/kernel-lts/0010-surface-gpe.patch new file mode 120000 index 000000000..fcd545d35 --- /dev/null +++ b/pkg/arch/kernel-lts/0010-surface-gpe.patch @@ -0,0 +1 @@ +../../../patches/4.19/0010-surface-gpe.patch \ No newline at end of file diff --git a/pkg/arch/kernel-lts/PKGBUILD b/pkg/arch/kernel-lts/PKGBUILD index 9dc68f4ca..8ea475a95 100644 --- a/pkg/arch/kernel-lts/PKGBUILD +++ b/pkg/arch/kernel-lts/PKGBUILD @@ -24,11 +24,12 @@ source=( 0002-surface3-touchscreen-dma-fix.patch 0003-surface3-oemb.patch 0004-surface-buttons.patch - 0005-surface-sam.patch - 0006-surface-sam-over-hid.patch - 0007-suspend.patch - 0008-ipts.patch - 0009-wifi.patch + 0005-suspend.patch + 0006-ipts.patch + 0007-wifi.patch + 0008-surface-sam.patch + 0009-surface-sam-over-hid.patch + 0010-surface-gpe.patch ) validpgpkeys=( 'ABAF11C65A2970B130ABE3C479BE3E4300411886' # Linus Torvalds @@ -39,16 +40,17 @@ sha256sums=('9c4ebf21fe949f80fbcfbbd6e7fe181040d325e89475e230ab53ef01f9d55605' 'SKIP' '4e68572e7cc4c5368f0236e0792660ae8498373988625dca46e509399a7eaea6' 'a13581d3c6dc595206e4fe7fcf6b542e7a1bdbe96101f0f010fc5be49f99baf2' - '930997898a624430f476e9add2ae22a1953f30d28b0f88c92cedc49d522dd150' - '91ffa503907109951878e1ef58dd5ebe4deef7a75a3c4e06dbad747418869acb' - '02b670f7e2f1ab7ae00aebbc3569d7757a228fb662ae81203bacb1f704dd9c5a' - '38e4c43a780f62582340bfcd7a1e22558b5d98719e8463e35dec6571322fe51a' - '3dbcdc0cf83fbc1a97634ab736ddfc2518034758c2aed19ac406a3a1dee40377' - '6c521827ec9d6dcd89c895c1dc140457163347311adb4f441f62d6fb6ecaca26' - 'bfbc9ce608433badd5b856f2941d4c71070bb8e01b97313091d4339766f0fa4c' - 'a9d05b5564ada9305a87eabdc78b7d27d58d597481b49c98af93319bdc57f83e' - '56d31e9aa1425403c6c1cd7dc9a79d026c866bdd29effdb951bfaea91816386c' - '44c73f31333c98049f287a1949ce12d47d6d1810fca22b90aa3b5d21bfa648b6') + 'c04b7652fbc7dbca65bfd64e7123adcb6da9dae0ab33d85be58907b5b9de1c66' + 'fd7947445ac061cd6e1fe0f08ac544cb1f64acef373ebbafff8fc35e1e5fca2e' + '60fa93019ad2ad2a915d532761c0d496a991f133cd0dfac0e10933a06e804b1c' + 'eb49152ccffac67adde2ccc3851754d5c0276657ba979d0cacd4ea47110dd473' + '60d09f7234cb4ed0d5dbdc9444c5815c459e2cd89926e95ffadef67369238614' + '49de4afebf12d41bc0a484ab3f4267041f6c6c4f62f1af7429e23acd2eb54d0f' + '6381243b6fe07dd45b14450d44041d6e962174e3bd32e726a34d0240064f60f8' + 'e7088d9aace068df6d1cd5d77262cc5756c8bfea046c11b2acaba43b43b0baca' + '939fa0ead281a4ca23ac9c8d935c84e612fd96148a53d68a055d813eb0ea9550' + '639419729d561a5a1c792ad151830f256a0b9bdf284019ec2646748365e69096' + '1ad17b0a6dffe38d260dc0e46a3fc390f9ee019f4d59708e13732c3ee50e13f1') export KBUILD_BUILD_HOST=archlinux export KBUILD_BUILD_USER=$pkgbase