diff --git a/configs/surface-5.4.config b/configs/surface-5.4.config index 0c274673f..7566a5bcc 100644 --- a/configs/surface-5.4.config +++ b/configs/surface-5.4.config @@ -1,19 +1,19 @@ # # Surface Aggregator Module # -CONFIG_GPIO_SYSFS=y # required for SURFACE_SAM_HPS -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_KBD=m -CONFIG_SURFACE_SAM_DTX=m -CONFIG_SURFACE_SAM_HPS=m -CONFIG_SURFACE_SAM_PERFMODE=m -CONFIG_SURFACE_SAM_HID=m -CONFIG_SURFACE_SAM_PSY=m +CONFIG_GPIO_SYSFS=y # required for SURFACE_HOTPLUG +CONFIG_SURFACE_AGGREGATOR=m +CONFIG_SURFACE_AGGREGATOR_ERROR_INJECTION=n +CONFIG_SURFACE_AGGREGATOR_BUS=y +CONFIG_SURFACE_AGGREGATOR_DEBUGFS=m +CONFIG_SURFACE_AGGREGATOR_REGISTRY=m +CONFIG_SURFACE_ACPI_NOTIFY=m +CONFIG_SURFACE_BATTERY=m +CONFIG_SURFACE_DTX=m +CONFIG_SURFACE_HID=m +CONFIG_SURFACE_HOTPLUG=m +CONFIG_SURFACE_KEYBOARD=m +CONFIG_SURFACE_PERFMODE=m # # IPTS touchscreen diff --git a/patches/5.4/0001-surface3-power.patch b/patches/5.4/0001-surface3-power.patch index c407bbc7c..300a70531 100644 --- a/patches/5.4/0001-surface3-power.patch +++ b/patches/5.4/0001-surface3-power.patch @@ -1,4 +1,4 @@ -From 0d51533624bd156df8d97aa4280040c7c7e362b4 Mon Sep 17 00:00:00 2001 +From d882e5f0af12470cc9c8bdf33acde2ca60cf027f Mon Sep 17 00:00:00 2001 From: qzed Date: Tue, 17 Sep 2019 17:17:56 +0200 Subject: [PATCH 1/7] surface3-power diff --git a/patches/5.4/0002-surface3-oemb.patch b/patches/5.4/0002-surface3-oemb.patch index addbd59e4..29f2788c7 100644 --- a/patches/5.4/0002-surface3-oemb.patch +++ b/patches/5.4/0002-surface3-oemb.patch @@ -1,4 +1,4 @@ -From 644f7558884abc017463703239a02ba1b2255e04 Mon Sep 17 00:00:00 2001 +From f5e1881913400187c698b3ddfd68d015c077d671 Mon Sep 17 00:00:00 2001 From: Chih-Wei Huang Date: Tue, 18 Sep 2018 11:01:37 +0800 Subject: [PATCH 2/7] surface3-oemb diff --git a/patches/5.4/0003-wifi.patch b/patches/5.4/0003-wifi.patch index 6e9c8e650..7212423f9 100644 --- a/patches/5.4/0003-wifi.patch +++ b/patches/5.4/0003-wifi.patch @@ -1,4 +1,4 @@ -From 830c1239c7459f608d566b142f426b67805814ca Mon Sep 17 00:00:00 2001 +From abac510de08eda2e3700f3d55fe82bb62a3b0360 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 3/7] wifi diff --git a/patches/5.4/0004-ipts.patch b/patches/5.4/0004-ipts.patch index 828582741..cd3120a96 100644 --- a/patches/5.4/0004-ipts.patch +++ b/patches/5.4/0004-ipts.patch @@ -1,4 +1,4 @@ -From 79bbcb2e3777ae6c824cacdbf03898e0a8301a69 Mon Sep 17 00:00:00 2001 +From 687a6a4f4b10a78839a0d64db38ca07baab7e80a Mon Sep 17 00:00:00 2001 From: Dorian Stoll Date: Mon, 27 Jan 2020 21:16:20 +0100 Subject: [PATCH 4/7] ipts diff --git a/patches/5.4/0005-surface-sam.patch b/patches/5.4/0005-surface-sam.patch index 99bb53e31..42a67ca96 100644 --- a/patches/5.4/0005-surface-sam.patch +++ b/patches/5.4/0005-surface-sam.patch @@ -1,141 +1,162 @@ -From 6196060ec0cacf88b487d5880b3ba70c2cc5d8ae Mon Sep 17 00:00:00 2001 +From d7e8113fe37ed0d3970029fcf61e5fdf4e8b6d24 Mon Sep 17 00:00:00 2001 From: qzed Date: Mon, 26 Aug 2019 01:11:08 +0200 Subject: [PATCH 5/7] surface-sam --- Documentation/driver-api/index.rst | 1 + - Documentation/driver-api/ssam/client-api.rst | 19 + - Documentation/driver-api/ssam/client.rst | 381 +++ - .../driver-api/ssam/clients/dbgdev.rst | 128 + - .../driver-api/ssam/clients/index.rst | 21 + - Documentation/driver-api/ssam/clients/san.rst | 44 + - Documentation/driver-api/ssam/index.rst | 21 + - .../driver-api/ssam/internal-api.rst | 70 + - Documentation/driver-api/ssam/internal.rst | 50 + - Documentation/driver-api/ssam/overview.rst | 76 + - Documentation/driver-api/ssam/ssh.rst | 343 +++ + .../surface_aggregator/client-api.rst | 38 + + .../driver-api/surface_aggregator/client.rst | 394 +++ + .../surface_aggregator/clients/dbgdev.rst | 130 + + .../surface_aggregator/clients/index.rst | 21 + + .../surface_aggregator/clients/san.rst | 44 + + .../driver-api/surface_aggregator/index.rst | 21 + + .../surface_aggregator/internal-api.rst | 67 + + .../surface_aggregator/internal.rst | 50 + + .../surface_aggregator/overview.rst | 76 + + .../driver-api/surface_aggregator/ssh.rst | 343 +++ drivers/acpi/acpica/dsopcode.c | 2 +- drivers/acpi/acpica/exfield.c | 12 +- 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 | 414 +++ - 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 | 282 ++ - .../clients/surface_sam_device_hub.c | 583 ++++ - .../surface_sam/clients/surface_sam_dtx.c | 583 ++++ - .../surface_sam/clients/surface_sam_hps.c | 1296 +++++++++ - .../surface_sam/clients/surface_sam_san.c | 841 ++++++ - .../clients/surface_sam_sid_perfmode.c | 190 ++ - .../clients/surface_sam_sid_power.c | 1114 ++++++++ - .../surface_sam/clients/surface_sam_sid_vhf.c | 498 ++++ - .../surface_sam/clients/surface_sam_vhf.c | 334 +++ - drivers/misc/surface_sam/controller.c | 2441 +++++++++++++++++ - drivers/misc/surface_sam/controller.h | 275 ++ - drivers/misc/surface_sam/core.c | 771 ++++++ - drivers/misc/surface_sam/ssam_trace.h | 619 +++++ - drivers/misc/surface_sam/ssh_msgb.h | 196 ++ - drivers/misc/surface_sam/ssh_packet_layer.c | 2001 ++++++++++++++ - drivers/misc/surface_sam/ssh_packet_layer.h | 173 ++ - drivers/misc/surface_sam/ssh_parser.c | 221 ++ - drivers/misc/surface_sam/ssh_parser.h | 151 + - drivers/misc/surface_sam/ssh_protocol.h | 103 + - drivers/misc/surface_sam/ssh_request_layer.c | 1246 +++++++++ - drivers/misc/surface_sam/ssh_request_layer.h | 136 + + drivers/misc/surface_aggregator/Kconfig | 65 + + drivers/misc/surface_aggregator/Makefile | 17 + + drivers/misc/surface_aggregator/bus.c | 419 +++ + drivers/misc/surface_aggregator/bus.h | 22 + + .../misc/surface_aggregator/clients/Kconfig | 154 + + .../misc/surface_aggregator/clients/Makefile | 11 + + .../clients/surface_acpi_notify.c | 844 ++++++ + .../clients/surface_aggregator_debugfs.c | 280 ++ + .../clients/surface_aggregator_registry.c | 601 ++++ + .../clients/surface_battery.c | 1096 +++++++ + .../surface_aggregator/clients/surface_dtx.c | 589 ++++ + .../surface_aggregator/clients/surface_hid.c | 493 ++++ + .../clients/surface_hotplug.c | 1282 +++++++++ + .../clients/surface_keyboard.c | 331 +++ + .../clients/surface_perfmode.c | 194 ++ + drivers/misc/surface_aggregator/controller.c | 2509 +++++++++++++++++ + drivers/misc/surface_aggregator/controller.h | 283 ++ + drivers/misc/surface_aggregator/core.c | 773 +++++ + drivers/misc/surface_aggregator/ssh_msgb.h | 196 ++ + .../surface_aggregator/ssh_packet_layer.c | 2002 +++++++++++++ + .../surface_aggregator/ssh_packet_layer.h | 170 ++ + drivers/misc/surface_aggregator/ssh_parser.c | 224 ++ + drivers/misc/surface_aggregator/ssh_parser.h | 152 + + .../surface_aggregator/ssh_request_layer.c | 1247 ++++++++ + .../surface_aggregator/ssh_request_layer.h | 137 + + drivers/misc/surface_aggregator/trace.h | 621 ++++ drivers/tty/serdev/core.c | 111 +- - include/linux/mod_devicetable.h | 17 + - include/linux/surface_acpi_notify.h | 39 + - include/linux/surface_aggregator_module.h | 1744 ++++++++++++ - scripts/mod/devicetable-offsets.c | 7 + - scripts/mod/file2alias.c | 22 + - 48 files changed, 17768 insertions(+), 16 deletions(-) - create mode 100644 Documentation/driver-api/ssam/client-api.rst - create mode 100644 Documentation/driver-api/ssam/client.rst - create mode 100644 Documentation/driver-api/ssam/clients/dbgdev.rst - create mode 100644 Documentation/driver-api/ssam/clients/index.rst - create mode 100644 Documentation/driver-api/ssam/clients/san.rst - create mode 100644 Documentation/driver-api/ssam/index.rst - create mode 100644 Documentation/driver-api/ssam/internal-api.rst - create mode 100644 Documentation/driver-api/ssam/internal.rst - create mode 100644 Documentation/driver-api/ssam/overview.rst - create mode 100644 Documentation/driver-api/ssam/ssh.rst - 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_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 + include/linux/mod_devicetable.h | 18 + + include/linux/surface_acpi_notify.h | 37 + + include/linux/surface_aggregator/controller.h | 812 ++++++ + include/linux/surface_aggregator/device.h | 408 +++ + include/linux/surface_aggregator/serial_hub.h | 657 +++++ + scripts/mod/devicetable-offsets.c | 8 + + scripts/mod/file2alias.c | 23 + + 49 files changed, 17971 insertions(+), 16 deletions(-) + create mode 100644 Documentation/driver-api/surface_aggregator/client-api.rst + create mode 100644 Documentation/driver-api/surface_aggregator/client.rst + create mode 100644 Documentation/driver-api/surface_aggregator/clients/dbgdev.rst + create mode 100644 Documentation/driver-api/surface_aggregator/clients/index.rst + create mode 100644 Documentation/driver-api/surface_aggregator/clients/san.rst + create mode 100644 Documentation/driver-api/surface_aggregator/index.rst + create mode 100644 Documentation/driver-api/surface_aggregator/internal-api.rst + create mode 100644 Documentation/driver-api/surface_aggregator/internal.rst + create mode 100644 Documentation/driver-api/surface_aggregator/overview.rst + create mode 100644 Documentation/driver-api/surface_aggregator/ssh.rst + create mode 100644 drivers/misc/surface_aggregator/Kconfig + create mode 100644 drivers/misc/surface_aggregator/Makefile + create mode 100644 drivers/misc/surface_aggregator/bus.c + create mode 100644 drivers/misc/surface_aggregator/bus.h + create mode 100644 drivers/misc/surface_aggregator/clients/Kconfig + create mode 100644 drivers/misc/surface_aggregator/clients/Makefile + create mode 100644 drivers/misc/surface_aggregator/clients/surface_acpi_notify.c + create mode 100644 drivers/misc/surface_aggregator/clients/surface_aggregator_debugfs.c + create mode 100644 drivers/misc/surface_aggregator/clients/surface_aggregator_registry.c + create mode 100644 drivers/misc/surface_aggregator/clients/surface_battery.c + create mode 100644 drivers/misc/surface_aggregator/clients/surface_dtx.c + create mode 100644 drivers/misc/surface_aggregator/clients/surface_hid.c + create mode 100644 drivers/misc/surface_aggregator/clients/surface_hotplug.c + create mode 100644 drivers/misc/surface_aggregator/clients/surface_keyboard.c + create mode 100644 drivers/misc/surface_aggregator/clients/surface_perfmode.c + create mode 100644 drivers/misc/surface_aggregator/controller.c + create mode 100644 drivers/misc/surface_aggregator/controller.h + create mode 100644 drivers/misc/surface_aggregator/core.c + create mode 100644 drivers/misc/surface_aggregator/ssh_msgb.h + create mode 100644 drivers/misc/surface_aggregator/ssh_packet_layer.c + create mode 100644 drivers/misc/surface_aggregator/ssh_packet_layer.h + create mode 100644 drivers/misc/surface_aggregator/ssh_parser.c + create mode 100644 drivers/misc/surface_aggregator/ssh_parser.h + create mode 100644 drivers/misc/surface_aggregator/ssh_request_layer.c + create mode 100644 drivers/misc/surface_aggregator/ssh_request_layer.h + create mode 100644 drivers/misc/surface_aggregator/trace.h create mode 100644 include/linux/surface_acpi_notify.h - create mode 100644 include/linux/surface_aggregator_module.h + create mode 100644 include/linux/surface_aggregator/controller.h + create mode 100644 include/linux/surface_aggregator/device.h + create mode 100644 include/linux/surface_aggregator/serial_hub.h diff --git a/Documentation/driver-api/index.rst b/Documentation/driver-api/index.rst -index 38e638abe3eb..c6fcff170774 100644 +index 38e638abe3eb..130ee3353fc6 100644 --- a/Documentation/driver-api/index.rst +++ b/Documentation/driver-api/index.rst @@ -96,6 +96,7 @@ available subsections can be seen below. sgi-ioc4 sm501 smsc_ece1099 -+ ssam/index ++ surface_aggregator/index switchtec sync_file vfio-mediated-device -diff --git a/Documentation/driver-api/ssam/client-api.rst b/Documentation/driver-api/ssam/client-api.rst +diff --git a/Documentation/driver-api/surface_aggregator/client-api.rst b/Documentation/driver-api/surface_aggregator/client-api.rst new file mode 100644 -index 000000000000..3d5fc6a4dc95 +index 000000000000..b93608a1be38 --- /dev/null -+++ b/Documentation/driver-api/ssam/client-api.rst -@@ -0,0 +1,19 @@ ++++ b/Documentation/driver-api/surface_aggregator/client-api.rst +@@ -0,0 +1,38 @@ +.. SPDX-License-Identifier: GPL-2.0 + +=============================== +Client Driver API Documentation +=============================== + -+.. kernel-doc:: include/linux/surface_aggregator_module.h ++.. contents:: ++ :depth: 2 + -+.. kernel-doc:: drivers/misc/surface_sam/ssh_packet_layer.c ++ ++Serial Hub Communication ++======================== ++ ++.. kernel-doc:: include/linux/surface_aggregator/serial_hub.h ++ ++.. kernel-doc:: drivers/misc/surface_aggregator/ssh_packet_layer.c + :export: + -+.. kernel-doc:: drivers/misc/surface_sam/controller.c ++ ++Controller and Core Interface ++============================= ++ ++.. kernel-doc:: include/linux/surface_aggregator/controller.h ++ ++.. kernel-doc:: drivers/misc/surface_aggregator/controller.c + :export: + -+.. kernel-doc:: drivers/misc/surface_sam/core.c ++.. kernel-doc:: drivers/misc/surface_aggregator/core.c + :export: + -+.. kernel-doc:: drivers/misc/surface_sam/bus.c ++ ++Client Bus and Client Device API ++================================ ++ ++.. kernel-doc:: include/linux/surface_aggregator/device.h ++ ++.. kernel-doc:: drivers/misc/surface_aggregator/bus.c + :export: -diff --git a/Documentation/driver-api/ssam/client.rst b/Documentation/driver-api/ssam/client.rst +diff --git a/Documentation/driver-api/surface_aggregator/client.rst b/Documentation/driver-api/surface_aggregator/client.rst new file mode 100644 -index 000000000000..fa0aa032a948 +index 000000000000..c12343f599b2 --- /dev/null -+++ b/Documentation/driver-api/ssam/client.rst -@@ -0,0 +1,381 @@ ++++ b/Documentation/driver-api/surface_aggregator/client.rst +@@ -0,0 +1,394 @@ +.. SPDX-License-Identifier: GPL-2.0 + +.. |ssam_controller| replace:: :c:type:`struct ssam_controller ` @@ -263,6 +284,19 @@ index 000000000000..fa0aa032a948 +|SSAM_DEVICE| macro documentation for more details on how to define members +of the driver's match table. + ++The UID for SSAM client devices consists of a ``domain``, a ``category``, ++a ``target``, an ``instance``, and a ``function``. The ``domain`` is used ++differentiate between physical SAM devices ++(:c:type:`SSAM_DOMAIN_SERIALHUB `), i.e. devices that can ++be accessed via the Surface Serial Hub, and virtual ones ++(:c:type:`SSAM_DOMAIN_VIRTUAL `), such as client-device ++hubs, that have no real representation on the SAM EC and are solely used on ++the kernel/driver-side. For physical devices, ``category`` represents the ++target category, ``target`` the target ID, and ``instace`` the instance ID ++used to access the physical SAM device. In addition, ``function`` references ++a specific device functionality, but has no meaning to the SAM EC. The ++(default) name of a client device is generated based on its UID. ++ +A driver instance can be registered via |ssam_device_driver_register| and +unregistered via |ssam_device_driver_unregister|. For convenience, the +|module_ssam_device_driver| macro may be used to define module init- and @@ -517,33 +551,35 @@ index 000000000000..fa0aa032a948 +flags are therefore only used on the first registered notifier, however, one +should take care that notifiers for a specific event are always registered +with the same flag and it is considered a bug to do otherwise. -diff --git a/Documentation/driver-api/ssam/clients/dbgdev.rst b/Documentation/driver-api/ssam/clients/dbgdev.rst +diff --git a/Documentation/driver-api/surface_aggregator/clients/dbgdev.rst b/Documentation/driver-api/surface_aggregator/clients/dbgdev.rst new file mode 100644 -index 000000000000..d4d42634f516 +index 000000000000..e45d7e7fd13f --- /dev/null -+++ b/Documentation/driver-api/ssam/clients/dbgdev.rst -@@ -0,0 +1,128 @@ ++++ b/Documentation/driver-api/surface_aggregator/clients/dbgdev.rst +@@ -0,0 +1,130 @@ +.. SPDX-License-Identifier: GPL-2.0 + +.. |u8| replace:: :c:type:`u8 ` +.. |u16| replace:: :c:type:`u16 ` -+.. |ssam_dbgdev_request| replace:: :c:type:`struct ssam_dbgdev_request ` ++.. |ssam_dbg_request| replace:: :c:type:`struct ssam_dbg_request ` +.. |ssam_request_flags| replace:: :c:type:`enum ssam_request_flags ` + +======================================= +SSAM Debug Device and DebugFS Interface +======================================= + -+The ``surface_sam_debugfs`` module provides a DebugFS interface for the SSAM -+controller to allow for a (more or less) direct connection from userspace to -+the SAM EC. It is intended to be used for development and debugging, and -+therefore should not be used or relied upon in any other way. Note that this -+module is not loaded automatically, but instead must be loaded manually. ++The ``surface_aggregator_debugfs`` module provides a DebugFS interface for ++the SSAM controller to allow for a (more or less) direct connection from ++userspace to the SAM EC. It is intended to be used for development and ++debugging, and therefore should not be used or relied upon in any other way. ++Note that this module is not loaded automatically, but instead must be ++loaded manually. + -+The provided interface is accessible through the ``ssam/controller`` -+device-file in debugfs, so, if the conventional mount path is being used, -+``/sys/kernel/debug/ssam/controller``. All functionality of this interface -+is provided via IOCTLs. ++The provided interface is accessible through the ++``surface_aggregator/controller`` device-file in debugfs, so, if the ++conventional mount path is being used, ++``/sys/kernel/debug/surface_aggregator/controller``. All functionality of ++this interface is provided via IOCTLs. + + +Controller IOCTLs @@ -614,10 +650,10 @@ index 000000000000..d4d42634f516 +``REQUEST`` +----------- + -+Defined as ``_IOWR(0xA5, 1, struct ssam_dbgdev_request)``. ++Defined as ``_IOWR(0xA5, 1, struct ssam_dbg_request)``. + +Executes a synchronous SAM request. The request specification is passed in -+as argument of type |ssam_dbgdev_request|, which is then written to/modified ++as argument of type |ssam_dbg_request|, which is then written to/modified +by the IOCTL to return status and result of the request. + +Request payload data must be allocated separately and is passed in via the @@ -649,13 +685,13 @@ index 000000000000..d4d42634f516 + +A full definition of the argument struct is provided below: + -+.. kernel-doc:: drivers/misc/surface_sam/clients/surface_sam_debugfs.c -+ :functions: ssam_dbgdev_request -diff --git a/Documentation/driver-api/ssam/clients/index.rst b/Documentation/driver-api/ssam/clients/index.rst ++.. kernel-doc:: drivers/misc/surface_aggregator/clients/surface_aggregator_debugfs.c ++ :functions: ssam_dbg_request +diff --git a/Documentation/driver-api/surface_aggregator/clients/index.rst b/Documentation/driver-api/surface_aggregator/clients/index.rst new file mode 100644 index 000000000000..7cd91fc75e91 --- /dev/null -+++ b/Documentation/driver-api/ssam/clients/index.rst ++++ b/Documentation/driver-api/surface_aggregator/clients/index.rst @@ -0,0 +1,21 @@ +.. SPDX-License-Identifier: GPL-2.0 + @@ -678,11 +714,11 @@ index 000000000000..7cd91fc75e91 + ======= + + * :ref:`genindex` -diff --git a/Documentation/driver-api/ssam/clients/san.rst b/Documentation/driver-api/ssam/clients/san.rst +diff --git a/Documentation/driver-api/surface_aggregator/clients/san.rst b/Documentation/driver-api/surface_aggregator/clients/san.rst new file mode 100644 -index 000000000000..380b89e61955 +index 000000000000..f91c0a7ab884 --- /dev/null -+++ b/Documentation/driver-api/ssam/clients/san.rst ++++ b/Documentation/driver-api/surface_aggregator/clients/san.rst @@ -0,0 +1,44 @@ +.. SPDX-License-Identifier: GPL-2.0 + @@ -726,13 +762,13 @@ index 000000000000..380b89e61955 + +.. kernel-doc:: include/linux/surface_acpi_notify.h + -+.. kernel-doc:: drivers/misc/surface_sam/clients/surface_sam_san.c ++.. kernel-doc:: drivers/misc/surface_aggregator/clients/surface_acpi_notify.c + :export: -diff --git a/Documentation/driver-api/ssam/index.rst b/Documentation/driver-api/ssam/index.rst +diff --git a/Documentation/driver-api/surface_aggregator/index.rst b/Documentation/driver-api/surface_aggregator/index.rst new file mode 100644 index 000000000000..5eff57c1836d --- /dev/null -+++ b/Documentation/driver-api/ssam/index.rst ++++ b/Documentation/driver-api/surface_aggregator/index.rst @@ -0,0 +1,21 @@ +.. SPDX-License-Identifier: GPL-2.0 + @@ -755,12 +791,12 @@ index 000000000000..5eff57c1836d + ======= + + * :ref:`genindex` -diff --git a/Documentation/driver-api/ssam/internal-api.rst b/Documentation/driver-api/ssam/internal-api.rst +diff --git a/Documentation/driver-api/surface_aggregator/internal-api.rst b/Documentation/driver-api/surface_aggregator/internal-api.rst new file mode 100644 -index 000000000000..cd55b6b206e4 +index 000000000000..910fa9ec736c --- /dev/null -+++ b/Documentation/driver-api/ssam/internal-api.rst -@@ -0,0 +1,70 @@ ++++ b/Documentation/driver-api/surface_aggregator/internal-api.rst +@@ -0,0 +1,67 @@ +.. SPDX-License-Identifier: GPL-2.0 + +========================== @@ -774,68 +810,65 @@ index 000000000000..cd55b6b206e4 +Packet Transport Layer +====================== + -+.. kernel-doc:: drivers/misc/surface_sam/ssh_protocol.h ++.. kernel-doc:: drivers/misc/surface_aggregator/ssh_parser.h + :internal: + -+.. kernel-doc:: drivers/misc/surface_sam/ssh_parser.h ++.. kernel-doc:: drivers/misc/surface_aggregator/ssh_parser.c + :internal: + -+.. kernel-doc:: drivers/misc/surface_sam/ssh_parser.c ++.. kernel-doc:: drivers/misc/surface_aggregator/ssh_msgb.h + :internal: + -+.. kernel-doc:: drivers/misc/surface_sam/ssh_msgb.h ++.. kernel-doc:: drivers/misc/surface_aggregator/ssh_packet_layer.h + :internal: + -+.. kernel-doc:: drivers/misc/surface_sam/ssh_packet_layer.h -+ :internal: -+ -+.. kernel-doc:: drivers/misc/surface_sam/ssh_packet_layer.c ++.. kernel-doc:: drivers/misc/surface_aggregator/ssh_packet_layer.c + :internal: + + +Request Transport Layer +======================= + -+.. kernel-doc:: drivers/misc/surface_sam/ssh_request_layer.h ++.. kernel-doc:: drivers/misc/surface_aggregator/ssh_request_layer.h + :internal: + -+.. kernel-doc:: drivers/misc/surface_sam/ssh_request_layer.c ++.. kernel-doc:: drivers/misc/surface_aggregator/ssh_request_layer.c + :internal: + + +Controller +========== + -+.. kernel-doc:: drivers/misc/surface_sam/controller.h ++.. kernel-doc:: drivers/misc/surface_aggregator/controller.h + :internal: + -+.. kernel-doc:: drivers/misc/surface_sam/controller.c ++.. kernel-doc:: drivers/misc/surface_aggregator/controller.c + :internal: + + +Client Device Bus +================= + -+.. kernel-doc:: drivers/misc/surface_sam/bus.c ++.. kernel-doc:: drivers/misc/surface_aggregator/bus.c + :internal: + + +Core +==== + -+.. kernel-doc:: drivers/misc/surface_sam/core.c ++.. kernel-doc:: drivers/misc/surface_aggregator/core.c + :internal: + + +Trace Helpers +============= + -+.. kernel-doc:: drivers/misc/surface_sam/ssam_trace.h -diff --git a/Documentation/driver-api/ssam/internal.rst b/Documentation/driver-api/ssam/internal.rst ++.. kernel-doc:: drivers/misc/surface_aggregator/trace.h +diff --git a/Documentation/driver-api/surface_aggregator/internal.rst b/Documentation/driver-api/surface_aggregator/internal.rst new file mode 100644 index 000000000000..0504247d8786 --- /dev/null -+++ b/Documentation/driver-api/ssam/internal.rst ++++ b/Documentation/driver-api/surface_aggregator/internal.rst @@ -0,0 +1,50 @@ +.. SPDX-License-Identifier: GPL-2.0 + @@ -887,11 +920,11 @@ index 000000000000..0504247d8786 + +Refer to :doc:`client` for documentation regarding the client device/driver +API and interface options for other kernel drivers. -diff --git a/Documentation/driver-api/ssam/overview.rst b/Documentation/driver-api/ssam/overview.rst +diff --git a/Documentation/driver-api/surface_aggregator/overview.rst b/Documentation/driver-api/surface_aggregator/overview.rst new file mode 100644 index 000000000000..7b7a6d9e8e22 --- /dev/null -+++ b/Documentation/driver-api/ssam/overview.rst ++++ b/Documentation/driver-api/surface_aggregator/overview.rst @@ -0,0 +1,76 @@ +.. SPDX-License-Identifier: GPL-2.0 + @@ -969,11 +1002,11 @@ index 000000000000..7b7a6d9e8e22 +of multiple dedicated commands before they are sent by the EC. + +See :doc:`ssh` for a more technical protocol documentation. -diff --git a/Documentation/driver-api/ssam/ssh.rst b/Documentation/driver-api/ssam/ssh.rst +diff --git a/Documentation/driver-api/surface_aggregator/ssh.rst b/Documentation/driver-api/surface_aggregator/ssh.rst new file mode 100644 index 000000000000..35c903009cf3 --- /dev/null -+++ b/Documentation/driver-api/ssam/ssh.rst ++++ b/Documentation/driver-api/surface_aggregator/ssh.rst @@ -0,0 +1,343 @@ +.. SPDX-License-Identifier: GPL-2.0 + @@ -1364,106 +1397,131 @@ index d3d2dbfba680..0b7f617a6e9b 100644 buffer_desc = acpi_ut_create_buffer_object(buffer_length); if (!buffer_desc) { diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig -index c55b63750757..ff65acb97b18 100644 +index c55b63750757..b8626bef683c 100644 --- a/drivers/misc/Kconfig +++ b/drivers/misc/Kconfig @@ -481,4 +481,5 @@ source "drivers/misc/cxl/Kconfig" source "drivers/misc/ocxl/Kconfig" source "drivers/misc/cardreader/Kconfig" source "drivers/misc/habanalabs/Kconfig" -+source "drivers/misc/surface_sam/Kconfig" ++source "drivers/misc/surface_aggregator/Kconfig" endmenu diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile -index c1860d35dc7e..808666175859 100644 +index c1860d35dc7e..986da863df27 100644 --- a/drivers/misc/Makefile +++ b/drivers/misc/Makefile @@ -57,3 +57,4 @@ obj-y += cardreader/ obj-$(CONFIG_PVPANIC) += pvpanic.o obj-$(CONFIG_HABANA_AI) += habanalabs/ obj-$(CONFIG_XILINX_SDFEC) += xilinx_sdfec.o -+obj-$(CONFIG_SURFACE_SAM) += surface_sam/ -diff --git a/drivers/misc/surface_sam/Kconfig b/drivers/misc/surface_sam/Kconfig ++obj-$(CONFIG_SURFACE_AGGREGATOR) += surface_aggregator/ +diff --git a/drivers/misc/surface_aggregator/Kconfig b/drivers/misc/surface_aggregator/Kconfig new file mode 100644 -index 000000000000..ca560d91e229 +index 000000000000..e0a9bb37d178 --- /dev/null -+++ 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" -+ help -+ Drivers for the Surface/System Aggregator Module (SAM) of Microsoft -+ Surface devices. -+ -+ SAM is an embedded controller that provides access to various -+ functionalities on these devices, including battery status, keyboard -+ events (on the Laptops) and many more. -+ -+ Say M/Y here if you have a Microsoft Surface device with a SAM device -+ (i.e. 5th generation or later). -+ -+config SURFACE_SAM_SSH -+ tristate "Surface Serial Hub Driver" -+ depends on SURFACE_SAM ++++ b/drivers/misc/surface_aggregator/Kconfig +@@ -0,0 +1,65 @@ ++menuconfig SURFACE_AGGREGATOR ++ tristate "Microsoft Surface System Aggregator Module Subsystem and Drivers" + depends on SERIAL_DEV_BUS ++ depends on ACPI + select CRC_CCITT -+ default m + help -+ Surface Serial Hub driver for 5th generation (or later) Microsoft -+ Surface devices. ++ The Surface System Aggregator Module (Surface SAM or SSAM) is an ++ embedded controller (EC) found on 5th- and later-generation Microsoft ++ Surface devices (i.e. Surface Pro 5, Surface Book 2, Surface Laptop, ++ and newer, with exception of Surface Go series devices). + -+ This is the base driver for the embedded serial controller found on -+ 5th generation (and later) Microsoft Surface devices (e.g. Book 2, -+ Laptop, Laptop 2, Pro 2017, Pro 6, ...). This driver itself only -+ provides access to the embedded controller (SAM) and subsequent -+ drivers are required for the respective functionalities. ++ Depending on the device in question, this EC provides varying ++ functionality, including: ++ - EC access from ACPI via Surface ACPI Notify (5th- and 6th-generation) ++ - battery status information (all devices) ++ - thermal sensor access (all devices) ++ - performance mode / cooling mode control (all devices) ++ - clipboard detachment system control (Surface Book 2 and 3) ++ - HID / keyboard input (Surface Laptops, Surface Book 3) + -+ If you have a 5th generation (or later) Microsoft Surface device, say -+ Y or M here. ++ This option controls whether the Surface SAM subsystem core will be ++ built. This includes a driver for the Surface Serial Hub (SSH), which ++ is the device responsible for the communication with the EC, and a ++ basic kernel interface exposing the EC functionality to other client ++ drivers, i.e. allowing them to make requests to the EC and receive ++ events from it. Selecting this option alone will not provide any ++ client drivers and therefore no functionality beyond the in-kernel ++ interface. Said functionality is the repsonsibility of the respective ++ client drivers. + -+config SURFACE_SAM_SSH_ERROR_INJECTION -+ bool "Surface Serial Hub Error Injection Capabilities" -+ depends on SURFACE_SAM_SSH ++ Note: While 4th-generation Surface devices also make use of a SAM EC, ++ due to a difference in the communication interface of the controller, ++ only 5th and later generations are currently supported. Specifically, ++ devices using SAM-over-SSH are supported, whereas devices using ++ SAM-over-HID, which is used on the 4th generation, are currently not ++ supported. ++ ++config SURFACE_AGGREGATOR_BUS ++ bool "Surface System Aggregator Module Bus" ++ depends on SURFACE_AGGREGATOR ++ default y ++ help ++ Expands the Surface System Aggregator Module (SSAM) core driver by ++ providing a dedicated bus and client-device type. ++ ++ This bus and device type are intended to provide and simplify support ++ for non-platform and non-ACPI SSAM devices, i.e. SSAM devices that are ++ not auto-detectable via the conventional means (e.g. ACPI). ++ ++config SURFACE_AGGREGATOR_ERROR_INJECTION ++ bool "Surface System Aggregator Module Error Injection Capabilities" ++ depends on SURFACE_AGGREGATOR + depends on FUNCTION_ERROR_INJECTION + default n + help -+ Enable error injection capabilities for the Surface Serial Hub. -+ This is used to debug the driver, specifically the communication -+ interface. It is not required for normal use. ++ Provides error-injection capabilities for the Surface System ++ Aggregator Module subsystem and Surface Serial Hub driver. + -+ If you are not sure, say N here. ++ Specifically, exports error injection hooks to be used with the ++ kernel's function error injection capabilities to simulate underlying ++ transport and communication problems, such as invalid data sent to or ++ received from the EC, dropped data, and communication timeouts. ++ Intended for development and debugging. + -+source "drivers/misc/surface_sam/clients/Kconfig" -diff --git a/drivers/misc/surface_sam/Makefile b/drivers/misc/surface_sam/Makefile ++source "drivers/misc/surface_aggregator/clients/Kconfig" +diff --git a/drivers/misc/surface_aggregator/Makefile b/drivers/misc/surface_aggregator/Makefile new file mode 100644 -index 000000000000..0a07dd229787 +index 000000000000..acf42597e6bb --- /dev/null -+++ b/drivers/misc/surface_sam/Makefile -@@ -0,0 +1,14 @@ ++++ b/drivers/misc/surface_aggregator/Makefile +@@ -0,0 +1,17 @@ +# SPDX-License-Identifier: GPL-2.0-or-later + -+# For include/trace/define_trace.h to include ssam_trace.h ++# For include/trace/define_trace.h to include trace.h +CFLAGS_core.o = -I$(src) + -+obj-$(CONFIG_SURFACE_SAM_SSH) += surface_sam_ssh.o -+obj-$(CONFIG_SURFACE_SAM_SSH) += clients/ ++obj-$(CONFIG_SURFACE_AGGREGATOR) += surface_aggregator.o ++obj-$(CONFIG_SURFACE_AGGREGATOR) += 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 ++surface_aggregator-objs := core.o ++surface_aggregator-objs += ssh_parser.o ++surface_aggregator-objs += ssh_packet_layer.o ++surface_aggregator-objs += ssh_request_layer.o ++surface_aggregator-objs += controller.o ++ ++ifeq ($(CONFIG_SURFACE_AGGREGATOR_BUS),y) ++surface_aggregator-objs += bus.o ++endif +diff --git a/drivers/misc/surface_aggregator/bus.c b/drivers/misc/surface_aggregator/bus.c new file mode 100644 -index 000000000000..6089e7b74f46 +index 000000000000..5581dca2eee2 --- /dev/null -+++ b/drivers/misc/surface_sam/bus.c -@@ -0,0 +1,414 @@ ++++ b/drivers/misc/surface_aggregator/bus.c +@@ -0,0 +1,419 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#include -+#include ++#include ++ ++#include ++#include + +#include "bus.h" +#include "controller.h" @@ -1474,8 +1532,8 @@ index 000000000000..6089e7b74f46 +{ + 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.target, ++ return snprintf(buf, PAGE_SIZE - 1, "ssam:d%02Xc%02Xt%02Xi%02xf%02X\n", ++ sdev->uid.domain, sdev->uid.category, sdev->uid.target, + sdev->uid.instance, sdev->uid.function); +} +static DEVICE_ATTR_RO(modalias); @@ -1490,9 +1548,10 @@ index 000000000000..6089e7b74f46 +{ + 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.target, -+ sdev->uid.instance, sdev->uid.function); ++ return add_uevent_var(env, "MODALIAS=ssam:d%02Xc%02Xt%02Xi%02xf%02X", ++ sdev->uid.domain, sdev->uid.category, ++ sdev->uid.target, sdev->uid.instance, ++ sdev->uid.function); +} + +static void ssam_device_release(struct device *dev) @@ -1504,7 +1563,7 @@ index 000000000000..6089e7b74f46 +} + +const struct device_type ssam_device_type = { -+ .name = "ssam_client", ++ .name = "surface_aggregator_device", + .groups = ssam_device_groups, + .uevent = ssam_device_uevent, + .release = ssam_device_release, @@ -1541,9 +1600,9 @@ index 000000000000..6089e7b74f46 + sdev->ctrl = ssam_controller_get(ctrl); + sdev->uid = uid; + -+ dev_set_name(&sdev->dev, "%02x:%02x:%02x:%02x", -+ sdev->uid.category, sdev->uid.target, sdev->uid.instance, -+ sdev->uid.function); ++ dev_set_name(&sdev->dev, "%02x:%02x:%02x:%02x:%02x", ++ sdev->uid.domain, sdev->uid.category, sdev->uid.target, ++ sdev->uid.instance, sdev->uid.function); + + return sdev; +} @@ -1634,10 +1693,10 @@ index 000000000000..6089e7b74f46 + * Return: Returns %true iff the given UID is compatible to the match rule + * described by the given ID, %false otherwise. + */ -+static inline bool ssam_device_id_compatible(const struct ssam_device_id *id, -+ struct ssam_device_uid uid) ++static bool ssam_device_id_compatible(const struct ssam_device_id *id, ++ struct ssam_device_uid uid) +{ -+ if (id->category != uid.category) ++ if (id->domain != uid.domain || id->category != uid.category) + return false; + + if ((id->match_flags & SSAM_MATCH_TARGET) && id->target != uid.target) @@ -1662,9 +1721,10 @@ index 000000000000..6089e7b74f46 + * Return: Returns %true if the given ID represents a null ID, %false + * otherwise. + */ -+static inline bool ssam_device_id_is_null(const struct ssam_device_id *id) ++static bool ssam_device_id_is_null(const struct ssam_device_id *id) +{ + return id->match_flags == 0 ++ && id->domain == 0 + && id->category == 0 + && id->target == 0 + && id->instance == 0 @@ -1785,7 +1845,7 @@ index 000000000000..6089e7b74f46 +} + +struct bus_type ssam_bus_type = { -+ .name = "ssam", ++ .name = "surface_aggregator", + .match = ssam_bus_match, + .probe = ssam_bus_probe, + .remove = ssam_bus_remove, @@ -1874,194 +1934,1087 @@ index 000000000000..6089e7b74f46 +{ + return bus_unregister(&ssam_bus_type); +} -diff --git a/drivers/misc/surface_sam/bus.h b/drivers/misc/surface_sam/bus.h +diff --git a/drivers/misc/surface_aggregator/bus.h b/drivers/misc/surface_aggregator/bus.h new file mode 100644 -index 000000000000..8b3ddf210087 +index 000000000000..82c697b1e45f --- /dev/null -+++ b/drivers/misc/surface_sam/bus.h -@@ -0,0 +1,14 @@ ++++ b/drivers/misc/surface_aggregator/bus.h +@@ -0,0 +1,22 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + -+#ifndef _SSAM_BUS_H -+#define _SSAM_BUS_H ++#ifndef _SURFACE_AGGREGATOR_BUS_H ++#define _SURFACE_AGGREGATOR_BUS_H + -+#include ++#include + ++#ifdef CONFIG_SURFACE_AGGREGATOR_BUS + +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 ++#else /* CONFIG_SURFACE_AGGREGATOR_BUS */ ++ ++static inline void ssam_controller_remove_clients(struct ssam_controller *ctrl) {} ++static inline int ssam_bus_register(void) { return 0; } ++static inline void ssam_bus_unregister(void) {} ++ ++#endif /* CONFIG_SURFACE_AGGREGATOR_BUS */ ++#endif /* _SURFACE_AGGREGATOR_BUS_H */ +diff --git a/drivers/misc/surface_aggregator/clients/Kconfig b/drivers/misc/surface_aggregator/clients/Kconfig new file mode 100644 -index 000000000000..189bf02e280d +index 000000000000..1642a43a61a9 --- /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 ++++ b/drivers/misc/surface_aggregator/clients/Kconfig +@@ -0,0 +1,154 @@ ++config SURFACE_AGGREGATOR_DEBUGFS ++ tristate "Surface System Aggregator Module DebugFS interface" ++ depends on SURFACE_AGGREGATOR + depends on DEBUG_FS + default n + help -+ Debug device for direct communication with the embedded controller -+ found on 5th generation (and later) Microsoft Surface devices (e.g. -+ Book 2, Laptop, Laptop 2, Pro 2017, Pro 6, ...) via debugfs. ++ Provides a DebugFS interface to the Surface System Aggregator Module ++ (SSAM) controller. + -+ If you are not sure, say N here. ++ This option provides a module (called surface_aggregator_debugfs), ++ that, when loaded, will add a client device (and its respective ++ driver) to the SSAM controller. Said client device manages a DebugFS ++ interface (/sys/kernel/debug/surface_aggregator/controller), which can ++ be used by user-space tools to directly communicate with the SSAM EC ++ by sending requests and receiving the correspondign responses. + -+config SURFACE_SAM_DEVICE_HUB -+ tristate "Surface SAM Device Hub" -+ depends on SURFACE_SAM_SSH ++ The provided interface is intended for debugging and development only, ++ and should not be used otherwise. ++ ++config SURFACE_AGGREGATOR_REGISTRY ++ tristate "Surface System Aggregator Module Device Registry" ++ depends on SURFACE_AGGREGATOR_BUS + 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. ++ Device-registry and device-hubs for Surface System Aggregator Module ++ (SSAM) 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. ++ Provides a module and driver which act as device-registry for SSAM ++ client devices that cannot be detected automatically, e.g. via ACPI. ++ Such devices are instead provided via this registry and attached via ++ device hubs, also provided in this module. + -+ If you are not sure, say M here. ++ Devices provided via this registry are: ++ - performance / cooling mode device (all generations) ++ - battery/AC devices (7th generation) ++ - HID input devices (7th generation) + -+config SURFACE_SAM_SAN ++ Note that this module only provides the respective client devices. ++ Drivers for these devices still need to be selected via the other ++ options. ++ ++config SURFACE_ACPI_NOTIFY + tristate "Surface ACPI Notify Driver" -+ depends on SURFACE_SAM_SSH ++ depends on SURFACE_AGGREGATOR + default m + help -+ Surface ACPI Notify driver for 5th generation (or later) Microsoft -+ Surface devices. ++ Surface ACPI Notify (SAN) driver for Microsoft Surface devices. + -+ This driver enables basic ACPI events and requests, such as battery -+ status requests/events, thermal events, lid status, and possibly more, -+ which would otherwise not work on these devices. ++ This driver provides support for the ACPI interface (called SAN) of ++ the Surface System Aggregator Module (SSAM) EC. This interface is used ++ on 5th- and 6th-generation Microsoft Surface devices (including ++ Surface Pro 5 and 6, Surface Book 2, Surface Laptops 1 and 2, and in ++ reduced functionality on the Surface Laptop 3) to execute SSAM ++ requests directly from ACPI code, as well as receive SSAM events and ++ turn them into ACPI notifications. It essentially acts as a ++ translation layer between the SSAM controller and ACPI. + -+ If you are not sure, say M here. ++ Specifically, this driver may be needed for battery status reporting, ++ thermal sensor access, and real-time clock information, depending on ++ the Surface device in question. + -+config SURFACE_SAM_KBD -+ tristate "Surface Virtual HID Framework Driver" -+ depends on SURFACE_SAM_SSH -+ depends on HID -+ default m -+ help -+ Surface Virtual HID Framework driver for 5th generation (or later) -+ Microsoft Surface devices. -+ -+ This driver provides support for the Microsoft Virtual HID framework, -+ which is required for keyboard support on the Surface Laptop 1 and 2. -+ -+ If you are not sure, say M here. -+ -+config SURFACE_SAM_DTX -+ tristate "Surface Detachment System (DTX) Driver" -+ depends on SURFACE_SAM_SSH -+ depends on INPUT -+ default m -+ help -+ Surface Detachment System (DTX) driver for the Microsoft Surface Book -+ 2. This driver provides support for proper detachment handling in -+ user-space, status-events relating to the base and support for -+ the safe-guard keeping the base attached when the discrete GPU -+ contained in it is running via the special /dev/surface-dtx device. -+ -+ Also provides a standard input device to provide SW_TABLET_MODE events -+ upon device mode change. -+ -+ If you are not sure, say M here. -+ -+config SURFACE_SAM_HPS -+ tristate "Surface dGPU Hot-Plug System (dGPU-HPS) Driver" -+ depends on SURFACE_SAM_SSH -+ depends on SURFACE_SAM_SAN -+ depends on GPIO_SYSFS -+ default m -+ help -+ Driver to properly handle hot-plugging and explicit power-on/power-off -+ of the discrete GPU (dGPU) on the Surface Book 2 and 3. -+ -+ If you are not sure, say M here. -+ -+config SURFACE_SAM_PERFMODE -+ tristate "Surface Performance Mode Driver" -+ depends on SURFACE_SAM_SSH -+ depends on SYSFS -+ default m -+ help -+ This driver provides support for setting performance-modes on Surface -+ devices via the perf_mode sysfs attribute. Currently only supports the -+ Surface Book 2. Performance-modes directly influence the fan-profile -+ of the device, allowing to choose between higher performance or -+ quieter operation. -+ -+ If you are not sure, say M here. -+ -+config SURFACE_SAM_HID -+ tristate "Surface SAM HID Driver" -+ depends on SURFACE_SAM_SSH -+ depends on HID -+ default m -+ help -+ This driver provides support for HID devices connected via the Surface -+ SAM embedded controller. It provides support for keyboard and touchpad -+ on the Surface Laptop 3 models. -+ -+ If you are not sure, say M here. -+ -+config SURFACE_SAM_PSY -+ tristate "Surface SAM Battery/AC Driver" -+ depends on SURFACE_SAM_SSH ++config SURFACE_BATTERY ++ tristate "Surface Battery Driver" ++ depends on SURFACE_AGGREGATOR_BUS + select POWER_SUPPLY + default m + help -+ This driver provides support for the battery and AC on 7th generation ++ Driver for battery and AC-adapter devices connected/managed via the ++ Surface System Aggregator Module (SSAM) EC. ++ ++ This driver provides battery-/AC-information and -status support for ++ Surface devices where said data is not exposed via the standard ACPI ++ devices. On those models (7th-generation, i.e. Surface pro 7, Surface ++ Laptop 3, and Surface Book 3), battery-/AC-status and -information is ++ instead handled directly via SSAM client devices. ++ ++config SURFACE_DTX ++ tristate "Surface Detachment System Driver" ++ depends on SURFACE_AGGREGATOR ++ depends on INPUT ++ default m ++ help ++ Driver for the Surface Book clipboard detachment system (DTX). ++ ++ On the Surface Book series devices, the display part containing the ++ CPU (called the clipboard) can be detached from the base (containing a ++ battery, the keyboard, and, optionally, a discrete GPU) by (if ++ necessary) unlocking and opening the latch connecting both parts. ++ ++ This driver provides a user-space interface that can influence the ++ behavior of this process, which includes the option to abort it in ++ case the base is still in use or speed it up in case it is not. ++ ++config SURFACE_HID ++ tristate "Surface HID Transport Driver" ++ depends on SURFACE_AGGREGATOR_BUS ++ depends on HID ++ default m ++ help ++ Transport driver for HID devices connected via the Surface System ++ Aggregator Module (SSAM). ++ ++ This driver provides support for HID input devices (e.g. touchpad and ++ keyboard) connected via SSAM. It is required for keyboard input on the ++ Surface Laptop 1 and 2, as well as keyboard and touchpad input on the ++ Surface Laptop 3 and Surface Book 3. ++ ++config SURFACE_HOTPLUG ++ tristate "Surface Hot-Plug System Driver" ++ depends on SURFACE_AGGREGATOR ++ depends on SURFACE_ACPI_NOTIFY ++ depends on GPIO_SYSFS ++ default m ++ help ++ Driver for the Surface discrete GPU (dGPU) hot-plug system. ++ ++ This driver manages the dGPU power on the Surface Books, including ++ when hot-plugging it by detaching the clipboard (display part ++ containing the CPU) from the base (containing the keyboard and dGPU) ++ of the device when it is running. This driver also provides a ++ user-space interface via which the dGPU power-state (on/off) can be ++ set, allowing users to turn off the dGPU in order to reduce power ++ consumption. ++ ++config SURFACE_KEYBOARD ++ tristate "Surface Legacy HID Keyboard Transport Driver" ++ depends on SURFACE_AGGREGATOR ++ depends on HID ++ default m ++ help ++ Legacy HID keyboard transport driver for the Surface System Aggregator ++ Module (SSAM). ++ ++ This driver provides a HID transport driver for the legacy HID ++ keyboard found on the Surface Laptop 1 and 2. To be integrated with ++ the Surface HID driver. ++ ++config SURFACE_PERFMODE ++ tristate "Surface Performance-Mode Driver" ++ depends on SURFACE_AGGREGATOR_BUS ++ depends on SYSFS ++ default m ++ help ++ Driver for the performance-/cooling-mode interface of Microsoft + Surface devices. + -+ If you are not sure, say M here. -diff --git a/drivers/misc/surface_sam/clients/Makefile b/drivers/misc/surface_sam/clients/Makefile ++ Microsoft Surface devices using the Surface System Aggregator Module ++ (SSAM) can be switched between different performance modes. This, ++ depending on the device, can influence their cooling behavior and may ++ influence power limits, allowing users to choose between performance ++ and higher power-draw, or lower power-draw and more silent operation. ++ ++ This driver provides a user-space interface (via sysfs) for ++ controlling said mode via the corresponding client device. +diff --git a/drivers/misc/surface_aggregator/clients/Makefile b/drivers/misc/surface_aggregator/clients/Makefile new file mode 100644 -index 000000000000..1db9db2f8625 +index 000000000000..a859fecae8d5 --- /dev/null -+++ b/drivers/misc/surface_sam/clients/Makefile ++++ b/drivers/misc/surface_aggregator/clients/Makefile @@ -0,0 +1,11 @@ +# SPDX-License-Identifier: GPL-2.0-or-later + -+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 ++obj-$(CONFIG_SURFACE_AGGREGATOR_DEBUGFS) += surface_aggregator_debugfs.o ++obj-$(CONFIG_SURFACE_AGGREGATOR_REGISTRY) += surface_aggregator_registry.o ++obj-$(CONFIG_SURFACE_ACPI_NOTIFY) += surface_acpi_notify.o ++obj-$(CONFIG_SURFACE_BATTERY) += surface_battery.o ++obj-$(CONFIG_SURFACE_DTX) += surface_dtx.o ++obj-$(CONFIG_SURFACE_HID) += surface_hid.o ++obj-$(CONFIG_SURFACE_HOTPLUG) += surface_hotplug.o ++obj-$(CONFIG_SURFACE_KEYBOARD) += surface_keyboard.o ++obj-$(CONFIG_SURFACE_PERFMODE) += surface_perfmode.o +diff --git a/drivers/misc/surface_aggregator/clients/surface_acpi_notify.c b/drivers/misc/surface_aggregator/clients/surface_acpi_notify.c new file mode 100644 -index 000000000000..cd1fd24b0582 +index 000000000000..3bf715c32c24 --- /dev/null -+++ b/drivers/misc/surface_sam/clients/surface_sam_debugfs.c -@@ -0,0 +1,282 @@ ++++ b/drivers/misc/surface_aggregator/clients/surface_acpi_notify.c +@@ -0,0 +1,844 @@ +// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Driver for the Surface ACPI Notify (SAN) interface/shim. ++ * ++ * Translates communication from ACPI to Surface System Aggregator Module ++ * (SSAM/SAM) requests and back, specifically SAM-over-SSH. Translates SSAM ++ * events back to ACPI notifications. Allows handling of discrete GPU ++ * notifications sent from ACPI via the SAN interface by providing them to any ++ * registered external driver. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++#include ++ ++ ++struct san_data { ++ struct device *dev; ++ struct ssam_controller *ctrl; ++ ++ struct acpi_connection_info info; ++ ++ struct ssam_event_notifier nf_bat; ++ struct ssam_event_notifier nf_tmp; ++}; ++ ++#define to_san_data(ptr, member) \ ++ container_of(ptr, struct san_data, member) ++ ++ ++/* -- dGPU Notifier Interface. ---------------------------------------------- */ ++ ++struct san_rqsg_if { ++ struct rw_semaphore lock; ++ struct device *dev; ++ struct blocking_notifier_head nh; ++}; ++ ++static struct san_rqsg_if san_rqsg_if = { ++ .lock = __RWSEM_INITIALIZER(san_rqsg_if.lock), ++ .dev = NULL, ++ .nh = BLOCKING_NOTIFIER_INIT(san_rqsg_if.nh), ++}; ++ ++static int san_set_rqsg_interface_device(struct device *dev) ++{ ++ int status = 0; ++ ++ down_write(&san_rqsg_if.lock); ++ if (!san_rqsg_if.dev && dev) ++ san_rqsg_if.dev = dev; ++ else ++ status = -EBUSY; ++ up_write(&san_rqsg_if.lock); ++ ++ return status; ++} ++ ++/** ++ * san_client_link() - Link client as consumer to SAN device. ++ * @client: The client to link. ++ * ++ * Sets up a device link between the provided client device as consumer and ++ * the SAN device as provider. This function can be used to ensure that the ++ * SAN interface has been set up and will be set up for as long as the driver ++ * of the client device is bound. This guarantees that, during that time, all ++ * dGPU events will be received by any registered notifier. ++ * ++ * The link will be automatically removed once the client device's driver is ++ * unbound. ++ * ++ * Return: Returns zero on succes, %-ENXIO if the SAN interface has not been ++ * set up yet, and %-ENOMEM if device link creation failed. ++ */ ++int san_client_link(struct device *client) ++{ ++ const u32 flags = DL_FLAG_PM_RUNTIME | DL_FLAG_AUTOREMOVE_CONSUMER; ++ struct device_link *link; ++ ++ down_read(&san_rqsg_if.lock); ++ ++ if (!san_rqsg_if.dev) { ++ up_read(&san_rqsg_if.lock); ++ return -ENXIO; ++ } ++ ++ link = device_link_add(client, san_rqsg_if.dev, flags); ++ if (!link) { ++ up_read(&san_rqsg_if.lock); ++ return -ENOMEM; ++ } ++ ++ if (READ_ONCE(link->status) == DL_STATE_SUPPLIER_UNBIND) { ++ up_read(&san_rqsg_if.lock); ++ return -ENXIO; ++ } ++ ++ up_read(&san_rqsg_if.lock); ++ return 0; ++} ++EXPORT_SYMBOL_GPL(san_client_link); ++ ++/** ++ * san_dgpu_notifier_register() - Register a SAN dGPU notifier. ++ * @nb: The notifier-block to register. ++ * ++ * Registers a SAN dGPU notifier, receiving any new SAN dGPU events sent from ++ * ACPI. The registered notifier will be called with &struct san_dgpu_event ++ * as notifier data and the command ID of that event as notifier action. ++ */ ++int san_dgpu_notifier_register(struct notifier_block *nb) ++{ ++ return blocking_notifier_chain_register(&san_rqsg_if.nh, nb); ++} ++EXPORT_SYMBOL_GPL(san_dgpu_notifier_register); ++ ++/** ++ * san_dgpu_notifier_unregister() - Unregister a SAN dGPU notifier. ++ * @nb: The notifier-block to unregister. ++ */ ++int san_dgpu_notifier_unregister(struct notifier_block *nb) ++{ ++ return blocking_notifier_chain_unregister(&san_rqsg_if.nh, nb); ++} ++EXPORT_SYMBOL_GPL(san_dgpu_notifier_unregister); ++ ++static int san_dgpu_notifier_call(struct san_dgpu_event *evt) ++{ ++ int ret; ++ ++ ret = blocking_notifier_call_chain(&san_rqsg_if.nh, evt->command, evt); ++ return notifier_to_errno(ret); ++} ++ ++ ++/* -- ACPI _DSM event relay. ------------------------------------------------ */ ++ ++#define SAN_DSM_REVISION 0 ++ ++static const guid_t SAN_DSM_UUID = ++ GUID_INIT(0x93b666c5, 0x70c6, 0x469f, 0xa2, 0x15, 0x3d, ++ 0x48, 0x7c, 0x91, 0xab, 0x3c); ++ ++enum san_dsm_event_fn { ++ SAN_DSM_EVENT_FN_BAT1_STAT = 0x03, ++ SAN_DSM_EVENT_FN_BAT1_INFO = 0x04, ++ SAN_DSM_EVENT_FN_ADP1_STAT = 0x05, ++ SAN_DSM_EVENT_FN_ADP1_INFO = 0x06, ++ SAN_DSM_EVENT_FN_BAT2_STAT = 0x07, ++ SAN_DSM_EVENT_FN_BAT2_INFO = 0x08, ++ SAN_DSM_EVENT_FN_THERMAL = 0x09, ++ SAN_DSM_EVENT_FN_DPTF = 0x0a, ++}; ++ ++enum sam_event_cid_bat { ++ SAM_EVENT_CID_BAT_BIX = 0x15, ++ SAM_EVENT_CID_BAT_BST = 0x16, ++ SAM_EVENT_CID_BAT_ADP = 0x17, ++ SAM_EVENT_CID_BAT_PROT = 0x18, ++ SAM_EVENT_CID_BAT_DPTF = 0x4f, ++}; ++ ++enum sam_event_cid_tmp { ++ SAM_EVENT_CID_TMP_TRIP = 0x0b, ++}; ++ ++struct san_event_work { ++ struct delayed_work work; ++ struct device *dev; ++ struct ssam_event event; // must be last ++}; ++ ++static int san_acpi_notify_event(struct device *dev, u64 func, ++ union acpi_object *param) ++{ ++ acpi_handle san = ACPI_HANDLE(dev); ++ union acpi_object *obj; ++ int status = 0; ++ ++ if (!acpi_check_dsm(san, &SAN_DSM_UUID, SAN_DSM_REVISION, 1 << func)) ++ return 0; ++ ++ dev_dbg(dev, "notify event 0x%02llx\n", func); ++ ++ obj = acpi_evaluate_dsm_typed(san, &SAN_DSM_UUID, SAN_DSM_REVISION, ++ func, param, ACPI_TYPE_BUFFER); ++ if (!obj) ++ return -EFAULT; ++ ++ if (obj->buffer.length != 1 || obj->buffer.pointer[0] != 0) { ++ dev_err(dev, "got unexpected result from _DSM\n"); ++ status = -EPROTO; ++ } ++ ++ ACPI_FREE(obj); ++ return status; ++} ++ ++static int san_evt_bat_adp(struct device *dev, const struct ssam_event *event) ++{ ++ int status; ++ ++ status = san_acpi_notify_event(dev, SAN_DSM_EVENT_FN_ADP1_STAT, NULL); ++ if (status) ++ return status; ++ ++ /* ++ * Enusre that the battery states get updated correctly. ++ * When the battery is fully charged and an adapter is plugged in, it ++ * sometimes is not updated correctly, instead showing it as charging. ++ * Explicitly trigger battery updates to fix this. ++ */ ++ ++ status = san_acpi_notify_event(dev, SAN_DSM_EVENT_FN_BAT1_STAT, NULL); ++ if (status) ++ return status; ++ ++ return san_acpi_notify_event(dev, SAN_DSM_EVENT_FN_BAT2_STAT, NULL); ++} ++ ++static int san_evt_bat_bix(struct device *dev, const struct ssam_event *event) ++{ ++ enum san_dsm_event_fn fn; ++ ++ if (event->instance_id == 0x02) ++ fn = SAN_DSM_EVENT_FN_BAT2_INFO; ++ else ++ fn = SAN_DSM_EVENT_FN_BAT1_INFO; ++ ++ return san_acpi_notify_event(dev, fn, NULL); ++} ++ ++static int san_evt_bat_bst(struct device *dev, const struct ssam_event *event) ++{ ++ enum san_dsm_event_fn fn; ++ ++ if (event->instance_id == 0x02) ++ fn = SAN_DSM_EVENT_FN_BAT2_STAT; ++ else ++ fn = SAN_DSM_EVENT_FN_BAT1_STAT; ++ ++ return san_acpi_notify_event(dev, fn, NULL); ++} ++ ++static int san_evt_bat_dptf(struct device *dev, const struct ssam_event *event) ++{ ++ union acpi_object payload; ++ ++ /* ++ * The Surface ACPI expects a buffer and not a package. It specifically ++ * checks for ObjectType (Arg3) == 0x03. This will cause a warning in ++ * acpica/nsarguments.c, but that warning can be safely ignored. ++ */ ++ payload.type = ACPI_TYPE_BUFFER; ++ payload.buffer.length = event->length; ++ payload.buffer.pointer = (u8 *)&event->data[0]; ++ ++ return san_acpi_notify_event(dev, SAN_DSM_EVENT_FN_DPTF, &payload); ++} ++ ++static unsigned long san_evt_bat_delay(u8 cid) ++{ ++ switch (cid) { ++ case SAM_EVENT_CID_BAT_ADP: ++ /* ++ * Wait for battery state to update before signalling adapter ++ * change. ++ */ ++ return msecs_to_jiffies(5000); ++ ++ case SAM_EVENT_CID_BAT_BST: ++ /* Ensure we do not miss anything important due to caching. */ ++ return msecs_to_jiffies(2000); ++ ++ default: ++ return 0; ++ } ++} ++ ++static bool san_evt_bat(const struct ssam_event *event, struct device *dev) ++{ ++ int status; ++ ++ switch (event->command_id) { ++ case SAM_EVENT_CID_BAT_BIX: ++ status = san_evt_bat_bix(dev, event); ++ break; ++ ++ case SAM_EVENT_CID_BAT_BST: ++ status = san_evt_bat_bst(dev, event); ++ break; ++ ++ case SAM_EVENT_CID_BAT_ADP: ++ status = san_evt_bat_adp(dev, event); ++ break; ++ ++ case SAM_EVENT_CID_BAT_PROT: ++ /* ++ * TODO: Implement support for battery protection status change ++ * event. ++ */ ++ return true; ++ ++ case SAM_EVENT_CID_BAT_DPTF: ++ status = san_evt_bat_dptf(dev, event); ++ break; ++ ++ default: ++ return false; ++ } ++ ++ if (status) ++ dev_err(dev, "error handling power event (cid = %x)\n", ++ event->command_id); ++ ++ return true; ++} ++ ++static void san_evt_bat_workfn(struct work_struct *work) ++{ ++ struct san_event_work *ev; ++ ++ ev = container_of(work, struct san_event_work, work.work); ++ san_evt_bat(&ev->event, ev->dev); ++ kfree(ev); ++} ++ ++static u32 san_evt_bat_nf(struct ssam_event_notifier *nf, ++ const struct ssam_event *event) ++{ ++ struct san_data *d = to_san_data(nf, nf_bat); ++ struct san_event_work *work; ++ unsigned long delay = san_evt_bat_delay(event->command_id); ++ ++ if (delay == 0) ++ return san_evt_bat(event, d->dev) ? SSAM_NOTIF_HANDLED : 0; ++ ++ work = kzalloc(sizeof(*work) + event->length, GFP_KERNEL); ++ if (!work) ++ return ssam_notifier_from_errno(-ENOMEM); ++ ++ INIT_DELAYED_WORK(&work->work, san_evt_bat_workfn); ++ work->dev = d->dev; ++ ++ memcpy(&work->event, event, sizeof(struct ssam_event) + event->length); ++ ++ schedule_delayed_work(&work->work, delay); ++ return SSAM_NOTIF_HANDLED; ++} ++ ++static int san_evt_tmp_trip(struct device *dev, const struct ssam_event *event) ++{ ++ union acpi_object param; ++ ++ /* ++ * The Surface ACPI expects an integer and not a package. This will ++ * cause a warning in acpica/nsarguments.c, but that warning can be ++ * safely ignored. ++ */ ++ param.type = ACPI_TYPE_INTEGER; ++ param.integer.value = event->instance_id; ++ ++ return san_acpi_notify_event(dev, SAN_DSM_EVENT_FN_THERMAL, ¶m); ++} ++ ++static bool san_evt_tmp(const struct ssam_event *event, struct device *dev) ++{ ++ int status; ++ ++ switch (event->command_id) { ++ case SAM_EVENT_CID_TMP_TRIP: ++ status = san_evt_tmp_trip(dev, event); ++ break; ++ ++ default: ++ return false; ++ } ++ ++ if (status) { ++ dev_err(dev, "error handling thermal event (cid = %x)\n", ++ event->command_id); ++ } ++ ++ return true; ++} ++ ++static u32 san_evt_tmp_nf(struct ssam_event_notifier *nf, ++ const struct ssam_event *event) ++{ ++ struct san_data *d = to_san_data(nf, nf_bat); ++ ++ return san_evt_tmp(event, d->dev) ? SSAM_NOTIF_HANDLED : 0; ++} ++ ++ ++/* -- ACPI GSB OperationRegion Handler -------------------------------------- */ ++ ++struct gsb_data_in { ++ u8 cv; ++} __packed; ++ ++struct gsb_data_rqsx { ++ u8 cv; // command value (san_gsb_request_cv) ++ u8 tc; // target category ++ u8 tid; // target ID ++ u8 iid; // instance ID ++ u8 snc; // expect-response-flag? ++ u8 cid; // command ID ++ u16 cdl; // payload length ++ u8 pld[]; // payload ++} __packed; ++ ++struct gsb_data_etwl { ++ u8 cv; // command value (should be 0x02) ++ u8 etw3; // unknown ++ u8 etw4; // unknown ++ u8 msg[]; // error message (ASCIIZ) ++} __packed; ++ ++struct gsb_data_out { ++ u8 status; // _SSH communication status ++ u8 len; // _SSH payload length ++ u8 pld[]; // _SSH payload ++} __packed; ++ ++union gsb_buffer_data { ++ struct gsb_data_in in; // common input ++ struct gsb_data_rqsx rqsx; // RQSX input ++ struct gsb_data_etwl etwl; // ETWL input ++ struct gsb_data_out out; // output ++}; ++ ++struct gsb_buffer { ++ u8 status; // GSB AttribRawProcess status ++ u8 len; // GSB AttribRawProcess length ++ union gsb_buffer_data data; ++} __packed; ++ ++#define SAN_GSB_MAX_RQSX_PAYLOAD (U8_MAX - 2 - sizeof(struct gsb_data_rqsx)) ++#define SAN_GSB_MAX_RESPONSE (U8_MAX - 2 - sizeof(struct gsb_data_out)) ++ ++#define SAN_GSB_COMMAND 0 ++ ++enum san_gsb_request_cv { ++ SAN_GSB_REQUEST_CV_RQST = 0x01, ++ SAN_GSB_REQUEST_CV_ETWL = 0x02, ++ SAN_GSB_REQUEST_CV_RQSG = 0x03, ++}; ++ ++#define SAN_REQUEST_NUM_TRIES 5 ++ ++static acpi_status san_etwl(struct san_data *d, struct gsb_buffer *b) ++{ ++ struct gsb_data_etwl *etwl = &b->data.etwl; ++ ++ if (b->len < sizeof(struct gsb_data_etwl)) { ++ dev_err(d->dev, "invalid ETWL package (len = %d)\n", b->len); ++ return AE_OK; ++ } ++ ++ dev_err(d->dev, "ETWL(0x%02x, 0x%02x): %.*s\n", etwl->etw3, etwl->etw4, ++ (unsigned int)(b->len - sizeof(struct gsb_data_etwl)), ++ (char *)etwl->msg); ++ ++ // indicate success ++ b->status = 0x00; ++ b->len = 0x00; ++ ++ return AE_OK; ++} ++ ++static struct gsb_data_rqsx *san_validate_rqsx(struct device *dev, ++ const char *type, struct gsb_buffer *b) ++{ ++ struct gsb_data_rqsx *rqsx = &b->data.rqsx; ++ ++ if (b->len < sizeof(struct gsb_data_rqsx)) { ++ dev_err(dev, "invalid %s package (len = %d)\n", type, b->len); ++ return NULL; ++ } ++ ++ if (get_unaligned(&rqsx->cdl) != b->len - sizeof(struct gsb_data_rqsx)) { ++ dev_err(dev, "bogus %s package (len = %d, cdl = %d)\n", ++ type, b->len, get_unaligned(&rqsx->cdl)); ++ return NULL; ++ } ++ ++ if (get_unaligned(&rqsx->cdl) > SAN_GSB_MAX_RQSX_PAYLOAD) { ++ dev_err(dev, "payload for %s package too large (cdl = %d)\n", ++ type, get_unaligned(&rqsx->cdl)); ++ return NULL; ++ } ++ ++ return rqsx; ++} ++ ++static void gsb_rqsx_response_error(struct gsb_buffer *gsb, int status) ++{ ++ gsb->status = 0x00; ++ gsb->len = 0x02; ++ gsb->data.out.status = (u8)(-status); ++ gsb->data.out.len = 0x00; ++} ++ ++static void gsb_rqsx_response_success(struct gsb_buffer *gsb, u8 *ptr, size_t len) ++{ ++ gsb->status = 0x00; ++ gsb->len = len + 2; ++ gsb->data.out.status = 0x00; ++ gsb->data.out.len = len; ++ ++ if (len) ++ memcpy(&gsb->data.out.pld[0], ptr, len); ++} ++ ++static acpi_status san_rqst_fixup_suspended(struct ssam_request *rqst, ++ struct gsb_buffer *gsb) ++{ ++ if (rqst->target_category == SSAM_SSH_TC_BAS && rqst->command_id == 0x0D) { ++ u8 base_state = 1; ++ ++ /* Base state quirk: ++ * The base state may be queried from ACPI when the EC is still ++ * suspended. In this case it will return '-EPERM'. This query ++ * will only be triggered from the ACPI lid GPE interrupt, thus ++ * we are either in laptop or studio mode (base status 0x01 or ++ * 0x02). Furthermore, we will only get here if the device (and ++ * EC) have been suspended. ++ * ++ * We now assume that the device is in laptop mode (0x01). This ++ * has the drawback that it will wake the device when unfolding ++ * it in studio mode, but it also allows us to avoid actively ++ * waiting for the EC to wake up, which may incur a notable ++ * delay. ++ */ ++ ++ gsb_rqsx_response_success(gsb, &base_state, sizeof(base_state)); ++ return AE_OK; ++ } ++ ++ gsb_rqsx_response_error(gsb, -ENXIO); ++ return AE_OK; ++} ++ ++static acpi_status san_rqst(struct san_data *d, struct gsb_buffer *buffer) ++{ ++ u8 rspbuf[SAN_GSB_MAX_RESPONSE]; ++ struct gsb_data_rqsx *gsb_rqst; ++ struct ssam_request rqst; ++ struct ssam_response rsp; ++ int status = 0; ++ ++ gsb_rqst = san_validate_rqsx(d->dev, "RQST", buffer); ++ if (!gsb_rqst) ++ return AE_OK; ++ ++ rqst.target_category = gsb_rqst->tc; ++ rqst.target_id = gsb_rqst->tid; ++ rqst.command_id = gsb_rqst->cid; ++ rqst.instance_id = gsb_rqst->iid; ++ rqst.flags = gsb_rqst->snc ? SSAM_REQUEST_HAS_RESPONSE : 0; ++ rqst.length = get_unaligned(&gsb_rqst->cdl); ++ rqst.payload = &gsb_rqst->pld[0]; ++ ++ rsp.capacity = ARRAY_SIZE(rspbuf); ++ rsp.length = 0; ++ rsp.pointer = &rspbuf[0]; ++ ++ // handle suspended device ++ if (d->dev->power.is_suspended) { ++ dev_warn(d->dev, "rqst: device is suspended, not executing\n"); ++ return san_rqst_fixup_suspended(&rqst, buffer); ++ } ++ ++ status = ssam_retry(ssam_request_sync_onstack, SAN_REQUEST_NUM_TRIES, ++ d->ctrl, &rqst, &rsp, SAN_GSB_MAX_RQSX_PAYLOAD); ++ ++ if (!status) { ++ gsb_rqsx_response_success(buffer, rsp.pointer, rsp.length); ++ } else { ++ dev_err(d->dev, "rqst: failed with error %d\n", status); ++ gsb_rqsx_response_error(buffer, status); ++ } ++ ++ return AE_OK; ++} ++ ++static acpi_status san_rqsg(struct san_data *d, struct gsb_buffer *buffer) ++{ ++ struct gsb_data_rqsx *gsb_rqsg; ++ struct san_dgpu_event evt; ++ int status; ++ ++ gsb_rqsg = san_validate_rqsx(d->dev, "RQSG", buffer); ++ if (!gsb_rqsg) ++ return AE_OK; ++ ++ evt.category = gsb_rqsg->tc; ++ evt.target = gsb_rqsg->tid; ++ evt.command = gsb_rqsg->cid; ++ evt.instance = gsb_rqsg->iid; ++ evt.length = get_unaligned(&gsb_rqsg->cdl); ++ evt.payload = &gsb_rqsg->pld[0]; ++ ++ status = san_dgpu_notifier_call(&evt); ++ if (!status) { ++ gsb_rqsx_response_success(buffer, NULL, 0); ++ } else { ++ dev_err(d->dev, "rqsg: failed with error %d\n", status); ++ gsb_rqsx_response_error(buffer, status); ++ } ++ ++ return AE_OK; ++} ++ ++static acpi_status san_opreg_handler(u32 function, ++ acpi_physical_address command, u32 bits, u64 *value64, ++ void *opreg_context, void *region_context) ++{ ++ struct san_data *d = to_san_data(opreg_context, info); ++ struct gsb_buffer *buffer = (struct gsb_buffer *)value64; ++ int accessor_type = (function & 0xFFFF0000) >> 16; ++ ++ if (command != SAN_GSB_COMMAND) { ++ dev_warn(d->dev, "unsupported command: 0x%02llx\n", command); ++ return AE_OK; ++ } ++ ++ if (accessor_type != ACPI_GSB_ACCESS_ATTRIB_RAW_PROCESS) { ++ dev_err(d->dev, "invalid access type: 0x%02x\n", accessor_type); ++ return AE_OK; ++ } ++ ++ // buffer must have at least contain the command-value ++ if (buffer->len == 0) { ++ dev_err(d->dev, "request-package too small\n"); ++ return AE_OK; ++ } ++ ++ switch (buffer->data.in.cv) { ++ case SAN_GSB_REQUEST_CV_RQST: ++ return san_rqst(d, buffer); ++ ++ case SAN_GSB_REQUEST_CV_ETWL: ++ return san_etwl(d, buffer); ++ ++ case SAN_GSB_REQUEST_CV_RQSG: ++ return san_rqsg(d, buffer); ++ ++ default: ++ dev_warn(d->dev, "unsupported SAN0 request (cv: 0x%02x)\n", ++ buffer->data.in.cv); ++ return AE_OK; ++ } ++} ++ ++ ++/* -- Driver setup. --------------------------------------------------------- */ ++ ++struct san_acpi_consumer { ++ const char *path; ++}; ++ ++static int san_events_register(struct platform_device *pdev) ++{ ++ struct san_data *d = platform_get_drvdata(pdev); ++ int status; ++ ++ d->nf_bat.base.priority = 1; ++ d->nf_bat.base.fn = san_evt_bat_nf; ++ d->nf_bat.event.reg = SSAM_EVENT_REGISTRY_SAM; ++ d->nf_bat.event.id.target_category = SSAM_SSH_TC_BAT; ++ d->nf_bat.event.id.instance = 0; ++ d->nf_bat.event.mask = SSAM_EVENT_MASK_TARGET; ++ d->nf_bat.event.flags = SSAM_EVENT_SEQUENCED; ++ ++ d->nf_tmp.base.priority = 1; ++ d->nf_tmp.base.fn = san_evt_tmp_nf; ++ d->nf_tmp.event.reg = SSAM_EVENT_REGISTRY_SAM; ++ d->nf_tmp.event.id.target_category = SSAM_SSH_TC_TMP; ++ d->nf_tmp.event.id.instance = 0; ++ d->nf_tmp.event.mask = SSAM_EVENT_MASK_TARGET; ++ d->nf_tmp.event.flags = SSAM_EVENT_SEQUENCED; ++ ++ status = ssam_notifier_register(d->ctrl, &d->nf_bat); ++ if (status) ++ return status; ++ ++ status = ssam_notifier_register(d->ctrl, &d->nf_tmp); ++ if (status) ++ ssam_notifier_unregister(d->ctrl, &d->nf_bat); ++ ++ return status; ++} ++ ++static void san_events_unregister(struct platform_device *pdev) ++{ ++ struct san_data *d = platform_get_drvdata(pdev); ++ ++ ssam_notifier_unregister(d->ctrl, &d->nf_bat); ++ ssam_notifier_unregister(d->ctrl, &d->nf_tmp); ++} ++ ++static int san_consumers_link(struct platform_device *pdev, ++ const struct san_acpi_consumer *cons) ++{ ++ const u32 flags = DL_FLAG_PM_RUNTIME | DL_FLAG_AUTOREMOVE_SUPPLIER; ++ const struct san_acpi_consumer *c; ++ ++ for (c = cons; c && c->path; ++c) { ++ struct acpi_device *adev; ++ acpi_handle handle; ++ acpi_status status; ++ int ret; ++ ++ status = acpi_get_handle(NULL, (acpi_string)c->path, &handle); ++ if (status == AE_NOT_FOUND) ++ continue; ++ else if (ACPI_FAILURE(status)) ++ return -ENXIO; ++ ++ ret = acpi_bus_get_device(handle, &adev); ++ if (ret) ++ return ret; ++ ++ if (!device_link_add(&adev->dev, &pdev->dev, flags)) ++ return -EFAULT; ++ } ++ ++ return 0; ++} ++ ++static int san_probe(struct platform_device *pdev) ++{ ++ const struct san_acpi_consumer *cons; ++ acpi_handle san = ACPI_HANDLE(&pdev->dev); ++ struct ssam_controller *ctrl; ++ struct san_data *data; ++ acpi_status astatus; ++ int status; ++ ++ status = ssam_client_bind(&pdev->dev, &ctrl); ++ if (status) ++ return status == -ENXIO ? -EPROBE_DEFER : status; ++ ++ cons = acpi_device_get_match_data(&pdev->dev); ++ if (cons) { ++ status = san_consumers_link(pdev, cons); ++ if (status) ++ return status; ++ } ++ ++ data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); ++ if (!data) ++ return -ENOMEM; ++ ++ data->dev = &pdev->dev; ++ data->ctrl = ctrl; ++ ++ platform_set_drvdata(pdev, data); ++ ++ astatus = acpi_install_address_space_handler(san, ACPI_ADR_SPACE_GSBUS, ++ &san_opreg_handler, NULL, &data->info); ++ if (ACPI_FAILURE(astatus)) ++ return -ENXIO; ++ ++ status = san_events_register(pdev); ++ if (status) ++ goto err_enable_events; ++ ++ status = san_set_rqsg_interface_device(&pdev->dev); ++ if (status) ++ goto err_install_dev; ++ ++ acpi_walk_dep_device_list(san); ++ return 0; ++ ++err_install_dev: ++ san_events_unregister(pdev); ++err_enable_events: ++ acpi_remove_address_space_handler(san, ACPI_ADR_SPACE_GSBUS, ++ &san_opreg_handler); ++ return status; ++} ++ ++static int san_remove(struct platform_device *pdev) ++{ ++ acpi_handle san = ACPI_HANDLE(&pdev->dev); ++ ++ san_set_rqsg_interface_device(NULL); ++ acpi_remove_address_space_handler(san, ACPI_ADR_SPACE_GSBUS, ++ &san_opreg_handler); ++ san_events_unregister(pdev); ++ ++ /* ++ * We have unregistered our event sources. Now we need to ensure that ++ * all delayed works they may have spawned are run to completion. ++ */ ++ flush_scheduled_work(); ++ ++ return 0; ++} ++ ++/* ++ * ACPI devices that make use of the SAM EC via the SAN interface. Link them ++ * to the SAN device to try and enforce correct suspend/resume orderding. ++ */ ++static const struct san_acpi_consumer san_mshw0091_consumers[] = { ++ { "\\_SB.SRTC" }, ++ { "\\ADP1" }, ++ { "\\_SB.BAT1" }, ++ { "\\_SB.BAT2" }, ++ { }, ++}; ++ ++static const struct acpi_device_id san_match[] = { ++ { "MSHW0091", (unsigned long) san_mshw0091_consumers }, ++ { }, ++}; ++MODULE_DEVICE_TABLE(acpi, san_match); ++ ++static struct platform_driver surface_acpi_notify = { ++ .probe = san_probe, ++ .remove = san_remove, ++ .driver = { ++ .name = "surface_acpi_notify", ++ .acpi_match_table = san_match, ++ .probe_type = PROBE_PREFER_ASYNCHRONOUS, ++ }, ++}; ++module_platform_driver(surface_acpi_notify); ++ ++MODULE_AUTHOR("Maximilian Luz "); ++MODULE_DESCRIPTION("Surface ACPI Notify driver for Surface System Aggregator Module"); ++MODULE_LICENSE("GPL"); +diff --git a/drivers/misc/surface_aggregator/clients/surface_aggregator_debugfs.c b/drivers/misc/surface_aggregator/clients/surface_aggregator_debugfs.c +new file mode 100644 +index 000000000000..9bbfe724a653 +--- /dev/null ++++ b/drivers/misc/surface_aggregator/clients/surface_aggregator_debugfs.c +@@ -0,0 +1,280 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * DebugFS interface for Surface System Aggregator Module (SSAM) controller ++ * access from user-space. Intended for debugging and development. ++ */ + +#include -+#include +#include +#include +#include +#include +#include + -+#include -+ -+#define SSAM_DBGDEV_NAME "surface_sam_dbgdev" -+#define SSAM_DBGDEV_VERS 0x010000 ++#include + ++#define SSAM_DBG_DEVICE_NAME "surface_aggregator_dbg" ++#define SSAM_DBG_IF_VERSION 0x010000 + +/** -+ * struct ssam_dbgdev_request - Controller request IOCTL argument. ++ * struct ssam_debug_request - Controller request IOCTL argument. + * @target_category: Target category of the SAM request. + * @target_id: Target ID of the SAM request. + * @command_id: Command ID of the SAM request. @@ -2077,7 +3030,7 @@ index 000000000000..cd1fd24b0582 + * On output: Length of request response (number of bytes + * in the buffer that are actually used). + */ -+struct ssam_dbgdev_request { ++struct ssam_dbg_request { + __u8 target_category; + __u8 target_id; + __u8 command_id; @@ -2098,33 +3051,31 @@ index 000000000000..cd1fd24b0582 + } response; +}; + -+#define SSAM_DBGDEV_IOCTL_GETVERSION _IOR(0xA5, 0, __u32) -+#define SSAM_DBGDEV_IOCTL_REQUEST _IOWR(0xA5, 1, struct ssam_dbgdev_request) ++#define SSAM_DBG_IOCTL_GETVERSION _IOR(0xA5, 0, __u32) ++#define SSAM_DBG_IOCTL_REQUEST _IOWR(0xA5, 1, struct ssam_dbg_request) + -+ -+struct ssam_dbgdev { ++struct ssam_dbg_data { + struct ssam_controller *ctrl; + struct dentry *dentry_dir; + struct dentry *dentry_dev; +}; + -+ -+static int ssam_dbgdev_open(struct inode *inode, struct file *filp) ++static int ssam_dbg_device_open(struct inode *inode, struct file *filp) +{ + filp->private_data = inode->i_private; + return nonseekable_open(inode, filp); +} + -+static long ssam_dbgdev_request(struct file *file, unsigned long arg) ++static long ssam_dbg_if_request(struct file *file, unsigned long arg) +{ -+ struct ssam_dbgdev *ddev = file->private_data; -+ struct ssam_dbgdev_request __user *r; -+ struct ssam_dbgdev_request rqst; ++ struct ssam_dbg_data *data = file->private_data; ++ struct ssam_dbg_request __user *r; ++ struct ssam_dbg_request rqst; + struct ssam_request spec; + struct ssam_response rsp; + int status = 0, ret = 0, tmp; + -+ r = (struct ssam_dbgdev_request __user *)arg; ++ r = (struct ssam_dbg_request __user *)arg; + ret = copy_struct_from_user(&rqst, sizeof(rqst), r, sizeof(*r)); + if (ret) + goto out; @@ -2179,7 +3130,7 @@ index 000000000000..cd1fd24b0582 + } + + // perform request -+ status = ssam_request_sync(ddev->ctrl, &spec, &rsp); ++ status = ssam_request_sync(data->ctrl, &spec, &rsp); + if (status) + goto out; + @@ -2208,38 +3159,38 @@ index 000000000000..cd1fd24b0582 + return ret; +} + -+static long ssam_dbgdev_getversion(struct file *file, unsigned long arg) ++static long ssam_dbg_if_getversion(struct file *file, unsigned long arg) +{ -+ put_user(SSAM_DBGDEV_VERS, (u32 __user *)arg); ++ put_user(SSAM_DBG_IF_VERSION, (u32 __user *)arg); + return 0; +} + -+static long ssam_dbgdev_ioctl(struct file *file, unsigned int cmd, -+ unsigned long arg) ++static long ssam_dbg_device_ioctl(struct file *file, unsigned int cmd, ++ unsigned long arg) +{ + switch (cmd) { -+ case SSAM_DBGDEV_IOCTL_GETVERSION: -+ return ssam_dbgdev_getversion(file, arg); ++ case SSAM_DBG_IOCTL_GETVERSION: ++ return ssam_dbg_if_getversion(file, arg); + -+ case SSAM_DBGDEV_IOCTL_REQUEST: -+ return ssam_dbgdev_request(file, arg); ++ case SSAM_DBG_IOCTL_REQUEST: ++ return ssam_dbg_if_request(file, arg); + + default: + return -ENOIOCTLCMD; + } +} + -+const struct file_operations ssam_dbgdev_fops = { ++const struct file_operations ssam_dbg_device_fops = { + .owner = THIS_MODULE, -+ .open = ssam_dbgdev_open, -+ .unlocked_ioctl = ssam_dbgdev_ioctl, -+ .compat_ioctl = ssam_dbgdev_ioctl, ++ .open = ssam_dbg_device_open, ++ .unlocked_ioctl = ssam_dbg_device_ioctl, ++ .compat_ioctl = ssam_dbg_device_ioctl, + .llseek = noop_llseek, +}; + -+static int ssam_dbgdev_probe(struct platform_device *pdev) ++static int ssam_dbg_device_probe(struct platform_device *pdev) +{ -+ struct ssam_dbgdev *ddev; ++ struct ssam_dbg_data *data; + struct ssam_controller *ctrl; + int status; + @@ -2247,94 +3198,95 @@ index 000000000000..cd1fd24b0582 + if (status) + return status == -ENXIO ? -EPROBE_DEFER : status; + -+ ddev = devm_kzalloc(&pdev->dev, sizeof(struct ssam_dbgdev), GFP_KERNEL); -+ if (!ddev) ++ data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); ++ if (!data) + return -ENOMEM; + -+ ddev->ctrl = ctrl; ++ data->ctrl = ctrl; + -+ ddev->dentry_dir = debugfs_create_dir("ssam", NULL); -+ if (IS_ERR(ddev->dentry_dir)) -+ return PTR_ERR(ddev->dentry_dir); ++ data->dentry_dir = debugfs_create_dir("surface_aggregator", NULL); ++ if (IS_ERR(data->dentry_dir)) ++ return PTR_ERR(data->dentry_dir); + -+ ddev->dentry_dev = debugfs_create_file("controller", 0600, -+ ddev->dentry_dir, ddev, -+ &ssam_dbgdev_fops); -+ if (IS_ERR(ddev->dentry_dev)) { -+ debugfs_remove(ddev->dentry_dir); -+ return PTR_ERR(ddev->dentry_dev); ++ data->dentry_dev = debugfs_create_file("controller", 0600, ++ data->dentry_dir, data, ++ &ssam_dbg_device_fops); ++ if (IS_ERR(data->dentry_dev)) { ++ debugfs_remove(data->dentry_dir); ++ return PTR_ERR(data->dentry_dev); + } + -+ platform_set_drvdata(pdev, ddev); ++ platform_set_drvdata(pdev, data); + return 0; +} + -+static int ssam_dbgdev_remove(struct platform_device *pdev) ++static int ssam_dbg_device_remove(struct platform_device *pdev) +{ -+ struct ssam_dbgdev *ddev = platform_get_drvdata(pdev); ++ struct ssam_dbg_data *data = platform_get_drvdata(pdev); + -+ debugfs_remove(ddev->dentry_dev); -+ debugfs_remove(ddev->dentry_dir); ++ debugfs_remove(data->dentry_dev); ++ debugfs_remove(data->dentry_dir); + -+ platform_set_drvdata(pdev, NULL); + return 0; +} + -+static void ssam_dbgdev_release(struct device *dev) ++static void ssam_dbg_device_release(struct device *dev) +{ + // nothing to do +} + -+ -+static struct platform_device ssam_dbgdev_device = { -+ .name = SSAM_DBGDEV_NAME, ++static struct platform_device ssam_dbg_device = { ++ .name = SSAM_DBG_DEVICE_NAME, + .id = PLATFORM_DEVID_NONE, -+ .dev.release = ssam_dbgdev_release, ++ .dev.release = ssam_dbg_device_release, +}; + -+static struct platform_driver ssam_dbgdev_driver = { -+ .probe = ssam_dbgdev_probe, -+ .remove = ssam_dbgdev_remove, ++static struct platform_driver ssam_dbg_driver = { ++ .probe = ssam_dbg_device_probe, ++ .remove = ssam_dbg_device_remove, + .driver = { -+ .name = SSAM_DBGDEV_NAME, ++ .name = SSAM_DBG_DEVICE_NAME, + }, +}; + -+static int __init surface_sam_debugfs_init(void) ++static int __init ssam_debug_init(void) +{ + int status; + -+ status = platform_device_register(&ssam_dbgdev_device); ++ status = platform_device_register(&ssam_dbg_device); + if (status) + return status; + -+ status = platform_driver_register(&ssam_dbgdev_driver); ++ status = platform_driver_register(&ssam_dbg_driver); + if (status) -+ platform_device_unregister(&ssam_dbgdev_device); ++ platform_device_unregister(&ssam_dbg_device); + + return status; +} -+module_init(surface_sam_debugfs_init); ++module_init(ssam_debug_init); + -+static void __exit surface_sam_debugfs_exit(void) ++static void __exit ssam_debug_exit(void) +{ -+ platform_driver_unregister(&ssam_dbgdev_driver); -+ platform_device_unregister(&ssam_dbgdev_device); ++ platform_driver_unregister(&ssam_dbg_driver); ++ platform_device_unregister(&ssam_dbg_device); +} -+module_exit(surface_sam_debugfs_exit); ++module_exit(ssam_debug_exit); + +MODULE_AUTHOR("Maximilian Luz "); -+MODULE_DESCRIPTION("DebugFS entries for Surface Aggregator Module"); ++MODULE_DESCRIPTION("DebugFS interface for Surface System Aggregator Module"); +MODULE_LICENSE("GPL"); -diff --git a/drivers/misc/surface_sam/clients/surface_sam_device_hub.c b/drivers/misc/surface_sam/clients/surface_sam_device_hub.c +diff --git a/drivers/misc/surface_aggregator/clients/surface_aggregator_registry.c b/drivers/misc/surface_aggregator/clients/surface_aggregator_registry.c new file mode 100644 -index 000000000000..7b56d5e6ca1e +index 000000000000..18a81834ddf6 --- /dev/null -+++ b/drivers/misc/surface_sam/clients/surface_sam_device_hub.c -@@ -0,0 +1,583 @@ ++++ b/drivers/misc/surface_aggregator/clients/surface_aggregator_registry.c +@@ -0,0 +1,601 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* -+ * Surface Device Registry. ++ * Surface System Aggregator Module (SSAM) client device registry. ++ * ++ * Registry for non-platform/non-ACPI SSAM client devices, i.e. devices that ++ * cannot be auto-detected. Provides device-hubs for these devices. + */ + +#include @@ -2345,7 +3297,8 @@ index 000000000000..7b56d5e6ca1e +#include +#include + -+#include ++#include ++#include + + +/* -- Device registry structures. ------------------------------------------- */ @@ -2361,8 +3314,26 @@ index 000000000000..7b56d5e6ca1e +}; + + -+#define SSAM_DUID_HUB_MAIN SSAM_DUID(_HUB, 0x01, 0x00, 0x00) -+#define SSAM_DUID_HUB_BASE SSAM_DUID(_HUB, 0x02, 0x00, 0x00) ++#define SSAM_DUID(cat, tid, iid, fun) \ ++ ((struct ssam_device_uid) { \ ++ .domain = SSAM_DOMAIN_SERIALHUB, \ ++ .category = SSAM_SSH_TC_##cat, \ ++ .target = (tid), \ ++ .instance = (iid), \ ++ .function = (fun) \ ++ }) ++ ++#define SSAM_VDUID(cat, tid, iid, fun) \ ++ ((struct ssam_device_uid) { \ ++ .domain = SSAM_DOMAIN_VIRTUAL, \ ++ .category = SSAM_VIRTUAL_TC_##cat, \ ++ .target = (tid), \ ++ .instance = (iid), \ ++ .function = (fun) \ ++ }) ++ ++#define SSAM_DUID_HUB_MAIN SSAM_VDUID(HUB, 0x01, 0x00, 0x00) ++#define SSAM_DUID_HUB_BASE SSAM_VDUID(HUB, 0x02, 0x00, 0x00) + +#define SSAM_DEFINE_HUB_DESC(__name, __cells) \ + struct ssam_hub_desc __name = { \ @@ -2539,7 +3510,7 @@ index 000000000000..7b56d5e6ca1e +} + +static const struct ssam_device_id ssam_hub_match[] = { -+ { SSAM_DEVICE(_HUB, 0x01, 0x00, 0x00) }, ++ { SSAM_VDEV(HUB, 0x01, 0x00, 0x00) }, + { }, +}; + @@ -2548,7 +3519,7 @@ index 000000000000..7b56d5e6ca1e + .remove = ssam_hub_remove, + .match_table = ssam_hub_match, + .driver = { -+ .name = "surface_sam_hub", ++ .name = "surface_aggregator_device_hub", + .probe_type = PROBE_PREFER_ASYNCHRONOUS, + }, +}; @@ -2763,7 +3734,6 @@ index 000000000000..7b56d5e6ca1e + if (status) { + ssam_notifier_unregister(sdev->ctrl, &hub->notif); + ssam_hub_remove_devices(&sdev->dev); -+ return status; + } + + return status; @@ -2777,13 +3747,10 @@ index 000000000000..7b56d5e6ca1e + + 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) }, ++ { SSAM_VDEV(HUB, 0x02, 0x00, 0x00) }, + { }, +}; + @@ -2792,7 +3759,7 @@ index 000000000000..7b56d5e6ca1e + .remove = ssam_base_hub_remove, + .match_table = ssam_base_hub_match, + .driver = { -+ .name = "surface_sam_base_hub", ++ .name = "surface_aggregator_base_hub", + .probe_type = PROBE_PREFER_ASYNCHRONOUS, + .pm = &ssam_base_hub_pm_ops, + }, @@ -2867,7 +3834,7 @@ index 000000000000..7b56d5e6ca1e + .probe = ssam_platform_hub_probe, + .remove = ssam_platform_hub_remove, + .driver = { -+ .name = "surface_sam_platform_hub", ++ .name = "surface_aggregator_platform_hub", + .acpi_match_table = ssam_platform_hub_match, + .probe_type = PROBE_PREFER_ASYNCHRONOUS, + }, @@ -2913,17 +3880,1125 @@ index 000000000000..7b56d5e6ca1e +module_exit(ssam_device_hub_exit); + +MODULE_AUTHOR("Maximilian Luz "); -+MODULE_DESCRIPTION("Surface SAM Device Hub Driver for 5th Generation Surface Devices"); ++MODULE_DESCRIPTION("Device-registry for Surface System Aggregator Module"); +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 +diff --git a/drivers/misc/surface_aggregator/clients/surface_battery.c b/drivers/misc/surface_aggregator/clients/surface_battery.c new file mode 100644 -index 000000000000..ff73f5432806 +index 000000000000..b5cddd3cc35a --- /dev/null -+++ b/drivers/misc/surface_sam/clients/surface_sam_dtx.c -@@ -0,0 +1,583 @@ ++++ b/drivers/misc/surface_aggregator/clients/surface_battery.c +@@ -0,0 +1,1096 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* -+ * Detachment system (DTX) driver for Microsoft Surface Book 2. ++ * Surface battery and AC device driver. ++ * ++ * Provides support for battery and AC devices connected via the Surface ++ * System Aggregator Module. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++ ++ ++// TODO: check BIX/BST for unknown/unsupported 0xffffffff entries ++// TODO: DPTF (/SAN notifications)? ++// TODO: other properties? ++ ++ ++static unsigned int cache_time = 1000; ++module_param(cache_time, uint, 0644); ++MODULE_PARM_DESC(cache_time, "battery state chaching time in milliseconds [default: 1000]"); ++ ++#define SPWR_AC_BAT_UPDATE_DELAY msecs_to_jiffies(5000) ++ ++ ++/* ++ * SAM Interface. ++ */ ++ ++#define SAM_EVENT_PWR_CID_BIX 0x15 ++#define SAM_EVENT_PWR_CID_BST 0x16 ++#define SAM_EVENT_PWR_CID_ADAPTER 0x17 ++ ++#define SAM_BATTERY_STA_OK 0x0f ++#define SAM_BATTERY_STA_PRESENT 0x10 ++ ++#define SAM_BATTERY_STATE_DISCHARGING 0x01 ++#define SAM_BATTERY_STATE_CHARGING 0x02 ++#define SAM_BATTERY_STATE_CRITICAL 0x04 ++ ++#define SAM_BATTERY_POWER_UNIT_MA 1 ++ ++ ++/* Equivalent to data returned in ACPI _BIX method */ ++struct spwr_bix { ++ u8 revision; ++ __le32 power_unit; ++ __le32 design_cap; ++ __le32 last_full_charge_cap; ++ __le32 technology; ++ __le32 design_voltage; ++ __le32 design_cap_warn; ++ __le32 design_cap_low; ++ __le32 cycle_count; ++ __le32 measurement_accuracy; ++ __le32 max_sampling_time; ++ __le32 min_sampling_time; ++ __le32 max_avg_interval; ++ __le32 min_avg_interval; ++ __le32 bat_cap_granularity_1; ++ __le32 bat_cap_granularity_2; ++ u8 model[21]; ++ u8 serial[11]; ++ u8 type[5]; ++ u8 oem_info[21]; ++} __packed; ++ ++/* Equivalent to data returned in ACPI _BST method */ ++struct spwr_bst { ++ __le32 state; ++ __le32 present_rate; ++ __le32 remaining_cap; ++ __le32 present_voltage; ++} __packed; ++ ++/* DPTF event payload */ ++struct spwr_event_dptf { ++ __le32 pmax; ++ __le32 _1; /* currently unknown */ ++ __le32 _2; /* currently unknown */ ++} __packed; ++ ++ ++/* Get battery status (_STA) */ ++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_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_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_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_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_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_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_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_CL_W(ssam_bat_set_chgi, __le32, { ++ .target_category = SSAM_SSH_TC_BAT, ++ .command_id = 0x0e, ++}); ++ ++ ++/* ++ * Common Power-Subsystem Interface. ++ */ ++ ++struct spwr_psy_properties { ++ const char *name; ++ struct ssam_event_registry registry; ++}; ++ ++struct spwr_battery_device { ++ struct ssam_device *sdev; ++ ++ char name[32]; ++ struct power_supply *psy; ++ struct power_supply_desc psy_desc; ++ ++ struct delayed_work update_work; ++ ++ struct ssam_event_notifier notif; ++ ++ struct mutex lock; ++ unsigned long timestamp; ++ ++ __le32 sta; ++ struct spwr_bix bix; ++ struct spwr_bst bst; ++ u32 alarm; ++}; ++ ++struct spwr_ac_device { ++ struct ssam_device *sdev; ++ ++ char name[32]; ++ struct power_supply *psy; ++ struct power_supply_desc psy_desc; ++ ++ struct ssam_event_notifier notif; ++ ++ struct mutex lock; ++ ++ __le32 state; ++}; ++ ++static enum power_supply_property spwr_ac_props[] = { ++ POWER_SUPPLY_PROP_ONLINE, ++}; ++ ++static enum power_supply_property spwr_battery_props_chg[] = { ++ POWER_SUPPLY_PROP_STATUS, ++ POWER_SUPPLY_PROP_PRESENT, ++ POWER_SUPPLY_PROP_TECHNOLOGY, ++ POWER_SUPPLY_PROP_CYCLE_COUNT, ++ POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN, ++ POWER_SUPPLY_PROP_VOLTAGE_NOW, ++ POWER_SUPPLY_PROP_CURRENT_NOW, ++ POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, ++ POWER_SUPPLY_PROP_CHARGE_FULL, ++ POWER_SUPPLY_PROP_CHARGE_NOW, ++ POWER_SUPPLY_PROP_CAPACITY, ++ POWER_SUPPLY_PROP_CAPACITY_LEVEL, ++ POWER_SUPPLY_PROP_MODEL_NAME, ++ POWER_SUPPLY_PROP_MANUFACTURER, ++ POWER_SUPPLY_PROP_SERIAL_NUMBER, ++}; ++ ++static enum power_supply_property spwr_battery_props_eng[] = { ++ POWER_SUPPLY_PROP_STATUS, ++ POWER_SUPPLY_PROP_PRESENT, ++ POWER_SUPPLY_PROP_TECHNOLOGY, ++ POWER_SUPPLY_PROP_CYCLE_COUNT, ++ POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN, ++ POWER_SUPPLY_PROP_VOLTAGE_NOW, ++ POWER_SUPPLY_PROP_POWER_NOW, ++ POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN, ++ POWER_SUPPLY_PROP_ENERGY_FULL, ++ POWER_SUPPLY_PROP_ENERGY_NOW, ++ POWER_SUPPLY_PROP_CAPACITY, ++ POWER_SUPPLY_PROP_CAPACITY_LEVEL, ++ POWER_SUPPLY_PROP_MODEL_NAME, ++ POWER_SUPPLY_PROP_MANUFACTURER, ++ POWER_SUPPLY_PROP_SERIAL_NUMBER, ++}; ++ ++ ++static int spwr_battery_register(struct spwr_battery_device *bat, ++ struct ssam_device *sdev, ++ struct ssam_event_registry registry); ++ ++static void spwr_battery_unregister(struct spwr_battery_device *bat); ++ ++ ++static bool spwr_battery_present(struct spwr_battery_device *bat) ++{ ++ return le32_to_cpu(bat->sta) & SAM_BATTERY_STA_PRESENT; ++} ++ ++ ++static int spwr_battery_load_sta(struct spwr_battery_device *bat) ++{ ++ return ssam_bat_get_sta(bat->sdev, &bat->sta); ++} ++ ++static int spwr_battery_load_bix(struct spwr_battery_device *bat) ++{ ++ if (!spwr_battery_present(bat)) ++ return 0; ++ ++ return ssam_bat_get_bix(bat->sdev, &bat->bix); ++} ++ ++static int spwr_battery_load_bst(struct spwr_battery_device *bat) ++{ ++ if (!spwr_battery_present(bat)) ++ return 0; ++ ++ return ssam_bat_get_bst(bat->sdev, &bat->bst); ++} ++ ++ ++static 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->sdev, &alarm); ++} ++ ++static int spwr_battery_set_alarm(struct spwr_battery_device *bat, u32 value) ++{ ++ int status; ++ ++ mutex_lock(&bat->lock); ++ status = spwr_battery_set_alarm_unlocked(bat, value); ++ mutex_unlock(&bat->lock); ++ ++ return status; ++} ++ ++static int spwr_battery_update_bst_unlocked(struct spwr_battery_device *bat, ++ bool cached) ++{ ++ 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; ++ ++ status = spwr_battery_load_sta(bat); ++ if (status) ++ return status; ++ ++ status = spwr_battery_load_bst(bat); ++ if (status) ++ return status; ++ ++ bat->timestamp = jiffies; ++ return 0; ++} ++ ++static int spwr_battery_update_bst(struct spwr_battery_device *bat, bool cached) ++{ ++ int status; ++ ++ mutex_lock(&bat->lock); ++ status = spwr_battery_update_bst_unlocked(bat, cached); ++ mutex_unlock(&bat->lock); ++ ++ return status; ++} ++ ++static int spwr_battery_update_bix_unlocked(struct spwr_battery_device *bat) ++{ ++ int status; ++ ++ status = spwr_battery_load_sta(bat); ++ if (status) ++ return status; ++ ++ status = spwr_battery_load_bix(bat); ++ if (status) ++ return status; ++ ++ status = spwr_battery_load_bst(bat); ++ if (status) ++ return status; ++ ++ bat->timestamp = jiffies; ++ return 0; ++} ++ ++static int spwr_battery_update_bix(struct spwr_battery_device *bat) ++{ ++ int status; ++ ++ mutex_lock(&bat->lock); ++ status = spwr_battery_update_bix_unlocked(bat); ++ mutex_unlock(&bat->lock); ++ ++ return status; ++} ++ ++static int spwr_ac_update_unlocked(struct spwr_ac_device *ac) ++{ ++ 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) ++{ ++ int status; ++ ++ mutex_lock(&ac->lock); ++ status = spwr_ac_update_unlocked(ac); ++ mutex_unlock(&ac->lock); ++ ++ return status; ++} ++ ++ ++static int spwr_battery_recheck(struct spwr_battery_device *bat) ++{ ++ bool present = spwr_battery_present(bat); ++ u32 unit = get_unaligned_le32(&bat->bix.power_unit); ++ int status; ++ ++ status = spwr_battery_update_bix(bat); ++ if (status) ++ return status; ++ ++ // if battery has been attached, (re-)initialize alarm ++ if (!present && spwr_battery_present(bat)) { ++ u32 cap_warn = get_unaligned_le32(&bat->bix.design_cap_warn); ++ status = spwr_battery_set_alarm(bat, cap_warn); ++ if (status) ++ return status; ++ } ++ ++ // 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->sdev, ++ bat->notif.event.reg); ++ } ++ ++ return status; ++} ++ ++ ++static int spwr_notify_bix(struct spwr_battery_device *bat) ++{ ++ int status; ++ ++ status = spwr_battery_recheck(bat); ++ if (!status) ++ power_supply_changed(bat->psy); ++ ++ return status; ++} ++ ++static int spwr_notify_bst(struct spwr_battery_device *bat) ++{ ++ int status; ++ ++ status = spwr_battery_update_bst(bat, false); ++ if (!status) ++ power_supply_changed(bat->psy); ++ ++ return status; ++} ++ ++static int spwr_notify_adapter_bat(struct spwr_battery_device *bat) ++{ ++ u32 last_full_cap = get_unaligned_le32(&bat->bix.last_full_charge_cap); ++ u32 remaining_cap = get_unaligned_le32(&bat->bst.remaining_cap); ++ ++ /* ++ * Handle battery update quirk: ++ * When the battery is fully charged and the adapter is plugged in or ++ * removed, the EC does not send a separate event for the state ++ * (charging/discharging) change. Furthermore it may take some time until ++ * the state is updated on the battery. Schedule an update to solve this. ++ */ ++ ++ if (remaining_cap >= last_full_cap) ++ schedule_delayed_work(&bat->update_work, SPWR_AC_BAT_UPDATE_DELAY); ++ ++ return 0; ++} ++ ++static int spwr_notify_adapter_ac(struct spwr_ac_device *ac) ++{ ++ int status; ++ ++ status = spwr_ac_update(ac); ++ if (status > 0) ++ power_supply_changed(ac->psy); ++ ++ return status >= 0 ? 0 : status; ++} ++ ++static u32 spwr_notify_bat(struct ssam_event_notifier *nf, ++ const struct ssam_event *event) ++{ ++ struct spwr_battery_device *bat; ++ int status; ++ ++ bat = container_of(nf, struct spwr_battery_device, notif); ++ ++ dev_dbg(&bat->sdev->dev, "power event (cid = 0x%02x, iid = %d, tid = %d)\n", ++ event->command_id, event->instance_id, event->target_id); ++ ++ // handled here, needs to be handled for all targets/instances ++ if (event->command_id == SAM_EVENT_PWR_CID_ADAPTER) { ++ status = spwr_notify_adapter_bat(bat); ++ return ssam_notifier_from_errno(status) | SSAM_NOTIF_HANDLED; ++ } ++ ++ if (bat->sdev->uid.target != event->target_id) ++ return 0; ++ ++ if (bat->sdev->uid.instance != event->instance_id) ++ return 0; ++ ++ switch (event->command_id) { ++ case SAM_EVENT_PWR_CID_BIX: ++ status = spwr_notify_bix(bat); ++ break; ++ ++ case SAM_EVENT_PWR_CID_BST: ++ status = spwr_notify_bst(bat); ++ break; ++ ++ default: ++ return 0; ++ } ++ ++ return ssam_notifier_from_errno(status) | SSAM_NOTIF_HANDLED; ++} ++ ++static u32 spwr_notify_ac(struct ssam_event_notifier *nf, ++ const struct ssam_event *event) ++{ ++ struct spwr_ac_device *ac; ++ int status; ++ ++ ac = container_of(nf, struct spwr_ac_device, notif); ++ ++ dev_dbg(&ac->sdev->dev, "power event (cid = 0x%02x, iid = %d, tid = %d)\n", ++ event->command_id, event->instance_id, event->target_id); ++ ++ /* ++ * Allow events of all targets/instances here. Global adapter status ++ * seems to be handled via target=1 and instance=1, but events are ++ * reported on all targets/instances in use. ++ * ++ * While it should be enough to just listen on 1/1, listen everywhere to ++ * make sure we don't miss anything. ++ */ ++ ++ switch (event->command_id) { ++ case SAM_EVENT_PWR_CID_ADAPTER: ++ status = spwr_notify_adapter_ac(ac); ++ return ssam_notifier_from_errno(status) | SSAM_NOTIF_HANDLED; ++ ++ default: ++ return 0; ++ } ++} ++ ++static void spwr_battery_update_bst_workfn(struct work_struct *work) ++{ ++ struct delayed_work *dwork = to_delayed_work(work); ++ struct spwr_battery_device *bat; ++ int status; ++ ++ bat = container_of(dwork, struct spwr_battery_device, update_work); ++ ++ status = spwr_battery_update_bst(bat, false); ++ if (!status) ++ power_supply_changed(bat->psy); ++ ++ if (status) { ++ dev_err(&bat->sdev->dev, "failed to update battery state: %d\n", ++ status); ++ } ++} ++ ++ ++static int spwr_battery_prop_status(struct spwr_battery_device *bat) ++{ ++ u32 state = get_unaligned_le32(&bat->bst.state); ++ u32 last_full_cap = get_unaligned_le32(&bat->bix.last_full_charge_cap); ++ u32 remaining_cap = get_unaligned_le32(&bat->bst.remaining_cap); ++ u32 present_rate = get_unaligned_le32(&bat->bst.present_rate); ++ ++ if (state & SAM_BATTERY_STATE_DISCHARGING) ++ return POWER_SUPPLY_STATUS_DISCHARGING; ++ ++ if (state & SAM_BATTERY_STATE_CHARGING) ++ return POWER_SUPPLY_STATUS_CHARGING; ++ ++ if (last_full_cap == remaining_cap) ++ return POWER_SUPPLY_STATUS_FULL; ++ ++ if (present_rate == 0) ++ return POWER_SUPPLY_STATUS_NOT_CHARGING; ++ ++ return POWER_SUPPLY_STATUS_UNKNOWN; ++} ++ ++static int spwr_battery_prop_technology(struct spwr_battery_device *bat) ++{ ++ if (!strcasecmp("NiCd", bat->bix.type)) ++ return POWER_SUPPLY_TECHNOLOGY_NiCd; ++ ++ if (!strcasecmp("NiMH", bat->bix.type)) ++ return POWER_SUPPLY_TECHNOLOGY_NiMH; ++ ++ if (!strcasecmp("LION", bat->bix.type)) ++ return POWER_SUPPLY_TECHNOLOGY_LION; ++ ++ if (!strncasecmp("LI-ION", bat->bix.type, 6)) ++ return POWER_SUPPLY_TECHNOLOGY_LION; ++ ++ if (!strcasecmp("LiP", bat->bix.type)) ++ return POWER_SUPPLY_TECHNOLOGY_LIPO; ++ ++ return POWER_SUPPLY_TECHNOLOGY_UNKNOWN; ++} ++ ++static int spwr_battery_prop_capacity(struct spwr_battery_device *bat) ++{ ++ u32 last_full_cap = get_unaligned_le32(&bat->bix.last_full_charge_cap); ++ u32 remaining_cap = get_unaligned_le32(&bat->bst.remaining_cap); ++ ++ if (remaining_cap && last_full_cap) ++ return remaining_cap * 100 / last_full_cap; ++ else ++ return 0; ++} ++ ++static int spwr_battery_prop_capacity_level(struct spwr_battery_device *bat) ++{ ++ u32 state = get_unaligned_le32(&bat->bst.state); ++ u32 last_full_cap = get_unaligned_le32(&bat->bix.last_full_charge_cap); ++ u32 remaining_cap = get_unaligned_le32(&bat->bst.remaining_cap); ++ ++ if (state & SAM_BATTERY_STATE_CRITICAL) ++ return POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL; ++ ++ if (remaining_cap >= last_full_cap) ++ return POWER_SUPPLY_CAPACITY_LEVEL_FULL; ++ ++ if (remaining_cap <= bat->alarm) ++ return POWER_SUPPLY_CAPACITY_LEVEL_LOW; ++ ++ return POWER_SUPPLY_CAPACITY_LEVEL_NORMAL; ++} ++ ++static int spwr_ac_get_property(struct power_supply *psy, ++ enum power_supply_property psp, ++ union power_supply_propval *val) ++{ ++ struct spwr_ac_device *ac = power_supply_get_drvdata(psy); ++ int status; ++ ++ mutex_lock(&ac->lock); ++ ++ status = spwr_ac_update_unlocked(ac); ++ if (status) ++ goto out; ++ ++ switch (psp) { ++ case POWER_SUPPLY_PROP_ONLINE: ++ val->intval = le32_to_cpu(ac->state) == 1; ++ break; ++ ++ default: ++ status = -EINVAL; ++ goto out; ++ } ++ ++out: ++ mutex_unlock(&ac->lock); ++ return status; ++} ++ ++static int spwr_battery_get_property(struct power_supply *psy, ++ enum power_supply_property psp, ++ union power_supply_propval *val) ++{ ++ struct spwr_battery_device *bat = power_supply_get_drvdata(psy); ++ int status; ++ ++ mutex_lock(&bat->lock); ++ ++ status = spwr_battery_update_bst_unlocked(bat, true); ++ if (status) ++ goto out; ++ ++ // abort if battery is not present ++ if (!spwr_battery_present(bat) && psp != POWER_SUPPLY_PROP_PRESENT) { ++ status = -ENODEV; ++ goto out; ++ } ++ ++ switch (psp) { ++ case POWER_SUPPLY_PROP_STATUS: ++ val->intval = spwr_battery_prop_status(bat); ++ break; ++ ++ case POWER_SUPPLY_PROP_PRESENT: ++ val->intval = spwr_battery_present(bat); ++ break; ++ ++ case POWER_SUPPLY_PROP_TECHNOLOGY: ++ val->intval = spwr_battery_prop_technology(bat); ++ break; ++ ++ case POWER_SUPPLY_PROP_CYCLE_COUNT: ++ val->intval = get_unaligned_le32(&bat->bix.cycle_count); ++ break; ++ ++ case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN: ++ val->intval = get_unaligned_le32(&bat->bix.design_voltage) ++ * 1000; ++ break; ++ ++ case POWER_SUPPLY_PROP_VOLTAGE_NOW: ++ val->intval = get_unaligned_le32(&bat->bst.present_voltage) ++ * 1000; ++ break; ++ ++ case POWER_SUPPLY_PROP_CURRENT_NOW: ++ case POWER_SUPPLY_PROP_POWER_NOW: ++ val->intval = get_unaligned_le32(&bat->bst.present_rate) * 1000; ++ break; ++ ++ case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN: ++ case POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN: ++ val->intval = get_unaligned_le32(&bat->bix.design_cap) * 1000; ++ break; ++ ++ case POWER_SUPPLY_PROP_CHARGE_FULL: ++ case POWER_SUPPLY_PROP_ENERGY_FULL: ++ val->intval = get_unaligned_le32(&bat->bix.last_full_charge_cap) ++ * 1000; ++ break; ++ ++ case POWER_SUPPLY_PROP_CHARGE_NOW: ++ case POWER_SUPPLY_PROP_ENERGY_NOW: ++ val->intval = get_unaligned_le32(&bat->bst.remaining_cap) ++ * 1000; ++ break; ++ ++ case POWER_SUPPLY_PROP_CAPACITY: ++ val->intval = spwr_battery_prop_capacity(bat); ++ break; ++ ++ case POWER_SUPPLY_PROP_CAPACITY_LEVEL: ++ val->intval = spwr_battery_prop_capacity_level(bat); ++ break; ++ ++ case POWER_SUPPLY_PROP_MODEL_NAME: ++ val->strval = bat->bix.model; ++ break; ++ ++ case POWER_SUPPLY_PROP_MANUFACTURER: ++ val->strval = bat->bix.oem_info; ++ break; ++ ++ case POWER_SUPPLY_PROP_SERIAL_NUMBER: ++ val->strval = bat->bix.serial; ++ break; ++ ++ default: ++ status = -EINVAL; ++ goto out; ++ } ++ ++out: ++ mutex_unlock(&bat->lock); ++ return status; ++} ++ ++ ++static ssize_t spwr_battery_alarm_show(struct device *dev, ++ struct device_attribute *attr, ++ char *buf) ++{ ++ struct power_supply *psy = dev_get_drvdata(dev); ++ struct spwr_battery_device *bat = power_supply_get_drvdata(psy); ++ ++ return sprintf(buf, "%d\n", bat->alarm * 1000); ++} ++ ++static ssize_t spwr_battery_alarm_store(struct device *dev, ++ struct device_attribute *attr, ++ const char *buf, size_t count) ++{ ++ struct power_supply *psy = dev_get_drvdata(dev); ++ struct spwr_battery_device *bat = power_supply_get_drvdata(psy); ++ unsigned long value; ++ int status; ++ ++ status = kstrtoul(buf, 0, &value); ++ if (status) ++ return status; ++ ++ if (!spwr_battery_present(bat)) ++ return -ENODEV; ++ ++ status = spwr_battery_set_alarm(bat, value / 1000); ++ if (status) ++ return status; ++ ++ return count; ++} ++ ++static const struct device_attribute alarm_attr = { ++ .attr = {.name = "alarm", .mode = 0644}, ++ .show = spwr_battery_alarm_show, ++ .store = spwr_battery_alarm_store, ++}; ++ ++ ++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 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(sdev, &sta); ++ if (status) ++ return status; ++ ++ if ((le32_to_cpu(sta) & SAM_BATTERY_STA_OK) != SAM_BATTERY_STA_OK) ++ return -ENODEV; ++ ++ psy_cfg.drv_data = ac; ++ ++ ac->sdev = sdev; ++ mutex_init(&ac->lock); ++ ++ 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->sdev->dev, &ac->psy_desc, &psy_cfg); ++ if (IS_ERR(ac->psy)) { ++ status = PTR_ERR(ac->psy); ++ goto err_psy; ++ } ++ ++ ac->notif.base.priority = 1; ++ ac->notif.base.fn = spwr_notify_ac; ++ ac->notif.event.reg = registry; ++ ac->notif.event.id.target_category = sdev->uid.category; ++ ac->notif.event.id.instance = 0; ++ ac->notif.event.mask = SSAM_EVENT_MASK_NONE; ++ ac->notif.event.flags = SSAM_EVENT_SEQUENCED; ++ ++ status = ssam_notifier_register(sdev->ctrl, &ac->notif); ++ if (status) ++ goto err_notif; ++ ++ return 0; ++ ++err_notif: ++ power_supply_unregister(ac->psy); ++err_psy: ++ mutex_destroy(&ac->lock); ++ return status; ++} ++ ++static int spwr_ac_unregister(struct spwr_ac_device *ac) ++{ ++ 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 ssam_device *sdev, ++ struct ssam_event_registry registry) ++{ ++ struct power_supply_config psy_cfg = {}; ++ __le32 sta; ++ int status; ++ ++ bat->sdev = sdev; ++ ++ // make sure the device is there and functioning properly ++ status = ssam_bat_get_sta(sdev, &sta); ++ if (status) ++ return status; ++ ++ if ((le32_to_cpu(sta) & SAM_BATTERY_STA_OK) != SAM_BATTERY_STA_OK) ++ return -ENODEV; ++ ++ status = spwr_battery_update_bix_unlocked(bat); ++ if (status) ++ return status; ++ ++ if (spwr_battery_present(bat)) { ++ u32 cap_warn = get_unaligned_le32(&bat->bix.design_cap_warn); ++ status = spwr_battery_set_alarm_unlocked(bat, cap_warn); ++ if (status) ++ return status; ++ } ++ ++ bat->psy_desc.name = bat->name; ++ bat->psy_desc.type = POWER_SUPPLY_TYPE_BATTERY; ++ ++ if (get_unaligned_le32(&bat->bix.power_unit) == SAM_BATTERY_POWER_UNIT_MA) { ++ bat->psy_desc.properties = spwr_battery_props_chg; ++ bat->psy_desc.num_properties = ARRAY_SIZE(spwr_battery_props_chg); ++ } else { ++ bat->psy_desc.properties = spwr_battery_props_eng; ++ bat->psy_desc.num_properties = ARRAY_SIZE(spwr_battery_props_eng); ++ } ++ ++ bat->psy_desc.get_property = spwr_battery_get_property; ++ ++ mutex_init(&bat->lock); ++ psy_cfg.drv_data = bat; ++ ++ INIT_DELAYED_WORK(&bat->update_work, spwr_battery_update_bst_workfn); ++ ++ 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; ++ } ++ ++ bat->notif.base.priority = 1; ++ bat->notif.base.fn = spwr_notify_bat; ++ bat->notif.event.reg = registry; ++ bat->notif.event.id.target_category = sdev->uid.category; ++ bat->notif.event.id.instance = 0; ++ bat->notif.event.mask = SSAM_EVENT_MASK_NONE; ++ bat->notif.event.flags = SSAM_EVENT_SEQUENCED; ++ ++ status = ssam_notifier_register(sdev->ctrl, &bat->notif); ++ if (status) ++ goto err_notif; ++ ++ status = device_create_file(&bat->psy->dev, &alarm_attr); ++ if (status) ++ goto err_file; ++ ++ return 0; ++ ++err_file: ++ ssam_notifier_unregister(sdev->ctrl, &bat->notif); ++err_notif: ++ power_supply_unregister(bat->psy); ++err_psy: ++ mutex_destroy(&bat->lock); ++ return status; ++} ++ ++static void spwr_battery_unregister(struct spwr_battery_device *bat) ++{ ++ 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); ++ mutex_destroy(&bat->lock); ++} ++ ++ ++/* ++ * Battery Driver. ++ */ ++ ++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); ++} ++SIMPLE_DEV_PM_OPS(surface_sam_sid_battery_pm, ++ NULL, surface_sam_sid_battery_resume); ++ ++static int surface_sam_sid_battery_probe(struct ssam_device *sdev) ++{ ++ const struct spwr_psy_properties *p; ++ struct spwr_battery_device *bat; ++ ++ p = ssam_device_get_match_data(sdev); ++ if (!p) ++ return -ENODEV; ++ ++ bat = devm_kzalloc(&sdev->dev, sizeof(*bat), GFP_KERNEL); ++ if (!bat) ++ return -ENOMEM; ++ ++ spwr_battery_set_name(bat, p->name); ++ ssam_device_set_drvdata(sdev, bat); ++ ++ return spwr_battery_register(bat, sdev, p->registry); ++} ++ ++static void surface_sam_sid_battery_remove(struct ssam_device *sdev) ++{ ++ spwr_battery_unregister(ssam_device_get_drvdata(sdev)); ++} ++ ++static const struct spwr_psy_properties spwr_psy_props_bat1 = { ++ .name = "BAT1", ++ .registry = SSAM_EVENT_REGISTRY_SAM, ++}; ++ ++static const struct spwr_psy_properties spwr_psy_props_bat2_sb3 = { ++ .name = "BAT2", ++ .registry = SSAM_EVENT_REGISTRY_KIP, ++}; ++ ++static const struct ssam_device_id surface_sam_sid_battery_match[] = { ++ { SSAM_SDEV(BAT, 0x01, 0x01, 0x00), (unsigned long)&spwr_psy_props_bat1 }, ++ { SSAM_SDEV(BAT, 0x02, 0x01, 0x00), (unsigned long)&spwr_psy_props_bat2_sb3 }, ++ { }, ++}; ++MODULE_DEVICE_TABLE(ssam, surface_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_battery", ++ .pm = &surface_sam_sid_battery_pm, ++ .probe_type = PROBE_PREFER_ASYNCHRONOUS, ++ }, ++}; ++ ++ ++/* ++ * AC Driver. ++ */ ++ ++// 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; ++ ++ p = ssam_device_get_match_data(sdev); ++ if (!p) ++ return -ENODEV; ++ ++ ac = devm_kzalloc(&sdev->dev, sizeof(*ac), GFP_KERNEL); ++ if (!ac) ++ return -ENOMEM; ++ ++ spwr_ac_set_name(ac, p->name); ++ ssam_device_set_drvdata(sdev, ac); ++ ++ return spwr_ac_register(ac, sdev, p->registry); ++} ++ ++static void surface_sam_sid_ac_remove(struct ssam_device *sdev) ++{ ++ spwr_ac_unregister(ssam_device_get_drvdata(sdev)); ++} ++ ++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_SDEV(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_ac", ++ .probe_type = PROBE_PREFER_ASYNCHRONOUS, ++ }, ++}; ++ ++ ++static int __init surface_sam_sid_power_init(void) ++{ ++ int status; ++ ++ status = ssam_device_driver_register(&surface_sam_sid_battery); ++ if (status) ++ return status; ++ ++ status = ssam_device_driver_register(&surface_sam_sid_ac); ++ if (status) { ++ ssam_device_driver_unregister(&surface_sam_sid_battery); ++ return status; ++ } ++ ++ return 0; ++} ++module_init(surface_sam_sid_power_init); ++ ++static void __exit surface_sam_sid_power_exit(void) ++{ ++ ssam_device_driver_unregister(&surface_sam_sid_battery); ++ ssam_device_driver_unregister(&surface_sam_sid_ac); ++} ++module_exit(surface_sam_sid_power_exit); ++ ++MODULE_AUTHOR("Maximilian Luz "); ++MODULE_DESCRIPTION("Battery/AC driver for Surface System Aggregator Module"); ++MODULE_LICENSE("GPL"); +diff --git a/drivers/misc/surface_aggregator/clients/surface_dtx.c b/drivers/misc/surface_aggregator/clients/surface_dtx.c +new file mode 100644 +index 000000000000..51b21e194ee3 +--- /dev/null ++++ b/drivers/misc/surface_aggregator/clients/surface_dtx.c +@@ -0,0 +1,589 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Surface Book (gen. 2 and later) detachment system (DTX) driver. ++ * ++ * Provides a user-space interface to properly handle clipboard/tablet ++ * (containing screen and processor) detachment from the base of the device ++ * (containing the keyboard and optionally a discrete GPU). Allows to ++ * acknowledge (to speed things up), abort (e.g. in case the dGPU is stil in ++ * use), or request detachment via user-space. + */ + +#include @@ -2934,13 +5009,13 @@ index 000000000000..ff73f5432806 +#include +#include +#include ++#include +#include +#include +#include +#include -+#include + -+#include ++#include + + +#define USB_VENDOR_ID_MICROSOFT 0x045e @@ -2974,8 +5049,8 @@ index 000000000000..ff73f5432806 + +#define DTX_CONNECT_OPMODE_DELAY 1000 + -+#define DTX_ERR KERN_ERR "surface_sam_dtx: " -+#define DTX_WARN KERN_WARNING "surface_sam_dtx: " ++#define DTX_ERR KERN_ERR "surface_dtx: " ++#define DTX_WARN KERN_WARNING "surface_dtx: " + + +struct surface_dtx_event { @@ -3072,7 +5147,7 @@ index 000000000000..ff73f5432806 + struct surface_dtx_client *client; + + // initialize client -+ client = kzalloc(sizeof(struct surface_dtx_client), GFP_KERNEL); ++ client = kzalloc(sizeof(*client), GFP_KERNEL); + if (!client) + return -ENOMEM; + @@ -3494,7 +5569,7 @@ index 000000000000..ff73f5432806 + .probe = surface_sam_dtx_probe, + .remove = surface_sam_dtx_remove, + .driver = { -+ .name = "surface_sam_dtx", ++ .name = "surface_dtx", + .acpi_match_table = surface_sam_dtx_match, + .probe_type = PROBE_PREFER_ASYNCHRONOUS, + }, @@ -3502,19 +5577,520 @@ index 000000000000..ff73f5432806 +module_platform_driver(surface_sam_dtx); + +MODULE_AUTHOR("Maximilian Luz "); -+MODULE_DESCRIPTION("Surface Detachment System (DTX) Driver for 5th Generation Surface Devices"); ++MODULE_DESCRIPTION("Detachment-system driver for Surface System Aggregator Module"); +MODULE_LICENSE("GPL"); -diff --git a/drivers/misc/surface_sam/clients/surface_sam_hps.c b/drivers/misc/surface_sam/clients/surface_sam_hps.c +diff --git a/drivers/misc/surface_aggregator/clients/surface_hid.c b/drivers/misc/surface_aggregator/clients/surface_hid.c new file mode 100644 -index 000000000000..b2232043552a +index 000000000000..50528a7c3680 --- /dev/null -+++ b/drivers/misc/surface_sam/clients/surface_sam_hps.c -@@ -0,0 +1,1296 @@ ++++ b/drivers/misc/surface_aggregator/clients/surface_hid.c +@@ -0,0 +1,493 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* -+ * Surface dGPU hot-plug system driver. -+ * Supports explicit setting of the dGPU power-state on the Surface Book 2 and -+ * properly handles hot-plugging by detaching the base. ++ * Surface System Aggregator Module (SSAM) HID device driver. ++ * ++ * Provides support for HID input devices connected via the Surface System ++ * Aggregator Module. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++ ++ ++#define SID_VHF_INPUT_NAME "Microsoft Surface HID" ++ ++#define SAM_EVENT_SID_VHF_TC 0x15 ++ ++#define VHF_HID_STARTED 0 ++ ++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; ++ unsigned long state; ++}; ++ ++ ++static int sid_vhf_hid_start(struct hid_device *hid) ++{ ++ hid_dbg(hid, "%s\n", __func__); ++ return 0; ++} ++ ++static void sid_vhf_hid_stop(struct hid_device *hid) ++{ ++ hid_dbg(hid, "%s\n", __func__); ++} ++ ++static int sid_vhf_hid_open(struct hid_device *hid) ++{ ++ struct sid_vhf *vhf = dev_get_drvdata(hid->dev.parent); ++ ++ hid_dbg(hid, "%s\n", __func__); ++ ++ set_bit(VHF_HID_STARTED, &vhf->state); ++ return 0; ++} ++ ++static void sid_vhf_hid_close(struct hid_device *hid) ++{ ++ ++ struct sid_vhf *vhf = dev_get_drvdata(hid->dev.parent); ++ ++ hid_dbg(hid, "%s\n", __func__); ++ ++ clear_bit(VHF_HID_STARTED, &vhf->state); ++} ++ ++struct surface_sam_sid_vhf_meta_rqst { ++ u8 id; ++ u32 offset; ++ u32 length; // buffer limit on send, length of data received on receive ++ u8 end; // 0x01 if end was reached ++} __packed; ++ ++struct vhf_device_metadata_info { ++ u8 len; ++ u8 _2; ++ u8 _3; ++ u8 _4; ++ u8 _5; ++ u8 _6; ++ u8 _7; ++ u16 hid_len; // hid descriptor length ++} __packed; ++ ++struct vhf_device_metadata { ++ u32 len; ++ u16 vendor_id; ++ u16 product_id; ++ u8 _1[24]; ++} __packed; ++ ++union vhf_buffer_data { ++ struct vhf_device_metadata_info info; ++ u8 pld[0x76]; ++ struct vhf_device_metadata meta; ++}; ++ ++struct surface_sam_sid_vhf_meta_resp { ++ struct surface_sam_sid_vhf_meta_rqst rqst; ++ union vhf_buffer_data data; ++} __packed; ++ ++ ++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; ++ struct ssam_response rsp; ++ int status; ++ ++ data.rqst.id = 2; ++ data.rqst.offset = 0; ++ data.rqst.length = 0x76; ++ data.rqst.end = 0; ++ ++ rqst.target_category = sdev->uid.category; ++ rqst.target_id = sdev->uid.target; ++ rqst.command_id = 0x04; ++ rqst.instance_id = sdev->uid.instance; ++ rqst.flags = SSAM_REQUEST_HAS_RESPONSE; ++ rqst.length = sizeof(struct surface_sam_sid_vhf_meta_rqst); ++ rqst.payload = (u8 *)&data.rqst; ++ ++ rsp.capacity = sizeof(struct surface_sam_sid_vhf_meta_resp); ++ rsp.length = 0; ++ rsp.pointer = (u8 *)&data; ++ ++ status = ssam_request_sync(sdev->ctrl, &rqst, &rsp); ++ if (status) ++ return status; ++ ++ *meta = data.data.meta; ++ ++ return 0; ++} ++ ++static int vhf_get_hid_descriptor(struct ssam_device *sdev, u8 **desc, int *size) ++{ ++ struct surface_sam_sid_vhf_meta_resp data = {}; ++ struct ssam_request rqst; ++ struct ssam_response rsp; ++ int status, len; ++ u8 *buf; ++ ++ data.rqst.id = 0; ++ data.rqst.offset = 0; ++ data.rqst.length = 0x76; ++ data.rqst.end = 0; ++ ++ rqst.target_category = sdev->uid.category; ++ rqst.target_id = sdev->uid.target;; ++ rqst.command_id = 0x04; ++ rqst.instance_id = sdev->uid.instance; ++ rqst.flags = SSAM_REQUEST_HAS_RESPONSE; ++ rqst.length = sizeof(struct surface_sam_sid_vhf_meta_rqst); ++ rqst.payload = (u8 *)&data.rqst; ++ ++ rsp.capacity = sizeof(struct surface_sam_sid_vhf_meta_resp); ++ rsp.length = 0; ++ rsp.pointer = (u8 *)&data; ++ ++ // first fetch 00 to get the total length ++ status = ssam_request_sync(sdev->ctrl, &rqst, &rsp); ++ if (status) ++ return status; ++ ++ len = data.data.info.hid_len; ++ ++ // allocate a buffer for the descriptor ++ buf = kzalloc(len, GFP_KERNEL); ++ ++ // then, iterate and write into buffer, copying out bytes ++ data.rqst.id = 1; ++ data.rqst.offset = 0; ++ data.rqst.length = 0x76; ++ data.rqst.end = 0; ++ ++ while (!data.rqst.end && data.rqst.offset < len) { ++ status = ssam_request_sync(sdev->ctrl, &rqst, &rsp); ++ if (status) { ++ kfree(buf); ++ return status; ++ } ++ memcpy(buf + data.rqst.offset, data.data.pld, data.rqst.length); ++ ++ data.rqst.offset += data.rqst.length; ++ } ++ ++ *desc = buf; ++ *size = len; ++ ++ return 0; ++} ++ ++static int sid_vhf_hid_parse(struct hid_device *hid) ++{ ++ struct sid_vhf *vhf = dev_get_drvdata(hid->dev.parent); ++ int ret = 0, size; ++ u8 *buf; ++ ++ 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; ++ } ++ hid_dbg(hid, "HID descriptor of device:"); ++ print_hex_dump_debug("descriptor:", DUMP_PREFIX_OFFSET, 16, 1, buf, size, false); ++ ++ ret = hid_parse_report(hid, buf, size); ++ kfree(buf); ++ return ret; ++ ++} ++ ++static int sid_vhf_hid_raw_request(struct hid_device *hid, unsigned char ++ reportnum, u8 *buf, size_t len, unsigned char rtype, int ++ reqtype) ++{ ++ struct sid_vhf *vhf = dev_get_drvdata(hid->dev.parent); ++ struct ssam_request rqst; ++ struct ssam_response rsp; ++ int status; ++ u8 cid; ++ ++ hid_dbg(hid, "%s: reportnum=%#04x rtype=%i reqtype=%i\n", __func__, reportnum, rtype, reqtype); ++ print_hex_dump_debug("report:", DUMP_PREFIX_OFFSET, 16, 1, buf, len, false); ++ ++ // Byte 0 is the report number. Report data starts at byte 1. ++ buf[0] = reportnum; ++ ++ switch (rtype) { ++ case HID_OUTPUT_REPORT: ++ cid = 0x01; ++ break; ++ case HID_FEATURE_REPORT: ++ switch (reqtype) { ++ case HID_REQ_GET_REPORT: ++ // The EC doesn't respond to GET FEATURE for these touchpad reports ++ // we immediately discard to avoid waiting for a timeout. ++ if (reportnum == 6 || reportnum == 7 || reportnum == 8 || reportnum == 9 || reportnum == 0x0b) { ++ hid_dbg(hid, "%s: skipping get feature report for 0x%02x\n", __func__, reportnum); ++ return 0; ++ } ++ ++ cid = 0x02; ++ break; ++ case HID_REQ_SET_REPORT: ++ cid = 0x03; ++ break; ++ default: ++ hid_err(hid, "%s: unknown req type 0x%02x\n", __func__, rtype); ++ return -EIO; ++ } ++ break; ++ default: ++ hid_err(hid, "%s: unknown report type 0x%02x\n", __func__, reportnum); ++ return -EIO; ++ } ++ ++ rqst.target_category = vhf->sdev->uid.category; ++ rqst.target_id = vhf->sdev->uid.target; ++ 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; ++ rqst.payload = buf; ++ ++ rsp.capacity = len; ++ rsp.length = 0; ++ rsp.pointer = buf; ++ ++ hid_dbg(hid, "%s: sending to cid=%#04x snc=%#04x\n", __func__, cid, HID_REQ_GET_REPORT == reqtype); ++ ++ status = ssam_request_sync(vhf->sdev->ctrl, &rqst, &rsp); ++ hid_dbg(hid, "%s: status %i\n", __func__, status); ++ ++ if (status) ++ return status; ++ ++ if (rsp.length > 0) ++ print_hex_dump_debug("response:", DUMP_PREFIX_OFFSET, 16, 1, rsp.pointer, rsp.length, false); ++ ++ return rsp.length; ++} ++ ++static struct hid_ll_driver sid_vhf_hid_ll_driver = { ++ .start = sid_vhf_hid_start, ++ .stop = sid_vhf_hid_stop, ++ .open = sid_vhf_hid_open, ++ .close = sid_vhf_hid_close, ++ .parse = sid_vhf_hid_parse, ++ .raw_request = sid_vhf_hid_raw_request, ++}; ++ ++ ++static struct hid_device *sid_vhf_create_hid_device(struct ssam_device *sdev, struct vhf_device_metadata *meta) ++{ ++ struct hid_device *hid; ++ ++ hid = hid_allocate_device(); ++ if (IS_ERR(hid)) ++ return hid; ++ ++ hid->dev.parent = &sdev->dev; ++ ++ hid->bus = BUS_VIRTUAL; ++ hid->vendor = meta->vendor_id; ++ hid->product = meta->product_id; ++ ++ hid->ll_driver = &sid_vhf_hid_ll_driver; ++ ++ sprintf(hid->name, "%s", SID_VHF_INPUT_NAME); ++ ++ return hid; ++} ++ ++static u32 sid_vhf_event_handler(struct ssam_event_notifier *nf, const struct ssam_event *event) ++{ ++ struct sid_vhf *vhf = container_of(nf, struct sid_vhf, notif); ++ int status; ++ ++ if (event->command_id != 0x00 && event->command_id != 0x03 && event->command_id != 0x04) ++ return 0; ++ ++ // skip if HID hasn't started yet ++ if (!test_bit(VHF_HID_STARTED, &vhf->state)) ++ return SSAM_NOTIF_HANDLED; ++ ++ status = hid_input_report(vhf->hid, HID_INPUT_REPORT, (u8 *)&event->data[0], event->length, 0); ++ return ssam_notifier_from_errno(status) | SSAM_NOTIF_HANDLED; ++} ++ ++ ++#ifdef CONFIG_PM ++ ++static int surface_sam_sid_vhf_suspend(struct device *dev) ++{ ++ struct sid_vhf *vhf = dev_get_drvdata(dev); ++ ++ if (vhf->hid->driver && vhf->hid->driver->suspend) ++ return vhf->hid->driver->suspend(vhf->hid, PMSG_SUSPEND); ++ ++ return 0; ++} ++ ++static int surface_sam_sid_vhf_resume(struct device *dev) ++{ ++ struct sid_vhf *vhf = dev_get_drvdata(dev); ++ ++ if (vhf->hid->driver && vhf->hid->driver->resume) ++ return vhf->hid->driver->resume(vhf->hid); ++ ++ return 0; ++} ++ ++static int surface_sam_sid_vhf_freeze(struct device *dev) ++{ ++ struct sid_vhf *vhf = dev_get_drvdata(dev); ++ ++ if (vhf->hid->driver && vhf->hid->driver->suspend) ++ return vhf->hid->driver->suspend(vhf->hid, PMSG_FREEZE); ++ ++ return 0; ++} ++ ++static int surface_sam_sid_vhf_poweroff(struct device *dev) ++{ ++ struct sid_vhf *vhf = dev_get_drvdata(dev); ++ ++ if (vhf->hid->driver && vhf->hid->driver->suspend) ++ return vhf->hid->driver->suspend(vhf->hid, PMSG_HIBERNATE); ++ ++ return 0; ++} ++ ++static int surface_sam_sid_vhf_restore(struct device *dev) ++{ ++ struct sid_vhf *vhf = dev_get_drvdata(dev); ++ ++ if (vhf->hid->driver && vhf->hid->driver->reset_resume) ++ return vhf->hid->driver->reset_resume(vhf->hid); ++ ++ return 0; ++} ++ ++struct dev_pm_ops surface_sam_sid_vhf_pm_ops = { ++ .freeze = surface_sam_sid_vhf_freeze, ++ .thaw = surface_sam_sid_vhf_resume, ++ .suspend = surface_sam_sid_vhf_suspend, ++ .resume = surface_sam_sid_vhf_resume, ++ .poweroff = surface_sam_sid_vhf_poweroff, ++ .restore = surface_sam_sid_vhf_restore, ++}; ++ ++#else /* CONFIG_PM */ ++ ++struct dev_pm_ops surface_sam_sid_vhf_pm_ops = { }; ++ ++#endif /* CONFIG_PM */ ++ ++ ++static int surface_sam_sid_vhf_probe(struct ssam_device *sdev) ++{ ++ const struct sid_vhf_properties *p; ++ struct sid_vhf *vhf; ++ struct vhf_device_metadata meta = {}; ++ struct hid_device *hid; ++ int status; ++ ++ p = ssam_device_get_match_data(sdev); ++ if (!p) ++ return -ENODEV; ++ ++ vhf = devm_kzalloc(&sdev->dev, sizeof(*vhf), GFP_KERNEL); ++ if (!vhf) ++ return -ENOMEM; ++ ++ status = vhf_get_metadata(sdev, &meta); ++ if (status) ++ return status; ++ ++ hid = sid_vhf_create_hid_device(sdev, &meta); ++ if (IS_ERR(hid)) ++ return PTR_ERR(hid); ++ ++ 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 = sdev->uid.category; ++ vhf->notif.event.id.instance = sdev->uid.instance; ++ vhf->notif.event.mask = SSAM_EVENT_MASK_STRICT; ++ vhf->notif.event.flags = 0; ++ ++ ssam_device_set_drvdata(sdev, vhf); ++ ++ status = ssam_notifier_register(sdev->ctrl, &vhf->notif); ++ if (status) ++ goto err_notif; ++ ++ status = hid_add_device(hid); ++ if (status) ++ goto err_add_hid; ++ ++ return 0; ++ ++err_add_hid: ++ ssam_notifier_unregister(sdev->ctrl, &vhf->notif); ++err_notif: ++ hid_destroy_device(hid); ++ return status; ++} ++ ++static void surface_sam_sid_vhf_remove(struct ssam_device *sdev) ++{ ++ struct sid_vhf *vhf = ssam_device_get_drvdata(sdev); ++ ++ ssam_notifier_unregister(sdev->ctrl, &vhf->notif); ++ hid_destroy_device(vhf->hid); ++} ++ ++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_SDEV(HID, SSAM_ANY_TID, 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_hid", ++ .pm = &surface_sam_sid_vhf_pm_ops, ++ .probe_type = PROBE_PREFER_ASYNCHRONOUS, ++ }, ++}; ++module_ssam_device_driver(surface_sam_sid_vhf); ++ ++MODULE_AUTHOR("Blaž Hrastnik "); ++MODULE_DESCRIPTION("HID transport-/device-driver for Surface System Aggregator Module"); ++MODULE_LICENSE("GPL"); +diff --git a/drivers/misc/surface_aggregator/clients/surface_hotplug.c b/drivers/misc/surface_aggregator/clients/surface_hotplug.c +new file mode 100644 +index 000000000000..33acbbc5de94 +--- /dev/null ++++ b/drivers/misc/surface_aggregator/clients/surface_hotplug.c +@@ -0,0 +1,1282 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Surface Book (gen. 2 and later) discrete GPU (dGPU) hot-plug system driver. ++ * ++ * Supports explicit setting of the dGPU power-state on the Surface Books via ++ * a user-space interface. Properly handles dGPU hot-plugging by detaching the ++ * base of the device. + */ + +#include @@ -3527,7 +6103,7 @@ index 000000000000..b2232043552a +#include +#include + -+#include ++#include +#include + + @@ -3708,7 +6284,7 @@ index 000000000000..b2232043552a + +static int shps_dgpu_dsm_get_pci_addr_from_adr(struct platform_device *pdev, const char *entry) { + acpi_handle handle = ACPI_HANDLE(&pdev->dev); -+ int status; ++ acpi_status status; + struct acpi_object_list input; + union acpi_object input_args[0]; + u64 device_addr; @@ -3719,9 +6295,8 @@ index 000000000000..b2232043552a + + + status = acpi_evaluate_integer(handle, (acpi_string)entry, &input, &device_addr); -+ if (status) { ++ if (ACPI_FAILURE(status)) + return -ENODEV; -+ } + + bus = 0; + dev = (device_addr & 0xFF0000) >> 16; @@ -3747,9 +6322,8 @@ index 000000000000..b2232043552a + + result = acpi_evaluate_dsm_typed(handle, &SHPS_DSM_UUID, SHPS_DSM_REVISION, + SHPS_DSM_GPU_ADDRS, NULL, ACPI_TYPE_PACKAGE); -+ -+ if (IS_ERR_OR_NULL(result)) -+ return result ? PTR_ERR(result) : -EIO; ++ if (!result) ++ return -EFAULT; + + // three entries per device: name, address, + for (i = 0; i + 2 < result->package.count; i += 3) { @@ -3848,9 +6422,8 @@ index 000000000000..b2232043552a + + result = acpi_evaluate_dsm_typed(handle, &SHPS_DSM_UUID, SHPS_DSM_REVISION, + SHPS_DSM_GPU_POWER, ¶m, ACPI_TYPE_BUFFER); -+ -+ if (IS_ERR_OR_NULL(result)) -+ return result ? PTR_ERR(result) : -EIO; ++ if (!result) ++ return -EFAULT; + + // check for the expected result + if (result->buffer.length != 1 || result->buffer.pointer[0] != 0) { @@ -4550,19 +7123,19 @@ index 000000000000..b2232043552a + +static int shps_start_sgcp_notification(struct platform_device *pdev, acpi_handle *sgpc_handle) { + acpi_handle handle; -+ int status; ++ acpi_status status; + + status = acpi_get_handle(NULL, "\\_SB.SGPC", &handle); -+ if (status) { -+ dev_err(&pdev->dev, "error in get_handle %d\n", status); -+ return status; ++ if (ACPI_FAILURE(status)) { ++ dev_err(&pdev->dev, "error in get_handle %x\n", status); ++ return -ENXIO; + } + + status = acpi_install_notify_handler(handle, ACPI_DEVICE_NOTIFY, shps_sgcp_notify, pdev); -+ if (status) { -+ dev_err(&pdev->dev, "error in install notify %d\n", status); ++ if (ACPI_FAILURE(status)) { ++ dev_err(&pdev->dev, "error in install notify %x\n", status); + *sgpc_handle = NULL; -+ return status; ++ return -EFAULT; + } + + *sgpc_handle = handle; @@ -4570,14 +7143,13 @@ index 000000000000..b2232043552a +} + +static void shps_remove_sgcp_notification(struct platform_device *pdev) { -+ int status; ++ acpi_status status; + struct shps_driver_data *drvdata = platform_get_drvdata(pdev); + + if (drvdata->sgpc_handle) { + status = acpi_remove_notify_handler(drvdata->sgpc_handle, ACPI_DEVICE_NOTIFY, shps_sgcp_notify); -+ if (status) { -+ dev_err(&pdev->dev, "failed to remove notify handler: %d\n", status); -+ } ++ if (ACPI_FAILURE(status)) ++ dev_err(&pdev->dev, "failed to remove notify handler: %x\n", status); + } +} + @@ -4600,7 +7172,6 @@ index 000000000000..b2232043552a + +static int shps_probe(struct platform_device *pdev) +{ -+ struct acpi_device *shps_dev = ACPI_COMPANION(&pdev->dev); + struct shps_driver_data *drvdata; + struct ssam_controller *ctrl; + struct device_link *link; @@ -4630,17 +7201,16 @@ index 000000000000..b2232043552a + } + } + -+ status = acpi_dev_add_driver_gpios(shps_dev, shps_acpi_gpios); ++ status = devm_acpi_dev_add_driver_gpios(&pdev->dev, shps_acpi_gpios); + if (status) { + dev_err(&pdev->dev, "failed to add gpios: %d\n", status); + return status; + } + -+ drvdata = kzalloc(sizeof(struct shps_driver_data), GFP_KERNEL); -+ if (!drvdata) { -+ status = -ENOMEM; -+ goto err_drvdata; -+ } ++ drvdata = devm_kzalloc(&pdev->dev, sizeof(*drvdata), GFP_KERNEL); ++ if (!drvdata) ++ return -ENOMEM; ++ + mutex_init(&drvdata->lock); + platform_set_drvdata(pdev, drvdata); + @@ -4652,7 +7222,7 @@ index 000000000000..b2232043552a + if (IS_ERR(drvdata->dgpu_root_port)) { + status = PTR_ERR(drvdata->dgpu_root_port); + dev_err(&pdev->dev, "failed to get pci dev: %d\n", status); -+ goto err_rp_lookup; ++ return status; + } + + status = shps_gpios_setup(pdev); @@ -4738,17 +7308,11 @@ index 000000000000..b2232043552a + shps_gpios_remove(pdev); +err_gpio: + pci_dev_put(drvdata->dgpu_root_port); -+err_rp_lookup: -+ platform_set_drvdata(pdev, NULL); -+ kfree(drvdata); -+err_drvdata: -+ acpi_dev_remove_driver_gpios(shps_dev); + return status; +} + +static int shps_remove(struct platform_device *pdev) +{ -+ struct acpi_device *shps_dev = ACPI_COMPANION(&pdev->dev); + struct shps_driver_data *drvdata = platform_get_drvdata(pdev); + int status; + @@ -4769,10 +7333,7 @@ index 000000000000..b2232043552a + shps_gpios_remove_irq(pdev); + shps_gpios_remove(pdev); + pci_dev_put(drvdata->dgpu_root_port); -+ platform_set_drvdata(pdev, NULL); -+ kfree(drvdata); + -+ acpi_dev_remove_driver_gpios(shps_dev); + return 0; +} + @@ -4795,2703 +7356,39 @@ index 000000000000..b2232043552a + .remove = shps_remove, + .shutdown = shps_shutdown, + .driver = { -+ .name = "surface_dgpu_hps", ++ .name = "surface_dgpu_hotplug", + .acpi_match_table = shps_acpi_match, + .pm = &shps_pm_ops, + }, +}; -+ +module_platform_driver(surface_sam_hps); + +MODULE_AUTHOR("Maximilian Luz "); -+MODULE_DESCRIPTION("Surface Hot-Plug System (HPS) and dGPU power-state Driver for Surface Book 2"); ++MODULE_DESCRIPTION("DGPU hot-plug system driver for Surface System Aggregator Module"); +MODULE_LICENSE("GPL"); -diff --git a/drivers/misc/surface_sam/clients/surface_sam_san.c b/drivers/misc/surface_sam/clients/surface_sam_san.c +diff --git a/drivers/misc/surface_aggregator/clients/surface_keyboard.c b/drivers/misc/surface_aggregator/clients/surface_keyboard.c new file mode 100644 -index 000000000000..59f2370be8c5 +index 000000000000..580c4255d068 --- /dev/null -+++ b/drivers/misc/surface_sam/clients/surface_sam_san.c -@@ -0,0 +1,841 @@ ++++ b/drivers/misc/surface_aggregator/clients/surface_keyboard.c +@@ -0,0 +1,331 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* -+ * Surface ACPI Notify (SAN) and ACPI integration driver for SAM. -+ * Translates communication from ACPI to SSH and back. -+ */ -+ -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#include -+#include -+ -+ -+struct san_data { -+ struct device *dev; -+ struct ssam_controller *ctrl; -+ -+ struct acpi_connection_info info; -+ -+ struct ssam_event_notifier nf_bat; -+ struct ssam_event_notifier nf_tmp; -+}; -+ -+#define to_san_data(ptr, member) \ -+ container_of(ptr, struct san_data, member) -+ -+ -+/* -- dGPU Notifier Interface. ---------------------------------------------- */ -+ -+struct san_rqsg_if { -+ struct rw_semaphore lock; -+ struct device *dev; -+ struct blocking_notifier_head nh; -+}; -+ -+static struct san_rqsg_if san_rqsg_if = { -+ .lock = __RWSEM_INITIALIZER(san_rqsg_if.lock), -+ .dev = NULL, -+ .nh = BLOCKING_NOTIFIER_INIT(san_rqsg_if.nh), -+}; -+ -+static int san_set_rqsg_interface_device(struct device *dev) -+{ -+ int status = 0; -+ -+ down_write(&san_rqsg_if.lock); -+ if (!san_rqsg_if.dev && dev) -+ san_rqsg_if.dev = dev; -+ else -+ status = -EBUSY; -+ up_write(&san_rqsg_if.lock); -+ -+ return status; -+} -+ -+/** -+ * san_client_link() - Link client as consumer to SAN device. -+ * @client: The client to link. ++ * Surface System Aggregator Module (SSAM) legacy HID input device driver. + * -+ * Sets up a device link between the provided client device as consumer and -+ * the SAN device as provider. This function can be used to ensure that the -+ * SAN interface has been set up and will be set up for as long as the driver -+ * of the client device is bound. This guarantees that, during that time, all -+ * dGPU events will be received by any registered notifier. -+ * -+ * The link will be automatically removed once the client device's driver is -+ * unbound. -+ * -+ * Return: Returns zero on succes, %-ENXIO if the SAN interface has not been -+ * set up yet, and %-ENOMEM if device link creation failed. -+ */ -+int san_client_link(struct device *client) -+{ -+ const u32 flags = DL_FLAG_PM_RUNTIME | DL_FLAG_AUTOREMOVE_CONSUMER; -+ struct device_link *link; -+ -+ down_read(&san_rqsg_if.lock); -+ -+ if (!san_rqsg_if.dev) { -+ up_read(&san_rqsg_if.lock); -+ return -ENXIO; -+ } -+ -+ link = device_link_add(client, san_rqsg_if.dev, flags); -+ if (!link) { -+ up_read(&san_rqsg_if.lock); -+ return -ENOMEM; -+ } -+ -+ if (READ_ONCE(link->status) == DL_STATE_SUPPLIER_UNBIND) { -+ up_read(&san_rqsg_if.lock); -+ return -ENXIO; -+ } -+ -+ up_read(&san_rqsg_if.lock); -+ return 0; -+} -+EXPORT_SYMBOL_GPL(san_client_link); -+ -+/** -+ * san_dgpu_notifier_register() - Register a SAN dGPU notifier. -+ * @nb: The notifier-block to register. -+ * -+ * Registers a SAN dGPU notifier, receiving any new SAN dGPU events sent from -+ * ACPI. The registered notifier will be called with &struct san_dgpu_event -+ * as notifier data and the command ID of that event as notifier action. -+ */ -+int san_dgpu_notifier_register(struct notifier_block *nb) -+{ -+ return blocking_notifier_chain_register(&san_rqsg_if.nh, nb); -+} -+EXPORT_SYMBOL_GPL(san_dgpu_notifier_register); -+ -+/** -+ * san_dgpu_notifier_unregister() - Unregister a SAN dGPU notifier. -+ * @nb: The notifier-block to unregister. -+ */ -+int san_dgpu_notifier_unregister(struct notifier_block *nb) -+{ -+ return blocking_notifier_chain_unregister(&san_rqsg_if.nh, nb); -+} -+EXPORT_SYMBOL_GPL(san_dgpu_notifier_unregister); -+ -+static int san_dgpu_notifier_call(struct san_dgpu_event *evt) -+{ -+ int ret; -+ -+ ret = blocking_notifier_call_chain(&san_rqsg_if.nh, evt->command, evt); -+ return notifier_to_errno(ret); -+} -+ -+ -+/* -- ACPI _DSM event relay. ------------------------------------------------ */ -+ -+#define SAN_DSM_REVISION 0 -+ -+static const guid_t SAN_DSM_UUID = -+ GUID_INIT(0x93b666c5, 0x70c6, 0x469f, 0xa2, 0x15, 0x3d, -+ 0x48, 0x7c, 0x91, 0xab, 0x3c); -+ -+enum san_dsm_event_fn { -+ SAN_DSM_EVENT_FN_BAT1_STAT = 0x03, -+ SAN_DSM_EVENT_FN_BAT1_INFO = 0x04, -+ SAN_DSM_EVENT_FN_ADP1_STAT = 0x05, -+ SAN_DSM_EVENT_FN_ADP1_INFO = 0x06, -+ SAN_DSM_EVENT_FN_BAT2_STAT = 0x07, -+ SAN_DSM_EVENT_FN_BAT2_INFO = 0x08, -+ SAN_DSM_EVENT_FN_THERMAL = 0x09, -+ SAN_DSM_EVENT_FN_DPTF = 0x0a, -+}; -+ -+enum sam_event_cid_bat { -+ SAM_EVENT_CID_BAT_BIX = 0x15, -+ SAM_EVENT_CID_BAT_BST = 0x16, -+ SAM_EVENT_CID_BAT_ADP = 0x17, -+ SAM_EVENT_CID_BAT_PROT = 0x18, -+ SAM_EVENT_CID_BAT_DPTF = 0x4f, -+}; -+ -+enum sam_event_cid_tmp { -+ SAM_EVENT_CID_TMP_TRIP = 0x0b, -+}; -+ -+struct san_event_work { -+ struct delayed_work work; -+ struct device *dev; -+ struct ssam_event event; // must be last -+}; -+ -+static int san_acpi_notify_event(struct device *dev, u64 func, -+ union acpi_object *param) -+{ -+ acpi_handle san = ACPI_HANDLE(dev); -+ union acpi_object *obj; -+ int status = 0; -+ -+ if (!acpi_check_dsm(san, &SAN_DSM_UUID, SAN_DSM_REVISION, 1 << func)) -+ return 0; -+ -+ dev_dbg(dev, "notify event 0x%02llx\n", func); -+ -+ obj = acpi_evaluate_dsm_typed(san, &SAN_DSM_UUID, SAN_DSM_REVISION, -+ func, param, ACPI_TYPE_BUFFER); -+ if (IS_ERR_OR_NULL(obj)) -+ return obj ? PTR_ERR(obj) : -ENXIO; -+ -+ if (obj->buffer.length != 1 || obj->buffer.pointer[0] != 0) { -+ dev_err(dev, "got unexpected result from _DSM\n"); -+ status = -EFAULT; -+ } -+ -+ ACPI_FREE(obj); -+ return status; -+} -+ -+static int san_evt_bat_adp(struct device *dev, const struct ssam_event *event) -+{ -+ int status; -+ -+ status = san_acpi_notify_event(dev, SAN_DSM_EVENT_FN_ADP1_STAT, NULL); -+ if (status) -+ return status; -+ -+ /* -+ * Enusre that the battery states get updated correctly. -+ * When the battery is fully charged and an adapter is plugged in, it -+ * sometimes is not updated correctly, instead showing it as charging. -+ * Explicitly trigger battery updates to fix this. -+ */ -+ -+ status = san_acpi_notify_event(dev, SAN_DSM_EVENT_FN_BAT1_STAT, NULL); -+ if (status) -+ return status; -+ -+ return san_acpi_notify_event(dev, SAN_DSM_EVENT_FN_BAT2_STAT, NULL); -+} -+ -+static int san_evt_bat_bix(struct device *dev, const struct ssam_event *event) -+{ -+ enum san_dsm_event_fn fn; -+ -+ if (event->instance_id == 0x02) -+ fn = SAN_DSM_EVENT_FN_BAT2_INFO; -+ else -+ fn = SAN_DSM_EVENT_FN_BAT1_INFO; -+ -+ return san_acpi_notify_event(dev, fn, NULL); -+} -+ -+static int san_evt_bat_bst(struct device *dev, const struct ssam_event *event) -+{ -+ enum san_dsm_event_fn fn; -+ -+ if (event->instance_id == 0x02) -+ fn = SAN_DSM_EVENT_FN_BAT2_STAT; -+ else -+ fn = SAN_DSM_EVENT_FN_BAT1_STAT; -+ -+ return san_acpi_notify_event(dev, fn, NULL); -+} -+ -+static int san_evt_bat_dptf(struct device *dev, const struct ssam_event *event) -+{ -+ union acpi_object payload; -+ -+ /* -+ * The Surface ACPI expects a buffer and not a package. It specifically -+ * checks for ObjectType (Arg3) == 0x03. This will cause a warning in -+ * acpica/nsarguments.c, but that warning can be safely ignored. -+ */ -+ payload.type = ACPI_TYPE_BUFFER; -+ payload.buffer.length = event->length; -+ payload.buffer.pointer = (u8 *)&event->data[0]; -+ -+ return san_acpi_notify_event(dev, SAN_DSM_EVENT_FN_DPTF, &payload); -+} -+ -+static unsigned long san_evt_bat_delay(u8 cid) -+{ -+ switch (cid) { -+ case SAM_EVENT_CID_BAT_ADP: -+ /* -+ * Wait for battery state to update before signalling adapter -+ * change. -+ */ -+ return msecs_to_jiffies(5000); -+ -+ case SAM_EVENT_CID_BAT_BST: -+ /* Ensure we do not miss anything important due to caching. */ -+ return msecs_to_jiffies(2000); -+ -+ default: -+ return 0; -+ } -+} -+ -+static bool san_evt_bat(const struct ssam_event *event, struct device *dev) -+{ -+ int status; -+ -+ switch (event->command_id) { -+ case SAM_EVENT_CID_BAT_BIX: -+ status = san_evt_bat_bix(dev, event); -+ break; -+ -+ case SAM_EVENT_CID_BAT_BST: -+ status = san_evt_bat_bst(dev, event); -+ break; -+ -+ case SAM_EVENT_CID_BAT_ADP: -+ status = san_evt_bat_adp(dev, event); -+ break; -+ -+ case SAM_EVENT_CID_BAT_PROT: -+ /* -+ * TODO: Implement support for battery protection status change -+ * event. -+ */ -+ return true; -+ -+ case SAM_EVENT_CID_BAT_DPTF: -+ status = san_evt_bat_dptf(dev, event); -+ break; -+ -+ default: -+ return false; -+ } -+ -+ if (status) -+ dev_err(dev, "error handling power event (cid = %x)\n", -+ event->command_id); -+ -+ return true; -+} -+ -+static void san_evt_bat_workfn(struct work_struct *work) -+{ -+ struct san_event_work *ev; -+ -+ ev = container_of(work, struct san_event_work, work.work); -+ san_evt_bat(&ev->event, ev->dev); -+ kfree(ev); -+} -+ -+static u32 san_evt_bat_nf(struct ssam_event_notifier *nf, -+ const struct ssam_event *event) -+{ -+ struct san_data *d = to_san_data(nf, nf_bat); -+ struct san_event_work *work; -+ unsigned long delay = san_evt_bat_delay(event->command_id); -+ -+ if (delay == 0) -+ return san_evt_bat(event, d->dev) ? SSAM_NOTIF_HANDLED : 0; -+ -+ work = kzalloc(sizeof(struct san_event_work) + event->length, GFP_KERNEL); -+ if (!work) -+ return ssam_notifier_from_errno(-ENOMEM); -+ -+ INIT_DELAYED_WORK(&work->work, san_evt_bat_workfn); -+ work->dev = d->dev; -+ -+ memcpy(&work->event, event, sizeof(struct ssam_event) + event->length); -+ -+ schedule_delayed_work(&work->work, delay); -+ return SSAM_NOTIF_HANDLED; -+} -+ -+static int san_evt_tmp_trip(struct device *dev, const struct ssam_event *event) -+{ -+ union acpi_object param; -+ -+ /* -+ * The Surface ACPI expects an integer and not a package. This will -+ * cause a warning in acpica/nsarguments.c, but that warning can be -+ * safely ignored. -+ */ -+ param.type = ACPI_TYPE_INTEGER; -+ param.integer.value = event->instance_id; -+ -+ return san_acpi_notify_event(dev, SAN_DSM_EVENT_FN_THERMAL, ¶m); -+} -+ -+static bool san_evt_tmp(const struct ssam_event *event, struct device *dev) -+{ -+ int status; -+ -+ switch (event->command_id) { -+ case SAM_EVENT_CID_TMP_TRIP: -+ status = san_evt_tmp_trip(dev, event); -+ break; -+ -+ default: -+ return false; -+ } -+ -+ if (status) { -+ dev_err(dev, "error handling thermal event (cid = %x)\n", -+ event->command_id); -+ } -+ -+ return true; -+} -+ -+static u32 san_evt_tmp_nf(struct ssam_event_notifier *nf, -+ const struct ssam_event *event) -+{ -+ struct san_data *d = to_san_data(nf, nf_bat); -+ -+ return san_evt_tmp(event, d->dev) ? SSAM_NOTIF_HANDLED : 0; -+} -+ -+ -+/* -- ACPI GSB OperationRegion Handler -------------------------------------- */ -+ -+struct gsb_data_in { -+ u8 cv; -+} __packed; -+ -+struct gsb_data_rqsx { -+ u8 cv; // command value (san_gsb_request_cv) -+ u8 tc; // target category -+ u8 tid; // target ID -+ u8 iid; // instance ID -+ u8 snc; // expect-response-flag? -+ u8 cid; // command ID -+ u16 cdl; // payload length -+ u8 pld[0]; // payload -+} __packed; -+ -+struct gsb_data_etwl { -+ u8 cv; // command value (should be 0x02) -+ u8 etw3; // unknown -+ u8 etw4; // unknown -+ u8 msg[0]; // error message (ASCIIZ) -+} __packed; -+ -+struct gsb_data_out { -+ u8 status; // _SSH communication status -+ u8 len; // _SSH payload length -+ u8 pld[0]; // _SSH payload -+} __packed; -+ -+union gsb_buffer_data { -+ struct gsb_data_in in; // common input -+ struct gsb_data_rqsx rqsx; // RQSX input -+ struct gsb_data_etwl etwl; // ETWL input -+ struct gsb_data_out out; // output -+}; -+ -+struct gsb_buffer { -+ u8 status; // GSB AttribRawProcess status -+ u8 len; // GSB AttribRawProcess length -+ union gsb_buffer_data data; -+} __packed; -+ -+#define SAN_GSB_MAX_RQSX_PAYLOAD (U8_MAX - 2 - sizeof(struct gsb_data_rqsx)) -+#define SAN_GSB_MAX_RESPONSE (U8_MAX - 2 - sizeof(struct gsb_data_out)) -+ -+#define SAN_GSB_COMMAND 0 -+ -+enum san_gsb_request_cv { -+ SAN_GSB_REQUEST_CV_RQST = 0x01, -+ SAN_GSB_REQUEST_CV_ETWL = 0x02, -+ SAN_GSB_REQUEST_CV_RQSG = 0x03, -+}; -+ -+#define SAN_REQUEST_NUM_TRIES 5 -+ -+static acpi_status san_etwl(struct san_data *d, struct gsb_buffer *b) -+{ -+ struct gsb_data_etwl *etwl = &b->data.etwl; -+ -+ if (b->len < sizeof(struct gsb_data_etwl)) { -+ dev_err(d->dev, "invalid ETWL package (len = %d)\n", b->len); -+ return AE_OK; -+ } -+ -+ dev_err(d->dev, "ETWL(0x%02x, 0x%02x): %.*s\n", etwl->etw3, etwl->etw4, -+ (unsigned int)(b->len - sizeof(struct gsb_data_etwl)), -+ (char *)etwl->msg); -+ -+ // indicate success -+ b->status = 0x00; -+ b->len = 0x00; -+ -+ return AE_OK; -+} -+ -+static struct gsb_data_rqsx *san_validate_rqsx(struct device *dev, -+ const char *type, struct gsb_buffer *b) -+{ -+ struct gsb_data_rqsx *rqsx = &b->data.rqsx; -+ -+ if (b->len < sizeof(struct gsb_data_rqsx)) { -+ dev_err(dev, "invalid %s package (len = %d)\n", type, b->len); -+ return NULL; -+ } -+ -+ if (get_unaligned(&rqsx->cdl) != b->len - sizeof(struct gsb_data_rqsx)) { -+ dev_err(dev, "bogus %s package (len = %d, cdl = %d)\n", -+ type, b->len, get_unaligned(&rqsx->cdl)); -+ return NULL; -+ } -+ -+ if (get_unaligned(&rqsx->cdl) > SAN_GSB_MAX_RQSX_PAYLOAD) { -+ dev_err(dev, "payload for %s package too large (cdl = %d)\n", -+ type, get_unaligned(&rqsx->cdl)); -+ return NULL; -+ } -+ -+ return rqsx; -+} -+ -+static void gsb_rqsx_response_error(struct gsb_buffer *gsb, int status) -+{ -+ gsb->status = 0x00; -+ gsb->len = 0x02; -+ gsb->data.out.status = (u8)(-status); -+ gsb->data.out.len = 0x00; -+} -+ -+static void gsb_rqsx_response_success(struct gsb_buffer *gsb, u8 *ptr, size_t len) -+{ -+ gsb->status = 0x00; -+ gsb->len = len + 2; -+ gsb->data.out.status = 0x00; -+ gsb->data.out.len = len; -+ -+ if (len) -+ memcpy(&gsb->data.out.pld[0], ptr, len); -+} -+ -+static acpi_status san_rqst_fixup_suspended(struct ssam_request *rqst, -+ struct gsb_buffer *gsb) -+{ -+ if (rqst->target_category == SSAM_SSH_TC_BAS && rqst->command_id == 0x0D) { -+ u8 base_state = 1; -+ -+ /* Base state quirk: -+ * The base state may be queried from ACPI when the EC is still -+ * suspended. In this case it will return '-EPERM'. This query -+ * will only be triggered from the ACPI lid GPE interrupt, thus -+ * we are either in laptop or studio mode (base status 0x01 or -+ * 0x02). Furthermore, we will only get here if the device (and -+ * EC) have been suspended. -+ * -+ * We now assume that the device is in laptop mode (0x01). This -+ * has the drawback that it will wake the device when unfolding -+ * it in studio mode, but it also allows us to avoid actively -+ * waiting for the EC to wake up, which may incur a notable -+ * delay. -+ */ -+ -+ gsb_rqsx_response_success(gsb, &base_state, sizeof(base_state)); -+ return AE_OK; -+ } -+ -+ gsb_rqsx_response_error(gsb, -ENXIO); -+ return AE_OK; -+} -+ -+static acpi_status san_rqst(struct san_data *d, struct gsb_buffer *buffer) -+{ -+ u8 rspbuf[SAN_GSB_MAX_RESPONSE]; -+ struct gsb_data_rqsx *gsb_rqst; -+ struct ssam_request rqst; -+ struct ssam_response rsp; -+ int status = 0; -+ -+ gsb_rqst = san_validate_rqsx(d->dev, "RQST", buffer); -+ if (!gsb_rqst) -+ return AE_OK; -+ -+ rqst.target_category = gsb_rqst->tc; -+ rqst.target_id = gsb_rqst->tid; -+ rqst.command_id = gsb_rqst->cid; -+ rqst.instance_id = gsb_rqst->iid; -+ rqst.flags = gsb_rqst->snc ? SSAM_REQUEST_HAS_RESPONSE : 0; -+ rqst.length = get_unaligned(&gsb_rqst->cdl); -+ rqst.payload = &gsb_rqst->pld[0]; -+ -+ rsp.capacity = ARRAY_SIZE(rspbuf); -+ rsp.length = 0; -+ rsp.pointer = &rspbuf[0]; -+ -+ // handle suspended device -+ if (d->dev->power.is_suspended) { -+ dev_warn(d->dev, "rqst: device is suspended, not executing\n"); -+ return san_rqst_fixup_suspended(&rqst, buffer); -+ } -+ -+ status = ssam_retry(ssam_request_sync_onstack, SAN_REQUEST_NUM_TRIES, -+ d->ctrl, &rqst, &rsp, SAN_GSB_MAX_RQSX_PAYLOAD); -+ -+ if (!status) { -+ gsb_rqsx_response_success(buffer, rsp.pointer, rsp.length); -+ } else { -+ dev_err(d->dev, "rqst: failed with error %d\n", status); -+ gsb_rqsx_response_error(buffer, status); -+ } -+ -+ return AE_OK; -+} -+ -+static acpi_status san_rqsg(struct san_data *d, struct gsb_buffer *buffer) -+{ -+ struct gsb_data_rqsx *gsb_rqsg; -+ struct san_dgpu_event evt; -+ int status; -+ -+ gsb_rqsg = san_validate_rqsx(d->dev, "RQSG", buffer); -+ if (!gsb_rqsg) -+ return AE_OK; -+ -+ evt.category = gsb_rqsg->tc; -+ evt.target = gsb_rqsg->tid; -+ evt.command = gsb_rqsg->cid; -+ evt.instance = gsb_rqsg->iid; -+ evt.length = get_unaligned(&gsb_rqsg->cdl); -+ evt.payload = &gsb_rqsg->pld[0]; -+ -+ status = san_dgpu_notifier_call(&evt); -+ if (!status) { -+ gsb_rqsx_response_success(buffer, NULL, 0); -+ } else { -+ dev_err(d->dev, "rqsg: failed with error %d\n", status); -+ gsb_rqsx_response_error(buffer, status); -+ } -+ -+ return AE_OK; -+} -+ -+static acpi_status san_opreg_handler(u32 function, -+ acpi_physical_address command, u32 bits, u64 *value64, -+ void *opreg_context, void *region_context) -+{ -+ struct san_data *d = to_san_data(opreg_context, info); -+ struct gsb_buffer *buffer = (struct gsb_buffer *)value64; -+ int accessor_type = (function & 0xFFFF0000) >> 16; -+ -+ if (command != SAN_GSB_COMMAND) { -+ dev_warn(d->dev, "unsupported command: 0x%02llx\n", command); -+ return AE_OK; -+ } -+ -+ if (accessor_type != ACPI_GSB_ACCESS_ATTRIB_RAW_PROCESS) { -+ dev_err(d->dev, "invalid access type: 0x%02x\n", accessor_type); -+ return AE_OK; -+ } -+ -+ // buffer must have at least contain the command-value -+ if (buffer->len == 0) { -+ dev_err(d->dev, "request-package too small\n"); -+ return AE_OK; -+ } -+ -+ switch (buffer->data.in.cv) { -+ case SAN_GSB_REQUEST_CV_RQST: -+ return san_rqst(d, buffer); -+ -+ case SAN_GSB_REQUEST_CV_ETWL: -+ return san_etwl(d, buffer); -+ -+ case SAN_GSB_REQUEST_CV_RQSG: -+ return san_rqsg(d, buffer); -+ -+ default: -+ dev_warn(d->dev, "unsupported SAN0 request (cv: 0x%02x)\n", -+ buffer->data.in.cv); -+ return AE_OK; -+ } -+} -+ -+ -+/* -- Driver setup. --------------------------------------------------------- */ -+ -+struct san_acpi_consumer { -+ const char *path; -+}; -+ -+static int san_events_register(struct platform_device *pdev) -+{ -+ struct san_data *d = platform_get_drvdata(pdev); -+ int status; -+ -+ d->nf_bat.base.priority = 1; -+ d->nf_bat.base.fn = san_evt_bat_nf; -+ d->nf_bat.event.reg = SSAM_EVENT_REGISTRY_SAM; -+ d->nf_bat.event.id.target_category = SSAM_SSH_TC_BAT; -+ d->nf_bat.event.id.instance = 0; -+ d->nf_bat.event.mask = SSAM_EVENT_MASK_TARGET; -+ d->nf_bat.event.flags = SSAM_EVENT_SEQUENCED; -+ -+ d->nf_tmp.base.priority = 1; -+ d->nf_tmp.base.fn = san_evt_tmp_nf; -+ d->nf_tmp.event.reg = SSAM_EVENT_REGISTRY_SAM; -+ d->nf_tmp.event.id.target_category = SSAM_SSH_TC_TMP; -+ d->nf_tmp.event.id.instance = 0; -+ d->nf_tmp.event.mask = SSAM_EVENT_MASK_TARGET; -+ d->nf_tmp.event.flags = SSAM_EVENT_SEQUENCED; -+ -+ status = ssam_notifier_register(d->ctrl, &d->nf_bat); -+ if (status) -+ return status; -+ -+ status = ssam_notifier_register(d->ctrl, &d->nf_tmp); -+ if (status) -+ ssam_notifier_unregister(d->ctrl, &d->nf_bat); -+ -+ return status; -+} -+ -+static void san_events_unregister(struct platform_device *pdev) -+{ -+ struct san_data *d = platform_get_drvdata(pdev); -+ -+ ssam_notifier_unregister(d->ctrl, &d->nf_bat); -+ ssam_notifier_unregister(d->ctrl, &d->nf_tmp); -+} -+ -+static int san_consumers_link(struct platform_device *pdev, -+ const struct san_acpi_consumer *cons) -+{ -+ const u32 flags = DL_FLAG_PM_RUNTIME | DL_FLAG_AUTOREMOVE_SUPPLIER; -+ const struct san_acpi_consumer *c; -+ -+ for (c = cons; c && c->path; ++c) { -+ struct acpi_device *adev; -+ acpi_handle handle; -+ int status; -+ -+ status = acpi_get_handle(NULL, (acpi_string)c->path, &handle); -+ if (status && status != AE_NOT_FOUND) -+ return -ENXIO; -+ -+ status = acpi_bus_get_device(handle, &adev); -+ if (status) -+ return status; -+ -+ if (!device_link_add(&adev->dev, &pdev->dev, flags)) -+ return -EFAULT; -+ } -+ -+ return 0; -+} -+ -+static int surface_sam_san_probe(struct platform_device *pdev) -+{ -+ const struct san_acpi_consumer *cons; -+ acpi_handle san = ACPI_HANDLE(&pdev->dev); -+ struct ssam_controller *ctrl; -+ struct san_data *data; -+ int status; -+ -+ status = ssam_client_bind(&pdev->dev, &ctrl); -+ if (status) -+ return status == -ENXIO ? -EPROBE_DEFER : status; -+ -+ cons = acpi_device_get_match_data(&pdev->dev); -+ if (cons) { -+ status = san_consumers_link(pdev, cons); -+ if (status) -+ return status; -+ } -+ -+ data = devm_kzalloc(&pdev->dev, sizeof(struct san_data), GFP_KERNEL); -+ if (!data) -+ return -ENOMEM; -+ -+ data->dev = &pdev->dev; -+ data->ctrl = ctrl; -+ -+ platform_set_drvdata(pdev, data); -+ -+ status = acpi_install_address_space_handler(san, ACPI_ADR_SPACE_GSBUS, -+ &san_opreg_handler, NULL, &data->info); -+ -+ if (ACPI_FAILURE(status)) { -+ status = -ENXIO; -+ goto err_install_handler; -+ } -+ -+ status = san_events_register(pdev); -+ if (status) -+ goto err_enable_events; -+ -+ status = san_set_rqsg_interface_device(&pdev->dev); -+ if (status) -+ goto err_install_dev; -+ -+ acpi_walk_dep_device_list(san); -+ return 0; -+ -+err_install_dev: -+ san_events_unregister(pdev); -+err_enable_events: -+ acpi_remove_address_space_handler(san, ACPI_ADR_SPACE_GSBUS, -+ &san_opreg_handler); -+err_install_handler: -+ platform_set_drvdata(san, NULL); -+ return status; -+} -+ -+static int surface_sam_san_remove(struct platform_device *pdev) -+{ -+ acpi_handle san = ACPI_HANDLE(&pdev->dev); // _SAN device node -+ acpi_status status = AE_OK; -+ -+ san_set_rqsg_interface_device(NULL); -+ acpi_remove_address_space_handler(san, ACPI_ADR_SPACE_GSBUS, -+ &san_opreg_handler); -+ san_events_unregister(pdev); -+ -+ /* -+ * We have unregistered our event sources. Now we need to ensure that -+ * all delayed works they may have spawned are run to completion. -+ */ -+ flush_scheduled_work(); -+ -+ platform_set_drvdata(pdev, NULL); -+ return status; -+} -+ -+/* -+ * ACPI devices that make use of the SAM EC via the SAN interface. Link them -+ * to the SAN device to try and enforce correct suspend/resume orderding. -+ */ -+static const struct san_acpi_consumer san_mshw0091_consumers[] = { -+ { "\\_SB.SRTC" }, -+ { "\\ADP1" }, -+ { "\\_SB.BAT1" }, -+ { "\\_SB.BAT2" }, -+ { }, -+}; -+ -+static const struct acpi_device_id surface_sam_san_match[] = { -+ { "MSHW0091", (unsigned long) san_mshw0091_consumers }, -+ { }, -+}; -+MODULE_DEVICE_TABLE(acpi, surface_sam_san_match); -+ -+static struct platform_driver surface_sam_san = { -+ .probe = surface_sam_san_probe, -+ .remove = surface_sam_san_remove, -+ .driver = { -+ .name = "surface_sam_san", -+ .acpi_match_table = surface_sam_san_match, -+ .probe_type = PROBE_PREFER_ASYNCHRONOUS, -+ }, -+}; -+module_platform_driver(surface_sam_san); -+ -+MODULE_AUTHOR("Maximilian Luz "); -+MODULE_DESCRIPTION("Surface ACPI Notify Driver for 5th Generation Surface Devices"); -+MODULE_LICENSE("GPL"); -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 000000000000..44f5d31709b2 ---- /dev/null -+++ b/drivers/misc/surface_sam/clients/surface_sam_sid_perfmode.c -@@ -0,0 +1,190 @@ -+// SPDX-License-Identifier: GPL-2.0-or-later -+/* -+ * Surface Performance Mode Driver. -+ * Allows to change cooling capabilities based on user preference. -+ */ -+ -+#include -+#include -+#include -+#include -+ -+#include -+ -+ -+#define SID_PARAM_PERM 0644 -+ -+enum sam_perf_mode { -+ SAM_PERF_MODE_NORMAL = 1, -+ SAM_PERF_MODE_BATTERY = 2, -+ SAM_PERF_MODE_PERF1 = 3, -+ SAM_PERF_MODE_PERF2 = 4, -+ -+ __SAM_PERF_MODE__START = 1, -+ __SAM_PERF_MODE__END = 4, -+}; -+ -+enum sid_param_perf_mode { -+ SID_PARAM_PERF_MODE_AS_IS = 0, -+ SID_PARAM_PERF_MODE_NORMAL = SAM_PERF_MODE_NORMAL, -+ SID_PARAM_PERF_MODE_BATTERY = SAM_PERF_MODE_BATTERY, -+ SID_PARAM_PERF_MODE_PERF1 = SAM_PERF_MODE_PERF1, -+ SID_PARAM_PERF_MODE_PERF2 = SAM_PERF_MODE_PERF2, -+ -+ __SID_PARAM_PERF_MODE__START = 0, -+ __SID_PARAM_PERF_MODE__END = 4, -+}; -+ -+ -+struct ssam_perf_info { -+ __le32 mode; -+ __le16 unknown1; -+ __le16 unknown2; -+} __packed; -+ -+static SSAM_DEFINE_SYNC_REQUEST_CL_R(ssam_tmp_perf_mode_get, struct ssam_perf_info, { -+ .target_category = SSAM_SSH_TC_TMP, -+ .command_id = 0x02, -+}); -+ -+static SSAM_DEFINE_SYNC_REQUEST_CL_W(__ssam_tmp_perf_mode_set, __le32, { -+ .target_category = SSAM_SSH_TC_TMP, -+ .command_id = 0x03, -+}); -+ -+static int ssam_tmp_perf_mode_set(struct ssam_device *sdev, u32 mode) -+{ -+ __le32 mode_le = cpu_to_le32(mode); -+ -+ if (mode < __SAM_PERF_MODE__START || mode > __SAM_PERF_MODE__END) -+ return -EINVAL; -+ -+ return __ssam_tmp_perf_mode_set(sdev, &mode_le); -+} -+ -+ -+static int param_perf_mode_set(const char *val, const struct kernel_param *kp) -+{ -+ int perf_mode; -+ int status; -+ -+ status = kstrtoint(val, 0, &perf_mode); -+ if (status) -+ return status; -+ -+ if (perf_mode < __SID_PARAM_PERF_MODE__START || perf_mode > __SID_PARAM_PERF_MODE__END) -+ return -EINVAL; -+ -+ return param_set_int(val, kp); -+} -+ -+static const struct kernel_param_ops param_perf_mode_ops = { -+ .set = param_perf_mode_set, -+ .get = param_get_int, -+}; -+ -+static int param_perf_mode_init = SID_PARAM_PERF_MODE_AS_IS; -+static int param_perf_mode_exit = SID_PARAM_PERF_MODE_AS_IS; -+ -+module_param_cb(perf_mode_init, ¶m_perf_mode_ops, ¶m_perf_mode_init, SID_PARAM_PERM); -+module_param_cb(perf_mode_exit, ¶m_perf_mode_ops, ¶m_perf_mode_exit, SID_PARAM_PERM); -+ -+MODULE_PARM_DESC(perf_mode_init, "Performance-mode to be set on module initialization"); -+MODULE_PARM_DESC(perf_mode_exit, "Performance-mode to be set on module exit"); -+ -+ -+static ssize_t perf_mode_show(struct device *dev, struct device_attribute *attr, char *data) -+{ -+ struct ssam_device *sdev = to_ssam_device(dev); -+ struct ssam_perf_info info; -+ int status; -+ -+ status = ssam_tmp_perf_mode_get(sdev, &info); -+ if (status) { -+ dev_err(dev, "failed to get current performance mode: %d\n", status); -+ return -EIO; -+ } -+ -+ return sprintf(data, "%d\n", le32_to_cpu(info.mode)); -+} -+ -+static ssize_t perf_mode_store(struct device *dev, struct device_attribute *attr, -+ const char *data, size_t count) -+{ -+ struct ssam_device *sdev = to_ssam_device(dev); -+ int perf_mode; -+ int status; -+ -+ status = kstrtoint(data, 0, &perf_mode); -+ if (status) -+ return status; -+ -+ status = ssam_tmp_perf_mode_set(sdev, perf_mode); -+ if (status) -+ return status; -+ -+ // TODO: Should we notify ACPI here? -+ // -+ // There is a _DSM call described as -+ // WSID._DSM: Notify DPTF on Slider State change -+ // which calls -+ // ODV3 = ToInteger (Arg3) -+ // Notify(IETM, 0x88) -+ // IETM is an INT3400 Intel Dynamic Power Performance Management -+ // device, part of the DPTF framework. From the corresponding -+ // kernel driver, it looks like event 0x88 is being ignored. Also -+ // it is currently unknown what the consequecnes of setting ODV3 -+ // are. -+ -+ return count; -+} -+ -+static const DEVICE_ATTR_RW(perf_mode); -+ -+ -+static int surface_sam_sid_perfmode_probe(struct ssam_device *sdev) -+{ -+ int status; -+ -+ // set initial perf_mode -+ if (param_perf_mode_init != SID_PARAM_PERF_MODE_AS_IS) { -+ status = ssam_tmp_perf_mode_set(sdev, param_perf_mode_init); -+ if (status) -+ return status; -+ } -+ -+ // register perf_mode attribute -+ status = sysfs_create_file(&sdev->dev.kobj, &dev_attr_perf_mode.attr); -+ if (status) -+ ssam_tmp_perf_mode_set(sdev, param_perf_mode_exit); -+ -+ return status; -+} -+ -+static void surface_sam_sid_perfmode_remove(struct ssam_device *sdev) -+{ -+ sysfs_remove_file(&sdev->dev.kobj, &dev_attr_perf_mode.attr); -+ ssam_tmp_perf_mode_set(sdev, param_perf_mode_exit); -+} -+ -+ -+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_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"); -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 000000000000..da8dd1e914b7 ---- /dev/null -+++ b/drivers/misc/surface_sam/clients/surface_sam_sid_power.c -@@ -0,0 +1,1114 @@ -+// SPDX-License-Identifier: GPL-2.0-or-later -+/* -+ * Surface SID Battery/AC Driver. -+ * Provides support for the battery and AC on 7th generation Surface devices. -+ */ -+ -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#include -+ -+ -+// TODO: check BIX/BST for unknown/unsupported 0xffffffff entries -+// TODO: DPTF (/SAN notifications)? -+// TODO: other properties? -+ -+ -+static unsigned int cache_time = 1000; -+module_param(cache_time, uint, 0644); -+MODULE_PARM_DESC(cache_time, "battery state chaching time in milliseconds [default: 1000]"); -+ -+#define SPWR_AC_BAT_UPDATE_DELAY msecs_to_jiffies(5000) -+ -+ -+/* -+ * SAM Interface. -+ */ -+ -+#define SAM_EVENT_PWR_CID_BIX 0x15 -+#define SAM_EVENT_PWR_CID_BST 0x16 -+#define SAM_EVENT_PWR_CID_ADAPTER 0x17 -+ -+#define SAM_BATTERY_STA_OK 0x0f -+#define SAM_BATTERY_STA_PRESENT 0x10 -+ -+#define SAM_BATTERY_STATE_DISCHARGING 0x01 -+#define SAM_BATTERY_STATE_CHARGING 0x02 -+#define SAM_BATTERY_STATE_CRITICAL 0x04 -+ -+#define SAM_BATTERY_POWER_UNIT_MA 1 -+ -+ -+/* Equivalent to data returned in ACPI _BIX method */ -+struct spwr_bix { -+ u8 revision; -+ __le32 power_unit; -+ __le32 design_cap; -+ __le32 last_full_charge_cap; -+ __le32 technology; -+ __le32 design_voltage; -+ __le32 design_cap_warn; -+ __le32 design_cap_low; -+ __le32 cycle_count; -+ __le32 measurement_accuracy; -+ __le32 max_sampling_time; -+ __le32 min_sampling_time; -+ __le32 max_avg_interval; -+ __le32 min_avg_interval; -+ __le32 bat_cap_granularity_1; -+ __le32 bat_cap_granularity_2; -+ u8 model[21]; -+ u8 serial[11]; -+ u8 type[5]; -+ u8 oem_info[21]; -+} __packed; -+ -+/* Equivalent to data returned in ACPI _BST method */ -+struct spwr_bst { -+ __le32 state; -+ __le32 present_rate; -+ __le32 remaining_cap; -+ __le32 present_voltage; -+} __packed; -+ -+/* DPTF event payload */ -+struct spwr_event_dptf { -+ __le32 pmax; -+ __le32 _1; /* currently unknown */ -+ __le32 _2; /* currently unknown */ -+} __packed; -+ -+ -+/* Get battery status (_STA) */ -+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_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_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_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_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_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_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_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_CL_W(ssam_bat_set_chgi, __le32, { -+ .target_category = SSAM_SSH_TC_BAT, -+ .command_id = 0x0e, -+}); -+ -+ -+/* -+ * Common Power-Subsystem Interface. -+ */ -+ -+struct spwr_psy_properties { -+ const char *name; -+ struct ssam_event_registry registry; -+}; -+ -+struct spwr_battery_device { -+ struct ssam_device *sdev; -+ -+ char name[32]; -+ struct power_supply *psy; -+ struct power_supply_desc psy_desc; -+ -+ struct delayed_work update_work; -+ -+ struct ssam_event_notifier notif; -+ -+ struct mutex lock; -+ unsigned long timestamp; -+ -+ __le32 sta; -+ struct spwr_bix bix; -+ struct spwr_bst bst; -+ u32 alarm; -+}; -+ -+struct spwr_ac_device { -+ struct ssam_device *sdev; -+ -+ char name[32]; -+ struct power_supply *psy; -+ struct power_supply_desc psy_desc; -+ -+ struct ssam_event_notifier notif; -+ -+ struct mutex lock; -+ -+ __le32 state; -+}; -+ -+static enum power_supply_property spwr_ac_props[] = { -+ POWER_SUPPLY_PROP_ONLINE, -+}; -+ -+static enum power_supply_property spwr_battery_props_chg[] = { -+ POWER_SUPPLY_PROP_STATUS, -+ POWER_SUPPLY_PROP_PRESENT, -+ POWER_SUPPLY_PROP_TECHNOLOGY, -+ POWER_SUPPLY_PROP_CYCLE_COUNT, -+ POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN, -+ POWER_SUPPLY_PROP_VOLTAGE_NOW, -+ POWER_SUPPLY_PROP_CURRENT_NOW, -+ POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, -+ POWER_SUPPLY_PROP_CHARGE_FULL, -+ POWER_SUPPLY_PROP_CHARGE_NOW, -+ POWER_SUPPLY_PROP_CAPACITY, -+ POWER_SUPPLY_PROP_CAPACITY_LEVEL, -+ POWER_SUPPLY_PROP_MODEL_NAME, -+ POWER_SUPPLY_PROP_MANUFACTURER, -+ POWER_SUPPLY_PROP_SERIAL_NUMBER, -+}; -+ -+static enum power_supply_property spwr_battery_props_eng[] = { -+ POWER_SUPPLY_PROP_STATUS, -+ POWER_SUPPLY_PROP_PRESENT, -+ POWER_SUPPLY_PROP_TECHNOLOGY, -+ POWER_SUPPLY_PROP_CYCLE_COUNT, -+ POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN, -+ POWER_SUPPLY_PROP_VOLTAGE_NOW, -+ POWER_SUPPLY_PROP_POWER_NOW, -+ POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN, -+ POWER_SUPPLY_PROP_ENERGY_FULL, -+ POWER_SUPPLY_PROP_ENERGY_NOW, -+ POWER_SUPPLY_PROP_CAPACITY, -+ POWER_SUPPLY_PROP_CAPACITY_LEVEL, -+ POWER_SUPPLY_PROP_MODEL_NAME, -+ POWER_SUPPLY_PROP_MANUFACTURER, -+ POWER_SUPPLY_PROP_SERIAL_NUMBER, -+}; -+ -+ -+static int spwr_battery_register(struct spwr_battery_device *bat, -+ struct ssam_device *sdev, -+ struct ssam_event_registry registry); -+ -+static void spwr_battery_unregister(struct spwr_battery_device *bat); -+ -+ -+static inline bool spwr_battery_present(struct spwr_battery_device *bat) -+{ -+ return le32_to_cpu(bat->sta) & SAM_BATTERY_STA_PRESENT; -+} -+ -+ -+static inline int spwr_battery_load_sta(struct spwr_battery_device *bat) -+{ -+ return ssam_bat_get_sta(bat->sdev, &bat->sta); -+} -+ -+static inline int spwr_battery_load_bix(struct spwr_battery_device *bat) -+{ -+ if (!spwr_battery_present(bat)) -+ return 0; -+ -+ return ssam_bat_get_bix(bat->sdev, &bat->bix); -+} -+ -+static inline int spwr_battery_load_bst(struct spwr_battery_device *bat) -+{ -+ if (!spwr_battery_present(bat)) -+ return 0; -+ -+ return ssam_bat_get_bst(bat->sdev, &bat->bst); -+} -+ -+ -+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->sdev, &alarm); -+} -+ -+static inline int spwr_battery_set_alarm(struct spwr_battery_device *bat, -+ u32 value) -+{ -+ int status; -+ -+ mutex_lock(&bat->lock); -+ status = spwr_battery_set_alarm_unlocked(bat, value); -+ mutex_unlock(&bat->lock); -+ -+ return status; -+} -+ -+static inline int spwr_battery_update_bst_unlocked( -+ struct spwr_battery_device *bat, bool cached) -+{ -+ 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; -+ -+ status = spwr_battery_load_sta(bat); -+ if (status) -+ return status; -+ -+ status = spwr_battery_load_bst(bat); -+ if (status) -+ return status; -+ -+ bat->timestamp = jiffies; -+ return 0; -+} -+ -+static int spwr_battery_update_bst(struct spwr_battery_device *bat, bool cached) -+{ -+ int status; -+ -+ mutex_lock(&bat->lock); -+ status = spwr_battery_update_bst_unlocked(bat, cached); -+ mutex_unlock(&bat->lock); -+ -+ return status; -+} -+ -+static inline int spwr_battery_update_bix_unlocked(struct spwr_battery_device *bat) -+{ -+ int status; -+ -+ status = spwr_battery_load_sta(bat); -+ if (status) -+ return status; -+ -+ status = spwr_battery_load_bix(bat); -+ if (status) -+ return status; -+ -+ status = spwr_battery_load_bst(bat); -+ if (status) -+ return status; -+ -+ bat->timestamp = jiffies; -+ return 0; -+} -+ -+static int spwr_battery_update_bix(struct spwr_battery_device *bat) -+{ -+ int status; -+ -+ mutex_lock(&bat->lock); -+ status = spwr_battery_update_bix_unlocked(bat); -+ mutex_unlock(&bat->lock); -+ -+ return status; -+} -+ -+static inline int spwr_ac_update_unlocked(struct spwr_ac_device *ac) -+{ -+ 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) -+{ -+ int status; -+ -+ mutex_lock(&ac->lock); -+ status = spwr_ac_update_unlocked(ac); -+ mutex_unlock(&ac->lock); -+ -+ return status; -+} -+ -+ -+static int spwr_battery_recheck(struct spwr_battery_device *bat) -+{ -+ bool present = spwr_battery_present(bat); -+ u32 unit = get_unaligned_le32(&bat->bix.power_unit); -+ int status; -+ -+ status = spwr_battery_update_bix(bat); -+ if (status) -+ return status; -+ -+ // if battery has been attached, (re-)initialize alarm -+ if (!present && spwr_battery_present(bat)) { -+ u32 cap_warn = get_unaligned_le32(&bat->bix.design_cap_warn); -+ status = spwr_battery_set_alarm(bat, cap_warn); -+ if (status) -+ return status; -+ } -+ -+ // 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->sdev, -+ bat->notif.event.reg); -+ } -+ -+ return status; -+} -+ -+ -+static inline int spwr_notify_bix(struct spwr_battery_device *bat) -+{ -+ int status; -+ -+ status = spwr_battery_recheck(bat); -+ if (!status) -+ power_supply_changed(bat->psy); -+ -+ return status; -+} -+ -+static inline int spwr_notify_bst(struct spwr_battery_device *bat) -+{ -+ int status; -+ -+ status = spwr_battery_update_bst(bat, false); -+ if (!status) -+ power_supply_changed(bat->psy); -+ -+ return status; -+} -+ -+static inline int spwr_notify_adapter_bat(struct spwr_battery_device *bat) -+{ -+ u32 last_full_cap = get_unaligned_le32(&bat->bix.last_full_charge_cap); -+ u32 remaining_cap = get_unaligned_le32(&bat->bst.remaining_cap); -+ -+ /* -+ * Handle battery update quirk: -+ * When the battery is fully charged and the adapter is plugged in or -+ * removed, the EC does not send a separate event for the state -+ * (charging/discharging) change. Furthermore it may take some time until -+ * the state is updated on the battery. Schedule an update to solve this. -+ */ -+ -+ if (remaining_cap >= last_full_cap) -+ schedule_delayed_work(&bat->update_work, SPWR_AC_BAT_UPDATE_DELAY); -+ -+ return 0; -+} -+ -+static inline int spwr_notify_adapter_ac(struct spwr_ac_device *ac) -+{ -+ int status; -+ -+ status = spwr_ac_update(ac); -+ if (status > 0) -+ power_supply_changed(ac->psy); -+ -+ return status >= 0 ? 0 : status; -+} -+ -+static u32 spwr_notify_bat(struct ssam_event_notifier *nf, -+ const struct ssam_event *event) -+{ -+ struct spwr_battery_device *bat; -+ int status; -+ -+ bat = container_of(nf, struct spwr_battery_device, notif); -+ -+ dev_dbg(&bat->sdev->dev, "power event (cid = 0x%02x, iid = %d, tid = %d)\n", -+ event->command_id, event->instance_id, event->target_id); -+ -+ // handled here, needs to be handled for all targets/instances -+ if (event->command_id == SAM_EVENT_PWR_CID_ADAPTER) { -+ status = spwr_notify_adapter_bat(bat); -+ return ssam_notifier_from_errno(status) | SSAM_NOTIF_HANDLED; -+ } -+ -+ if (bat->sdev->uid.target != event->target_id) -+ return 0; -+ -+ if (bat->sdev->uid.instance != event->instance_id) -+ return 0; -+ -+ switch (event->command_id) { -+ case SAM_EVENT_PWR_CID_BIX: -+ status = spwr_notify_bix(bat); -+ break; -+ -+ case SAM_EVENT_PWR_CID_BST: -+ status = spwr_notify_bst(bat); -+ break; -+ -+ default: -+ return 0; -+ } -+ -+ return ssam_notifier_from_errno(status) | SSAM_NOTIF_HANDLED; -+} -+ -+static u32 spwr_notify_ac(struct ssam_event_notifier *nf, -+ const struct ssam_event *event) -+{ -+ struct spwr_ac_device *ac; -+ int status; -+ -+ ac = container_of(nf, struct spwr_ac_device, notif); -+ -+ dev_dbg(&ac->sdev->dev, "power event (cid = 0x%02x, iid = %d, tid = %d)\n", -+ event->command_id, event->instance_id, event->target_id); -+ -+ /* -+ * Allow events of all targets/instances here. Global adapter status -+ * seems to be handled via target=1 and instance=1, but events are -+ * reported on all targets/instances in use. -+ * -+ * While it should be enough to just listen on 1/1, listen everywhere to -+ * make sure we don't miss anything. -+ */ -+ -+ switch (event->command_id) { -+ case SAM_EVENT_PWR_CID_ADAPTER: -+ status = spwr_notify_adapter_ac(ac); -+ return ssam_notifier_from_errno(status) | SSAM_NOTIF_HANDLED; -+ -+ default: -+ return 0; -+ } -+} -+ -+static void spwr_battery_update_bst_workfn(struct work_struct *work) -+{ -+ struct delayed_work *dwork = to_delayed_work(work); -+ struct spwr_battery_device *bat; -+ int status; -+ -+ bat = container_of(dwork, struct spwr_battery_device, update_work); -+ -+ status = spwr_battery_update_bst(bat, false); -+ if (!status) -+ power_supply_changed(bat->psy); -+ -+ if (status) { -+ dev_err(&bat->sdev->dev, "failed to update battery state: %d\n", -+ status); -+ } -+} -+ -+ -+static inline int spwr_battery_prop_status(struct spwr_battery_device *bat) -+{ -+ u32 state = get_unaligned_le32(&bat->bst.state); -+ u32 last_full_cap = get_unaligned_le32(&bat->bix.last_full_charge_cap); -+ u32 remaining_cap = get_unaligned_le32(&bat->bst.remaining_cap); -+ u32 present_rate = get_unaligned_le32(&bat->bst.present_rate); -+ -+ if (state & SAM_BATTERY_STATE_DISCHARGING) -+ return POWER_SUPPLY_STATUS_DISCHARGING; -+ -+ if (state & SAM_BATTERY_STATE_CHARGING) -+ return POWER_SUPPLY_STATUS_CHARGING; -+ -+ if (last_full_cap == remaining_cap) -+ return POWER_SUPPLY_STATUS_FULL; -+ -+ if (present_rate == 0) -+ return POWER_SUPPLY_STATUS_NOT_CHARGING; -+ -+ return POWER_SUPPLY_STATUS_UNKNOWN; -+} -+ -+static inline int spwr_battery_prop_technology(struct spwr_battery_device *bat) -+{ -+ if (!strcasecmp("NiCd", bat->bix.type)) -+ return POWER_SUPPLY_TECHNOLOGY_NiCd; -+ -+ if (!strcasecmp("NiMH", bat->bix.type)) -+ return POWER_SUPPLY_TECHNOLOGY_NiMH; -+ -+ if (!strcasecmp("LION", bat->bix.type)) -+ return POWER_SUPPLY_TECHNOLOGY_LION; -+ -+ if (!strncasecmp("LI-ION", bat->bix.type, 6)) -+ return POWER_SUPPLY_TECHNOLOGY_LION; -+ -+ if (!strcasecmp("LiP", bat->bix.type)) -+ return POWER_SUPPLY_TECHNOLOGY_LIPO; -+ -+ return POWER_SUPPLY_TECHNOLOGY_UNKNOWN; -+} -+ -+static inline int spwr_battery_prop_capacity(struct spwr_battery_device *bat) -+{ -+ u32 last_full_cap = get_unaligned_le32(&bat->bix.last_full_charge_cap); -+ u32 remaining_cap = get_unaligned_le32(&bat->bst.remaining_cap); -+ -+ if (remaining_cap && last_full_cap) -+ return remaining_cap * 100 / last_full_cap; -+ else -+ return 0; -+} -+ -+static inline int spwr_battery_prop_capacity_level(struct spwr_battery_device *bat) -+{ -+ u32 state = get_unaligned_le32(&bat->bst.state); -+ u32 last_full_cap = get_unaligned_le32(&bat->bix.last_full_charge_cap); -+ u32 remaining_cap = get_unaligned_le32(&bat->bst.remaining_cap); -+ -+ if (state & SAM_BATTERY_STATE_CRITICAL) -+ return POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL; -+ -+ if (remaining_cap >= last_full_cap) -+ return POWER_SUPPLY_CAPACITY_LEVEL_FULL; -+ -+ if (remaining_cap <= bat->alarm) -+ return POWER_SUPPLY_CAPACITY_LEVEL_LOW; -+ -+ return POWER_SUPPLY_CAPACITY_LEVEL_NORMAL; -+} -+ -+static int spwr_ac_get_property(struct power_supply *psy, -+ enum power_supply_property psp, -+ union power_supply_propval *val) -+{ -+ struct spwr_ac_device *ac = power_supply_get_drvdata(psy); -+ int status; -+ -+ mutex_lock(&ac->lock); -+ -+ status = spwr_ac_update_unlocked(ac); -+ if (status) -+ goto out; -+ -+ switch (psp) { -+ case POWER_SUPPLY_PROP_ONLINE: -+ val->intval = le32_to_cpu(ac->state) == 1; -+ break; -+ -+ default: -+ status = -EINVAL; -+ goto out; -+ } -+ -+out: -+ mutex_unlock(&ac->lock); -+ return status; -+} -+ -+static int spwr_battery_get_property(struct power_supply *psy, -+ enum power_supply_property psp, -+ union power_supply_propval *val) -+{ -+ struct spwr_battery_device *bat = power_supply_get_drvdata(psy); -+ int status; -+ -+ mutex_lock(&bat->lock); -+ -+ status = spwr_battery_update_bst_unlocked(bat, true); -+ if (status) -+ goto out; -+ -+ // abort if battery is not present -+ if (!spwr_battery_present(bat) && psp != POWER_SUPPLY_PROP_PRESENT) { -+ status = -ENODEV; -+ goto out; -+ } -+ -+ switch (psp) { -+ case POWER_SUPPLY_PROP_STATUS: -+ val->intval = spwr_battery_prop_status(bat); -+ break; -+ -+ case POWER_SUPPLY_PROP_PRESENT: -+ val->intval = spwr_battery_present(bat); -+ break; -+ -+ case POWER_SUPPLY_PROP_TECHNOLOGY: -+ val->intval = spwr_battery_prop_technology(bat); -+ break; -+ -+ case POWER_SUPPLY_PROP_CYCLE_COUNT: -+ val->intval = get_unaligned_le32(&bat->bix.cycle_count); -+ break; -+ -+ case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN: -+ val->intval = get_unaligned_le32(&bat->bix.design_voltage) -+ * 1000; -+ break; -+ -+ case POWER_SUPPLY_PROP_VOLTAGE_NOW: -+ val->intval = get_unaligned_le32(&bat->bst.present_voltage) -+ * 1000; -+ break; -+ -+ case POWER_SUPPLY_PROP_CURRENT_NOW: -+ case POWER_SUPPLY_PROP_POWER_NOW: -+ val->intval = get_unaligned_le32(&bat->bst.present_rate) * 1000; -+ break; -+ -+ case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN: -+ case POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN: -+ val->intval = get_unaligned_le32(&bat->bix.design_cap) * 1000; -+ break; -+ -+ case POWER_SUPPLY_PROP_CHARGE_FULL: -+ case POWER_SUPPLY_PROP_ENERGY_FULL: -+ val->intval = get_unaligned_le32(&bat->bix.last_full_charge_cap) -+ * 1000; -+ break; -+ -+ case POWER_SUPPLY_PROP_CHARGE_NOW: -+ case POWER_SUPPLY_PROP_ENERGY_NOW: -+ val->intval = get_unaligned_le32(&bat->bst.remaining_cap) -+ * 1000; -+ break; -+ -+ case POWER_SUPPLY_PROP_CAPACITY: -+ val->intval = spwr_battery_prop_capacity(bat); -+ break; -+ -+ case POWER_SUPPLY_PROP_CAPACITY_LEVEL: -+ val->intval = spwr_battery_prop_capacity_level(bat); -+ break; -+ -+ case POWER_SUPPLY_PROP_MODEL_NAME: -+ val->strval = bat->bix.model; -+ break; -+ -+ case POWER_SUPPLY_PROP_MANUFACTURER: -+ val->strval = bat->bix.oem_info; -+ break; -+ -+ case POWER_SUPPLY_PROP_SERIAL_NUMBER: -+ val->strval = bat->bix.serial; -+ break; -+ -+ default: -+ status = -EINVAL; -+ goto out; -+ } -+ -+out: -+ mutex_unlock(&bat->lock); -+ return status; -+} -+ -+ -+static ssize_t spwr_battery_alarm_show(struct device *dev, -+ struct device_attribute *attr, -+ char *buf) -+{ -+ struct power_supply *psy = dev_get_drvdata(dev); -+ struct spwr_battery_device *bat = power_supply_get_drvdata(psy); -+ -+ return sprintf(buf, "%d\n", bat->alarm * 1000); -+} -+ -+static ssize_t spwr_battery_alarm_store(struct device *dev, -+ struct device_attribute *attr, -+ const char *buf, size_t count) -+{ -+ struct power_supply *psy = dev_get_drvdata(dev); -+ struct spwr_battery_device *bat = power_supply_get_drvdata(psy); -+ unsigned long value; -+ int status; -+ -+ status = kstrtoul(buf, 0, &value); -+ if (status) -+ return status; -+ -+ if (!spwr_battery_present(bat)) -+ return -ENODEV; -+ -+ status = spwr_battery_set_alarm(bat, value / 1000); -+ if (status) -+ return status; -+ -+ return count; -+} -+ -+static const struct device_attribute alarm_attr = { -+ .attr = {.name = "alarm", .mode = 0644}, -+ .show = spwr_battery_alarm_show, -+ .store = spwr_battery_alarm_store, -+}; -+ -+ -+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 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(sdev, &sta); -+ if (status) -+ return status; -+ -+ if ((le32_to_cpu(sta) & SAM_BATTERY_STA_OK) != SAM_BATTERY_STA_OK) -+ return -ENODEV; -+ -+ psy_cfg.drv_data = ac; -+ -+ ac->sdev = sdev; -+ mutex_init(&ac->lock); -+ -+ 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->sdev->dev, &ac->psy_desc, &psy_cfg); -+ if (IS_ERR(ac->psy)) { -+ status = PTR_ERR(ac->psy); -+ goto err_psy; -+ } -+ -+ ac->notif.base.priority = 1; -+ ac->notif.base.fn = spwr_notify_ac; -+ ac->notif.event.reg = registry; -+ ac->notif.event.id.target_category = sdev->uid.category; -+ ac->notif.event.id.instance = 0; -+ ac->notif.event.mask = SSAM_EVENT_MASK_NONE; -+ ac->notif.event.flags = SSAM_EVENT_SEQUENCED; -+ -+ status = ssam_notifier_register(sdev->ctrl, &ac->notif); -+ if (status) -+ goto err_notif; -+ -+ return 0; -+ -+err_notif: -+ power_supply_unregister(ac->psy); -+err_psy: -+ mutex_destroy(&ac->lock); -+ return status; -+} -+ -+static int spwr_ac_unregister(struct spwr_ac_device *ac) -+{ -+ 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 ssam_device *sdev, -+ struct ssam_event_registry registry) -+{ -+ struct power_supply_config psy_cfg = {}; -+ __le32 sta; -+ int status; -+ -+ bat->sdev = sdev; -+ -+ // make sure the device is there and functioning properly -+ status = ssam_bat_get_sta(sdev, &sta); -+ if (status) -+ return status; -+ -+ if ((le32_to_cpu(sta) & SAM_BATTERY_STA_OK) != SAM_BATTERY_STA_OK) -+ return -ENODEV; -+ -+ status = spwr_battery_update_bix_unlocked(bat); -+ if (status) -+ return status; -+ -+ if (spwr_battery_present(bat)) { -+ u32 cap_warn = get_unaligned_le32(&bat->bix.design_cap_warn); -+ status = spwr_battery_set_alarm_unlocked(bat, cap_warn); -+ if (status) -+ return status; -+ } -+ -+ bat->psy_desc.name = bat->name; -+ bat->psy_desc.type = POWER_SUPPLY_TYPE_BATTERY; -+ -+ if (get_unaligned_le32(&bat->bix.power_unit) == SAM_BATTERY_POWER_UNIT_MA) { -+ bat->psy_desc.properties = spwr_battery_props_chg; -+ bat->psy_desc.num_properties = ARRAY_SIZE(spwr_battery_props_chg); -+ } else { -+ bat->psy_desc.properties = spwr_battery_props_eng; -+ bat->psy_desc.num_properties = ARRAY_SIZE(spwr_battery_props_eng); -+ } -+ -+ bat->psy_desc.get_property = spwr_battery_get_property; -+ -+ mutex_init(&bat->lock); -+ psy_cfg.drv_data = bat; -+ -+ INIT_DELAYED_WORK(&bat->update_work, spwr_battery_update_bst_workfn); -+ -+ 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; -+ } -+ -+ bat->notif.base.priority = 1; -+ bat->notif.base.fn = spwr_notify_bat; -+ bat->notif.event.reg = registry; -+ bat->notif.event.id.target_category = sdev->uid.category; -+ bat->notif.event.id.instance = 0; -+ bat->notif.event.mask = SSAM_EVENT_MASK_NONE; -+ bat->notif.event.flags = SSAM_EVENT_SEQUENCED; -+ -+ status = ssam_notifier_register(sdev->ctrl, &bat->notif); -+ if (status) -+ goto err_notif; -+ -+ status = device_create_file(&bat->psy->dev, &alarm_attr); -+ if (status) -+ goto err_file; -+ -+ return 0; -+ -+err_file: -+ ssam_notifier_unregister(sdev->ctrl, &bat->notif); -+err_notif: -+ power_supply_unregister(bat->psy); -+err_psy: -+ mutex_destroy(&bat->lock); -+ return status; -+} -+ -+static void spwr_battery_unregister(struct spwr_battery_device *bat) -+{ -+ 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); -+ mutex_destroy(&bat->lock); -+} -+ -+ -+/* -+ * Battery Driver. -+ */ -+ -+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); -+} -+SIMPLE_DEV_PM_OPS(surface_sam_sid_battery_pm, -+ NULL, surface_sam_sid_battery_resume); -+ -+static int surface_sam_sid_battery_probe(struct ssam_device *sdev) -+{ -+ const struct spwr_psy_properties *p; -+ struct spwr_battery_device *bat; -+ int status; -+ -+ p = ssam_device_get_match_data(sdev); -+ if (!p) -+ return -ENODEV; -+ -+ bat = devm_kzalloc(&sdev->dev, sizeof(*bat), GFP_KERNEL); -+ if (!bat) -+ return -ENOMEM; -+ -+ spwr_battery_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 void surface_sam_sid_battery_remove(struct ssam_device *sdev) -+{ -+ struct spwr_battery_device *bat; -+ -+ bat = ssam_device_get_drvdata(sdev); -+ spwr_battery_unregister(bat); -+ -+ ssam_device_set_drvdata(sdev, NULL); -+} -+ -+static const struct spwr_psy_properties spwr_psy_props_bat1 = { -+ .name = "BAT1", -+ .registry = SSAM_EVENT_REGISTRY_SAM, -+}; -+ -+static const struct spwr_psy_properties spwr_psy_props_bat2_sb3 = { -+ .name = "BAT2", -+ .registry = SSAM_EVENT_REGISTRY_KIP, -+}; -+ -+static const struct ssam_device_id surface_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, -+ .probe_type = PROBE_PREFER_ASYNCHRONOUS, -+ }, -+}; -+ -+ -+/* -+ * AC Driver. -+ */ -+ -+// 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; -+ int status; -+ -+ p = ssam_device_get_match_data(sdev); -+ if (!p) -+ return -ENODEV; -+ -+ ac = devm_kzalloc(&sdev->dev, sizeof(*ac), GFP_KERNEL); -+ if (!ac) -+ return -ENOMEM; -+ -+ spwr_ac_set_name(ac, p->name); -+ ssam_device_set_drvdata(sdev, ac); -+ -+ status = spwr_ac_register(ac, sdev, p->registry); -+ if (status) -+ ssam_device_set_drvdata(sdev, NULL); -+ -+ return status; -+} -+ -+static void surface_sam_sid_ac_remove(struct ssam_device *sdev) -+{ -+ struct spwr_ac_device *ac = ssam_device_get_drvdata(sdev); -+ -+ spwr_ac_unregister(ac); -+ ssam_device_set_drvdata(sdev, NULL); -+} -+ -+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, -+ }, -+}; -+ -+ -+static int __init surface_sam_sid_power_init(void) -+{ -+ int status; -+ -+ status = ssam_device_driver_register(&surface_sam_sid_battery); -+ if (status) -+ return status; -+ -+ status = ssam_device_driver_register(&surface_sam_sid_ac); -+ if (status) { -+ ssam_device_driver_unregister(&surface_sam_sid_battery); -+ return status; -+ } -+ -+ return 0; -+} -+ -+static void __exit surface_sam_sid_power_exit(void) -+{ -+ ssam_device_driver_unregister(&surface_sam_sid_battery); -+ ssam_device_driver_unregister(&surface_sam_sid_ac); -+} -+ -+module_init(surface_sam_sid_power_init); -+module_exit(surface_sam_sid_power_exit); -+ -+MODULE_AUTHOR("Maximilian Luz "); -+MODULE_DESCRIPTION("Surface Battery/AC Driver for 7th Generation Surface Devices"); -+MODULE_LICENSE("GPL"); -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 000000000000..e995c03f4b73 ---- /dev/null -+++ b/drivers/misc/surface_sam/clients/surface_sam_sid_vhf.c -@@ -0,0 +1,498 @@ -+// SPDX-License-Identifier: GPL-2.0-or-later -+/* -+ * Microsofs Surface HID (VHF) driver for HID input events via SAM. -+ * Used for keyboard input events on the 7th generation Surface Laptops. ++ * Provides support for the legacy HID keyboard device found on the Surface ++ * Laptop 1 and 2. + */ + +#include +#include +#include ++#include ++#include +#include +#include + -+#include -+ -+ -+#define SID_VHF_INPUT_NAME "Microsoft Surface HID" -+ -+#define SAM_EVENT_SID_VHF_TC 0x15 -+ -+#define VHF_HID_STARTED 0 -+ -+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; -+ unsigned long state; -+}; -+ -+ -+static int sid_vhf_hid_start(struct hid_device *hid) -+{ -+ hid_dbg(hid, "%s\n", __func__); -+ return 0; -+} -+ -+static void sid_vhf_hid_stop(struct hid_device *hid) -+{ -+ hid_dbg(hid, "%s\n", __func__); -+} -+ -+static int sid_vhf_hid_open(struct hid_device *hid) -+{ -+ struct sid_vhf *vhf = dev_get_drvdata(hid->dev.parent); -+ -+ hid_dbg(hid, "%s\n", __func__); -+ -+ set_bit(VHF_HID_STARTED, &vhf->state); -+ return 0; -+} -+ -+static void sid_vhf_hid_close(struct hid_device *hid) -+{ -+ -+ struct sid_vhf *vhf = dev_get_drvdata(hid->dev.parent); -+ -+ hid_dbg(hid, "%s\n", __func__); -+ -+ clear_bit(VHF_HID_STARTED, &vhf->state); -+} -+ -+struct surface_sam_sid_vhf_meta_rqst { -+ u8 id; -+ u32 offset; -+ u32 length; // buffer limit on send, length of data received on receive -+ u8 end; // 0x01 if end was reached -+} __packed; -+ -+struct vhf_device_metadata_info { -+ u8 len; -+ u8 _2; -+ u8 _3; -+ u8 _4; -+ u8 _5; -+ u8 _6; -+ u8 _7; -+ u16 hid_len; // hid descriptor length -+} __packed; -+ -+struct vhf_device_metadata { -+ u32 len; -+ u16 vendor_id; -+ u16 product_id; -+ u8 _1[24]; -+} __packed; -+ -+union vhf_buffer_data { -+ struct vhf_device_metadata_info info; -+ u8 pld[0x76]; -+ struct vhf_device_metadata meta; -+}; -+ -+struct surface_sam_sid_vhf_meta_resp { -+ struct surface_sam_sid_vhf_meta_rqst rqst; -+ union vhf_buffer_data data; -+} __packed; -+ -+ -+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; -+ struct ssam_response rsp; -+ int status; -+ -+ data.rqst.id = 2; -+ data.rqst.offset = 0; -+ data.rqst.length = 0x76; -+ data.rqst.end = 0; -+ -+ rqst.target_category = sdev->uid.category; -+ rqst.target_id = sdev->uid.target; -+ rqst.command_id = 0x04; -+ rqst.instance_id = sdev->uid.instance; -+ rqst.flags = SSAM_REQUEST_HAS_RESPONSE; -+ rqst.length = sizeof(struct surface_sam_sid_vhf_meta_rqst); -+ rqst.payload = (u8 *)&data.rqst; -+ -+ rsp.capacity = sizeof(struct surface_sam_sid_vhf_meta_resp); -+ rsp.length = 0; -+ rsp.pointer = (u8 *)&data; -+ -+ status = ssam_request_sync(sdev->ctrl, &rqst, &rsp); -+ if (status) -+ return status; -+ -+ *meta = data.data.meta; -+ -+ return 0; -+} -+ -+static int vhf_get_hid_descriptor(struct ssam_device *sdev, u8 **desc, int *size) -+{ -+ struct surface_sam_sid_vhf_meta_resp data = {}; -+ struct ssam_request rqst; -+ struct ssam_response rsp; -+ int status, len; -+ u8 *buf; -+ -+ data.rqst.id = 0; -+ data.rqst.offset = 0; -+ data.rqst.length = 0x76; -+ data.rqst.end = 0; -+ -+ rqst.target_category = sdev->uid.category; -+ rqst.target_id = sdev->uid.target;; -+ rqst.command_id = 0x04; -+ rqst.instance_id = sdev->uid.instance; -+ rqst.flags = SSAM_REQUEST_HAS_RESPONSE; -+ rqst.length = sizeof(struct surface_sam_sid_vhf_meta_rqst); -+ rqst.payload = (u8 *)&data.rqst; -+ -+ rsp.capacity = sizeof(struct surface_sam_sid_vhf_meta_resp); -+ rsp.length = 0; -+ rsp.pointer = (u8 *)&data; -+ -+ // first fetch 00 to get the total length -+ status = ssam_request_sync(sdev->ctrl, &rqst, &rsp); -+ if (status) -+ return status; -+ -+ len = data.data.info.hid_len; -+ -+ // allocate a buffer for the descriptor -+ buf = kzalloc(len, GFP_KERNEL); -+ -+ // then, iterate and write into buffer, copying out bytes -+ data.rqst.id = 1; -+ data.rqst.offset = 0; -+ data.rqst.length = 0x76; -+ data.rqst.end = 0; -+ -+ while (!data.rqst.end && data.rqst.offset < len) { -+ status = ssam_request_sync(sdev->ctrl, &rqst, &rsp); -+ if (status) { -+ kfree(buf); -+ return status; -+ } -+ memcpy(buf + data.rqst.offset, data.data.pld, data.rqst.length); -+ -+ data.rqst.offset += data.rqst.length; -+ } -+ -+ *desc = buf; -+ *size = len; -+ -+ return 0; -+} -+ -+static int sid_vhf_hid_parse(struct hid_device *hid) -+{ -+ struct sid_vhf *vhf = dev_get_drvdata(hid->dev.parent); -+ int ret = 0, size; -+ u8 *buf; -+ -+ 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; -+ } -+ hid_dbg(hid, "HID descriptor of device:"); -+ print_hex_dump_debug("descriptor:", DUMP_PREFIX_OFFSET, 16, 1, buf, size, false); -+ -+ ret = hid_parse_report(hid, buf, size); -+ kfree(buf); -+ return ret; -+ -+} -+ -+static int sid_vhf_hid_raw_request(struct hid_device *hid, unsigned char -+ reportnum, u8 *buf, size_t len, unsigned char rtype, int -+ reqtype) -+{ -+ struct sid_vhf *vhf = dev_get_drvdata(hid->dev.parent); -+ struct ssam_request rqst; -+ struct ssam_response rsp; -+ int status; -+ u8 cid; -+ -+ hid_dbg(hid, "%s: reportnum=%#04x rtype=%i reqtype=%i\n", __func__, reportnum, rtype, reqtype); -+ print_hex_dump_debug("report:", DUMP_PREFIX_OFFSET, 16, 1, buf, len, false); -+ -+ // Byte 0 is the report number. Report data starts at byte 1. -+ buf[0] = reportnum; -+ -+ switch (rtype) { -+ case HID_OUTPUT_REPORT: -+ cid = 0x01; -+ break; -+ case HID_FEATURE_REPORT: -+ switch (reqtype) { -+ case HID_REQ_GET_REPORT: -+ // The EC doesn't respond to GET FEATURE for these touchpad reports -+ // we immediately discard to avoid waiting for a timeout. -+ if (reportnum == 6 || reportnum == 7 || reportnum == 8 || reportnum == 9 || reportnum == 0x0b) { -+ hid_dbg(hid, "%s: skipping get feature report for 0x%02x\n", __func__, reportnum); -+ return 0; -+ } -+ -+ cid = 0x02; -+ break; -+ case HID_REQ_SET_REPORT: -+ cid = 0x03; -+ break; -+ default: -+ hid_err(hid, "%s: unknown req type 0x%02x\n", __func__, rtype); -+ return -EIO; -+ } -+ break; -+ default: -+ hid_err(hid, "%s: unknown report type 0x%02x\n", __func__, reportnum); -+ return -EIO; -+ } -+ -+ rqst.target_category = vhf->sdev->uid.category; -+ rqst.target_id = vhf->sdev->uid.target; -+ 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; -+ rqst.payload = buf; -+ -+ rsp.capacity = len; -+ rsp.length = 0; -+ rsp.pointer = buf; -+ -+ hid_dbg(hid, "%s: sending to cid=%#04x snc=%#04x\n", __func__, cid, HID_REQ_GET_REPORT == reqtype); -+ -+ status = ssam_request_sync(vhf->sdev->ctrl, &rqst, &rsp); -+ hid_dbg(hid, "%s: status %i\n", __func__, status); -+ -+ if (status) -+ return status; -+ -+ if (rsp.length > 0) -+ print_hex_dump_debug("response:", DUMP_PREFIX_OFFSET, 16, 1, rsp.pointer, rsp.length, false); -+ -+ return rsp.length; -+} -+ -+static struct hid_ll_driver sid_vhf_hid_ll_driver = { -+ .start = sid_vhf_hid_start, -+ .stop = sid_vhf_hid_stop, -+ .open = sid_vhf_hid_open, -+ .close = sid_vhf_hid_close, -+ .parse = sid_vhf_hid_parse, -+ .raw_request = sid_vhf_hid_raw_request, -+}; -+ -+ -+static struct hid_device *sid_vhf_create_hid_device(struct ssam_device *sdev, struct vhf_device_metadata *meta) -+{ -+ struct hid_device *hid; -+ -+ hid = hid_allocate_device(); -+ if (IS_ERR(hid)) -+ return hid; -+ -+ hid->dev.parent = &sdev->dev; -+ -+ hid->bus = BUS_VIRTUAL; -+ hid->vendor = meta->vendor_id; -+ hid->product = meta->product_id; -+ -+ hid->ll_driver = &sid_vhf_hid_ll_driver; -+ -+ sprintf(hid->name, "%s", SID_VHF_INPUT_NAME); -+ -+ return hid; -+} -+ -+static u32 sid_vhf_event_handler(struct ssam_event_notifier *nf, const struct ssam_event *event) -+{ -+ struct sid_vhf *vhf = container_of(nf, struct sid_vhf, notif); -+ int status; -+ -+ if (event->command_id != 0x00 && event->command_id != 0x03 && event->command_id != 0x04) -+ return 0; -+ -+ // skip if HID hasn't started yet -+ if (!test_bit(VHF_HID_STARTED, &vhf->state)) -+ return SSAM_NOTIF_HANDLED; -+ -+ status = hid_input_report(vhf->hid, HID_INPUT_REPORT, (u8 *)&event->data[0], event->length, 0); -+ return ssam_notifier_from_errno(status) | SSAM_NOTIF_HANDLED; -+} -+ -+ -+#ifdef CONFIG_PM -+ -+static int surface_sam_sid_vhf_suspend(struct device *dev) -+{ -+ struct sid_vhf *vhf = dev_get_drvdata(dev); -+ -+ if (vhf->hid->driver && vhf->hid->driver->suspend) -+ return vhf->hid->driver->suspend(vhf->hid, PMSG_SUSPEND); -+ -+ return 0; -+} -+ -+static int surface_sam_sid_vhf_resume(struct device *dev) -+{ -+ struct sid_vhf *vhf = dev_get_drvdata(dev); -+ -+ if (vhf->hid->driver && vhf->hid->driver->resume) -+ return vhf->hid->driver->resume(vhf->hid); -+ -+ return 0; -+} -+ -+static int surface_sam_sid_vhf_freeze(struct device *dev) -+{ -+ struct sid_vhf *vhf = dev_get_drvdata(dev); -+ -+ if (vhf->hid->driver && vhf->hid->driver->suspend) -+ return vhf->hid->driver->suspend(vhf->hid, PMSG_FREEZE); -+ -+ return 0; -+} -+ -+static int surface_sam_sid_vhf_poweroff(struct device *dev) -+{ -+ struct sid_vhf *vhf = dev_get_drvdata(dev); -+ -+ if (vhf->hid->driver && vhf->hid->driver->suspend) -+ return vhf->hid->driver->suspend(vhf->hid, PMSG_HIBERNATE); -+ -+ return 0; -+} -+ -+static int surface_sam_sid_vhf_restore(struct device *dev) -+{ -+ struct sid_vhf *vhf = dev_get_drvdata(dev); -+ -+ if (vhf->hid->driver && vhf->hid->driver->reset_resume) -+ return vhf->hid->driver->reset_resume(vhf->hid); -+ -+ return 0; -+} -+ -+struct dev_pm_ops surface_sam_sid_vhf_pm_ops = { -+ .freeze = surface_sam_sid_vhf_freeze, -+ .thaw = surface_sam_sid_vhf_resume, -+ .suspend = surface_sam_sid_vhf_suspend, -+ .resume = surface_sam_sid_vhf_resume, -+ .poweroff = surface_sam_sid_vhf_poweroff, -+ .restore = surface_sam_sid_vhf_restore, -+}; -+ -+#else /* CONFIG_PM */ -+ -+struct dev_pm_ops surface_sam_sid_vhf_pm_ops = { }; -+ -+#endif /* CONFIG_PM */ -+ -+ -+static int surface_sam_sid_vhf_probe(struct ssam_device *sdev) -+{ -+ const struct sid_vhf_properties *p; -+ struct sid_vhf *vhf; -+ struct vhf_device_metadata meta = {}; -+ struct hid_device *hid; -+ int status; -+ -+ p = ssam_device_get_match_data(sdev); -+ if (!p) -+ return -ENODEV; -+ -+ vhf = kzalloc(sizeof(*vhf), GFP_KERNEL); -+ if (!vhf) -+ return -ENOMEM; -+ -+ status = vhf_get_metadata(sdev, &meta); -+ if (status) -+ goto err_create_hid; -+ -+ hid = sid_vhf_create_hid_device(sdev, &meta); -+ if (IS_ERR(hid)) { -+ status = PTR_ERR(hid); -+ goto err_create_hid; -+ } -+ -+ 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 = sdev->uid.category; -+ vhf->notif.event.id.instance = sdev->uid.instance; -+ vhf->notif.event.mask = SSAM_EVENT_MASK_STRICT; -+ vhf->notif.event.flags = 0; -+ -+ ssam_device_set_drvdata(sdev, vhf); -+ -+ status = ssam_notifier_register(sdev->ctrl, &vhf->notif); -+ if (status) -+ goto err_notif; -+ -+ status = hid_add_device(hid); -+ if (status) -+ goto err_add_hid; -+ -+ return 0; -+ -+err_add_hid: -+ ssam_notifier_unregister(sdev->ctrl, &vhf->notif); -+err_notif: -+ hid_destroy_device(hid); -+ ssam_device_set_drvdata(sdev, NULL); -+err_create_hid: -+ kfree(vhf); -+ return status; -+} -+ -+static void surface_sam_sid_vhf_remove(struct ssam_device *sdev) -+{ -+ struct sid_vhf *vhf = ssam_device_get_drvdata(sdev); -+ -+ ssam_notifier_unregister(sdev->ctrl, &vhf->notif); -+ hid_destroy_device(vhf->hid); -+ kfree(vhf); -+ -+ ssam_device_set_drvdata(sdev, NULL); -+} -+ -+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_TID, 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", -+ .pm = &surface_sam_sid_vhf_pm_ops, -+ .probe_type = PROBE_PREFER_ASYNCHRONOUS, -+ }, -+}; -+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"); -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 000000000000..8f24e7a7bfea ---- /dev/null -+++ b/drivers/misc/surface_sam/clients/surface_sam_vhf.c -@@ -0,0 +1,334 @@ -+// 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. -+ */ -+ -+#include -+#include -+#include -+#include -+#include -+ -+#include ++#include + + +#define USB_VENDOR_ID_MICROSOFT 0x045e @@ -7737,15 +7634,13 @@ index 000000000000..8f24e7a7bfea + if (status) + return status == -ENXIO ? -EPROBE_DEFER : status; + -+ drvdata = kzalloc(sizeof(struct vhf_drvdata), GFP_KERNEL); ++ drvdata = devm_kzalloc(&pdev->dev, sizeof(*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; -+ } ++ if (IS_ERR(hid)) ++ return PTR_ERR(hid); + + status = hid_add_device(hid); + if (status) @@ -7773,9 +7668,6 @@ index 000000000000..8f24e7a7bfea + +err_add_hid: + hid_destroy_device(hid); -+ platform_set_drvdata(pdev, NULL); -+err_probe_hid: -+ kfree(drvdata); + return status; +} + @@ -7785,9 +7677,7 @@ index 000000000000..8f24e7a7bfea + + ssam_notifier_unregister(drvdata->ctrl, &drvdata->notif); + hid_destroy_device(drvdata->hid); -+ kfree(drvdata); + -+ platform_set_drvdata(pdev, NULL); + return 0; +} + @@ -7802,7 +7692,7 @@ index 000000000000..8f24e7a7bfea + .probe = surface_sam_vhf_probe, + .remove = surface_sam_vhf_remove, + .driver = { -+ .name = "surface_sam_vhf", ++ .name = "surface_keyboard", + .acpi_match_table = surface_sam_vhf_match, + .pm = &surface_sam_vhf_pm_ops, + .probe_type = PROBE_PREFER_ASYNCHRONOUS, @@ -7811,14 +7701,214 @@ index 000000000000..8f24e7a7bfea +module_platform_driver(surface_sam_vhf); + +MODULE_AUTHOR("Maximilian Luz "); -+MODULE_DESCRIPTION("Virtual HID Framework Driver for 5th Generation Surface Devices"); ++MODULE_DESCRIPTION("Legacy HID keyboard driver for Surface System Aggregator Module"); +MODULE_LICENSE("GPL"); -diff --git a/drivers/misc/surface_sam/controller.c b/drivers/misc/surface_sam/controller.c +diff --git a/drivers/misc/surface_aggregator/clients/surface_perfmode.c b/drivers/misc/surface_aggregator/clients/surface_perfmode.c new file mode 100644 -index 000000000000..c279159e3aab +index 000000000000..ed93596adcf6 --- /dev/null -+++ b/drivers/misc/surface_sam/controller.c -@@ -0,0 +1,2441 @@ ++++ b/drivers/misc/surface_aggregator/clients/surface_perfmode.c +@@ -0,0 +1,194 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Surface performance-mode driver. ++ * ++ * Proides a user-space interface for the performance mode control provided by ++ * the Surface System Aggregator Module (SSAM), influencing cooling behavior ++ * of the device and potentially managing power limits. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++ ++#include ++ ++ ++#define SID_PARAM_PERM 0644 ++ ++enum sam_perf_mode { ++ SAM_PERF_MODE_NORMAL = 1, ++ SAM_PERF_MODE_BATTERY = 2, ++ SAM_PERF_MODE_PERF1 = 3, ++ SAM_PERF_MODE_PERF2 = 4, ++ ++ __SAM_PERF_MODE__START = 1, ++ __SAM_PERF_MODE__END = 4, ++}; ++ ++enum sid_param_perf_mode { ++ SID_PARAM_PERF_MODE_AS_IS = 0, ++ SID_PARAM_PERF_MODE_NORMAL = SAM_PERF_MODE_NORMAL, ++ SID_PARAM_PERF_MODE_BATTERY = SAM_PERF_MODE_BATTERY, ++ SID_PARAM_PERF_MODE_PERF1 = SAM_PERF_MODE_PERF1, ++ SID_PARAM_PERF_MODE_PERF2 = SAM_PERF_MODE_PERF2, ++ ++ __SID_PARAM_PERF_MODE__START = 0, ++ __SID_PARAM_PERF_MODE__END = 4, ++}; ++ ++ ++struct ssam_perf_info { ++ __le32 mode; ++ __le16 unknown1; ++ __le16 unknown2; ++} __packed; ++ ++static SSAM_DEFINE_SYNC_REQUEST_CL_R(ssam_tmp_perf_mode_get, struct ssam_perf_info, { ++ .target_category = SSAM_SSH_TC_TMP, ++ .command_id = 0x02, ++}); ++ ++static SSAM_DEFINE_SYNC_REQUEST_CL_W(__ssam_tmp_perf_mode_set, __le32, { ++ .target_category = SSAM_SSH_TC_TMP, ++ .command_id = 0x03, ++}); ++ ++static int ssam_tmp_perf_mode_set(struct ssam_device *sdev, u32 mode) ++{ ++ __le32 mode_le = cpu_to_le32(mode); ++ ++ if (mode < __SAM_PERF_MODE__START || mode > __SAM_PERF_MODE__END) ++ return -EINVAL; ++ ++ return __ssam_tmp_perf_mode_set(sdev, &mode_le); ++} ++ ++ ++static int param_perf_mode_set(const char *val, const struct kernel_param *kp) ++{ ++ int perf_mode; ++ int status; ++ ++ status = kstrtoint(val, 0, &perf_mode); ++ if (status) ++ return status; ++ ++ if (perf_mode < __SID_PARAM_PERF_MODE__START || perf_mode > __SID_PARAM_PERF_MODE__END) ++ return -EINVAL; ++ ++ return param_set_int(val, kp); ++} ++ ++static const struct kernel_param_ops param_perf_mode_ops = { ++ .set = param_perf_mode_set, ++ .get = param_get_int, ++}; ++ ++static int param_perf_mode_init = SID_PARAM_PERF_MODE_AS_IS; ++static int param_perf_mode_exit = SID_PARAM_PERF_MODE_AS_IS; ++ ++module_param_cb(perf_mode_init, ¶m_perf_mode_ops, ¶m_perf_mode_init, SID_PARAM_PERM); ++module_param_cb(perf_mode_exit, ¶m_perf_mode_ops, ¶m_perf_mode_exit, SID_PARAM_PERM); ++ ++MODULE_PARM_DESC(perf_mode_init, "Performance-mode to be set on module initialization"); ++MODULE_PARM_DESC(perf_mode_exit, "Performance-mode to be set on module exit"); ++ ++ ++static ssize_t perf_mode_show(struct device *dev, struct device_attribute *attr, char *data) ++{ ++ struct ssam_device *sdev = to_ssam_device(dev); ++ struct ssam_perf_info info; ++ int status; ++ ++ status = ssam_tmp_perf_mode_get(sdev, &info); ++ if (status) { ++ dev_err(dev, "failed to get current performance mode: %d\n", status); ++ return -EIO; ++ } ++ ++ return sprintf(data, "%d\n", le32_to_cpu(info.mode)); ++} ++ ++static ssize_t perf_mode_store(struct device *dev, struct device_attribute *attr, ++ const char *data, size_t count) ++{ ++ struct ssam_device *sdev = to_ssam_device(dev); ++ int perf_mode; ++ int status; ++ ++ status = kstrtoint(data, 0, &perf_mode); ++ if (status) ++ return status; ++ ++ status = ssam_tmp_perf_mode_set(sdev, perf_mode); ++ if (status) ++ return status; ++ ++ // TODO: Should we notify ACPI here? ++ // ++ // There is a _DSM call described as ++ // WSID._DSM: Notify DPTF on Slider State change ++ // which calls ++ // ODV3 = ToInteger (Arg3) ++ // Notify(IETM, 0x88) ++ // IETM is an INT3400 Intel Dynamic Power Performance Management ++ // device, part of the DPTF framework. From the corresponding ++ // kernel driver, it looks like event 0x88 is being ignored. Also ++ // it is currently unknown what the consequecnes of setting ODV3 ++ // are. ++ ++ return count; ++} ++ ++static const DEVICE_ATTR_RW(perf_mode); ++ ++ ++static int surface_sam_sid_perfmode_probe(struct ssam_device *sdev) ++{ ++ int status; ++ ++ // set initial perf_mode ++ if (param_perf_mode_init != SID_PARAM_PERF_MODE_AS_IS) { ++ status = ssam_tmp_perf_mode_set(sdev, param_perf_mode_init); ++ if (status) ++ return status; ++ } ++ ++ // register perf_mode attribute ++ status = sysfs_create_file(&sdev->dev.kobj, &dev_attr_perf_mode.attr); ++ if (status) ++ ssam_tmp_perf_mode_set(sdev, param_perf_mode_exit); ++ ++ return status; ++} ++ ++static void surface_sam_sid_perfmode_remove(struct ssam_device *sdev) ++{ ++ sysfs_remove_file(&sdev->dev.kobj, &dev_attr_perf_mode.attr); ++ ssam_tmp_perf_mode_set(sdev, param_perf_mode_exit); ++} ++ ++ ++static const struct ssam_device_id ssam_perfmode_match[] = { ++ { SSAM_SDEV(TMP, 0x01, 0x00, 0x01) }, ++ { }, ++}; ++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_performance_mode", ++ .probe_type = PROBE_PREFER_ASYNCHRONOUS, ++ }, ++}; ++module_ssam_device_driver(surface_sam_sid_perfmode); ++ ++MODULE_AUTHOR("Maximilian Luz "); ++MODULE_DESCRIPTION("Performance mode interface for Surface System Aggregator Module"); ++MODULE_LICENSE("GPL"); +diff --git a/drivers/misc/surface_aggregator/controller.c b/drivers/misc/surface_aggregator/controller.c +new file mode 100644 +index 000000000000..d8973cb35a5e +--- /dev/null ++++ b/drivers/misc/surface_aggregator/controller.c +@@ -0,0 +1,2509 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#include @@ -7838,14 +7928,14 @@ index 000000000000..c279159e3aab +#include +#include + -+#include ++#include ++#include + +#include "controller.h" +#include "ssh_msgb.h" -+#include "ssh_protocol.h" +#include "ssh_request_layer.h" + -+#include "ssam_trace.h" ++#include "trace.h" + + +/* -- Safe counters. -------------------------------------------------------- */ @@ -7854,7 +7944,7 @@ index 000000000000..c279159e3aab + * ssh_seq_reset() - Reset/initialize sequence ID counter. + * @c: The counter to reset. + */ -+static inline void ssh_seq_reset(struct ssh_seq_counter *c) ++static void ssh_seq_reset(struct ssh_seq_counter *c) +{ + WRITE_ONCE(c->value, 0); +} @@ -7865,7 +7955,7 @@ index 000000000000..c279159e3aab + * + * Return: Retunrs the next sequence ID of the counter. + */ -+static inline u8 ssh_seq_next(struct ssh_seq_counter *c) ++static u8 ssh_seq_next(struct ssh_seq_counter *c) +{ + u8 old = READ_ONCE(c->value); + u8 new = old + 1; @@ -7883,7 +7973,7 @@ index 000000000000..c279159e3aab + * ssh_rqid_reset() - Reset/initialize request ID counter. + * @c: The counter to reset. + */ -+static inline void ssh_rqid_reset(struct ssh_rqid_counter *c) ++static void ssh_rqid_reset(struct ssh_rqid_counter *c) +{ + WRITE_ONCE(c->value, 0); +} @@ -7895,7 +7985,7 @@ index 000000000000..c279159e3aab + * Return: Returns the next request ID of the counter, skipping any reserved + * request IDs. + */ -+static inline u16 ssh_rqid_next(struct ssh_rqid_counter *c) ++static u16 ssh_rqid_next(struct ssh_rqid_counter *c) +{ + u16 old = READ_ONCE(c->value); + u16 new = ssh_rqid_next_valid(old); @@ -8405,7 +8495,7 @@ index 000000000000..c279159e3aab + * ssam_event_item_free() - Free the provided event item. + * @item: The event item to free. + */ -+static inline void ssam_event_item_free(struct ssam_event_item *item) ++static void ssam_event_item_free(struct ssam_event_item *item) +{ + trace_ssam_event_item_free(item); + item->ops.free(item); @@ -8435,9 +8525,7 @@ index 000000000000..c279159e3aab + + item->ops.free = __ssam_event_item_free_cached; + } else { -+ const size_t n = sizeof(struct ssam_event_item) + len; -+ -+ item = kzalloc(n, GFP_KERNEL); ++ item = kzalloc(sizeof(*item) + len, GFP_KERNEL); + if (!item) + return NULL; + @@ -8534,8 +8622,7 @@ index 000000000000..c279159e3aab + * @cplt: The completion system. + * @work: The work item to submit. + */ -+static inline bool ssam_cplt_submit(struct ssam_cplt *cplt, -+ struct work_struct *work) ++static bool ssam_cplt_submit(struct ssam_cplt *cplt, struct work_struct *work) +{ + return queue_work(cplt->wq, work); +} @@ -8819,10 +8906,68 @@ index 000000000000..c279159e3aab + + +#define SSAM_SSH_DSM_REVISION 0 -+#define SSAM_SSH_DSM_NOTIF_D0 8 -+static const guid_t SSAM_SSH_DSM_UUID = GUID_INIT(0xd5e383e1, 0xd892, 0x4a76, ++static const guid_t SSAM_SSH_DSM_GUID = GUID_INIT(0xd5e383e1, 0xd892, 0x4a76, + 0x89, 0xfc, 0xf6, 0xaa, 0xae, 0x7e, 0xd5, 0xb5); + ++enum ssh_dsm_fn { ++ SSH_DSM_FN_SSH_POWER_PROFILE = 0x05, ++ SSH_DSM_FN_SCREEN_ON_SLEEP_IDLE_TIMEOUT = 0x06, ++ SSH_DSM_FN_SCREEN_OFF_SLEEP_IDLE_TIMEOUT = 0x07, ++ SSH_DSM_FN_D3_CLOSES_HANDLE = 0x08, ++ SSH_DSM_FN_SSH_BUFFER_SIZE = 0x09, ++}; ++ ++static int ssam_dsm_get_functions(acpi_handle handle, u64 *funcs) ++{ ++ union acpi_object *obj; ++ u64 mask = 0; ++ int i; ++ ++ *funcs = 0; ++ ++ if (!acpi_has_method(handle, "_DSM")) ++ return 0; ++ ++ obj = acpi_evaluate_dsm_typed(handle, &SSAM_SSH_DSM_GUID, ++ SSAM_SSH_DSM_REVISION, 0, NULL, ++ ACPI_TYPE_BUFFER); ++ if (!obj) ++ return -EFAULT; ++ ++ for (i = 0; i < obj->buffer.length && i < 8; i++) ++ mask |= (((u64)obj->buffer.pointer[i]) << (i * 8)); ++ ++ if (mask & 0x01) ++ *funcs = mask; ++ ++ ACPI_FREE(obj); ++ return 0; ++} ++ ++static int ssam_dsm_load_u32(acpi_handle handle, u64 funcs, u64 func, u32 *ret) ++{ ++ union acpi_object *obj; ++ u64 val; ++ ++ if (!(funcs & BIT(func))) ++ return 0; ++ ++ obj = acpi_evaluate_dsm_typed(handle, &SSAM_SSH_DSM_GUID, ++ SSAM_SSH_DSM_REVISION, func, NULL, ++ ACPI_TYPE_INTEGER); ++ if (!obj) ++ return -EFAULT; ++ ++ val = obj->integer.value; ++ ACPI_FREE(obj); ++ ++ if (val > U32_MAX) ++ return -ERANGE; ++ ++ *ret = val; ++ return 0; ++} ++ +/** + * ssam_controller_caps_load_from_acpi() - Load controller capabilities from + * ACPI _DSM. @@ -8833,44 +8978,54 @@ index 000000000000..c279159e3aab + * checks and, if the respective _DSM functions are available, loads the + * actual capabilities from the _DSM. + * -+ * Return: Returns zero on success, %-EFAULT on failure. ++ * Return: Returns zero on success, a negative error code on failure. + */ +static int ssam_controller_caps_load_from_acpi( + acpi_handle handle, struct ssam_controller_caps *caps) +{ -+ union acpi_object *obj; -+ u64 funcs = 0; -+ int i; ++ u32 d3_closes_handle; ++ u64 funcs; ++ int status; + + // set defaults -+ caps->notif_display = true; -+ caps->notif_d0exit = false; ++ caps->ssh_power_profile = (u32)-1; ++ caps->screen_on_sleep_idle_timeout = (u32)-1; ++ caps->screen_off_sleep_idle_timeout = (u32)-1; ++ caps->d3_closes_handle = false; ++ caps->ssh_buffer_size = (u32)-1; + -+ if (!acpi_has_method(handle, "_DSM")) -+ return 0; ++ status = ssam_dsm_get_functions(handle, &funcs); ++ if (status) ++ return status; + -+ // get function availability bitfield -+ obj = acpi_evaluate_dsm_typed(handle, &SSAM_SSH_DSM_UUID, 0, 0, NULL, -+ ACPI_TYPE_BUFFER); -+ if (!obj) -+ return -EFAULT; ++ status = ssam_dsm_load_u32(handle, funcs, SSH_DSM_FN_SSH_POWER_PROFILE, ++ &caps->ssh_power_profile); ++ if (status) ++ return status; + -+ for (i = 0; i < obj->buffer.length && i < 8; i++) -+ funcs |= (((u64)obj->buffer.pointer[i]) << (i * 8)); ++ status = ssam_dsm_load_u32(handle, funcs, ++ SSH_DSM_FN_SCREEN_ON_SLEEP_IDLE_TIMEOUT, ++ &caps->screen_on_sleep_idle_timeout); ++ if (status) ++ return status; + -+ ACPI_FREE(obj); ++ status = ssam_dsm_load_u32(handle, funcs, ++ SSH_DSM_FN_SCREEN_OFF_SLEEP_IDLE_TIMEOUT, ++ &caps->screen_off_sleep_idle_timeout); ++ if (status) ++ return status; + -+ // D0 exit/entry notification -+ if (funcs & BIT(SSAM_SSH_DSM_NOTIF_D0)) { -+ obj = acpi_evaluate_dsm_typed(handle, &SSAM_SSH_DSM_UUID, -+ SSAM_SSH_DSM_REVISION, SSAM_SSH_DSM_NOTIF_D0, -+ NULL, ACPI_TYPE_INTEGER); -+ if (!obj) -+ return -EFAULT; ++ status = ssam_dsm_load_u32(handle, funcs, SSH_DSM_FN_D3_CLOSES_HANDLE, ++ &d3_closes_handle); ++ if (status) ++ return status; + -+ caps->notif_d0exit = !!obj->integer.value; -+ ACPI_FREE(obj); -+ } ++ caps->d3_closes_handle = !!d3_closes_handle; ++ ++ status = ssam_dsm_load_u32(handle, funcs, SSH_DSM_FN_SSH_BUFFER_SIZE, ++ &caps->ssh_buffer_size); ++ if (status) ++ return status; + + return 0; +} @@ -8901,9 +9056,18 @@ index 000000000000..c279159e3aab + if (status) + return status; + -+ dev_dbg(&serdev->dev, "device capabilities:\n"); -+ dev_dbg(&serdev->dev, " notif_display: %u\n", ctrl->caps.notif_display); -+ dev_dbg(&serdev->dev, " notif_d0exit: %u\n", ctrl->caps.notif_d0exit); ++ dev_dbg(&serdev->dev, ++ "device capabilities:\n" ++ " ssh_power_profile: %u\n" ++ " ssh_buffer_size: %u\n" ++ " screen_on_sleep_idle_timeout: %u\n" ++ " screen_off_sleep_idle_timeout: %u\n" ++ " d3_closes_handle: %u\n", ++ ctrl->caps.ssh_power_profile, ++ ctrl->caps.ssh_buffer_size, ++ ctrl->caps.screen_on_sleep_idle_timeout, ++ ctrl->caps.screen_off_sleep_idle_timeout, ++ ctrl->caps.d3_closes_handle); + + ssh_seq_reset(&ctrl->counter.seq); + ssh_rqid_reset(&ctrl->counter.rqid); @@ -9214,7 +9378,7 @@ index 000000000000..c279159e3aab +{ + size_t msglen = SSH_COMMAND_MESSAGE_LENGTH(payload_len); + -+ *rqst = kzalloc(sizeof(struct ssam_request_sync) + msglen, flags); ++ *rqst = kzalloc(sizeof(*rqst) + msglen, flags); + if (!*rqst) + return -ENOMEM; + @@ -9660,9 +9824,6 @@ index 000000000000..c279159e3aab + int status; + u8 response; + -+ if (!ctrl->caps.notif_display) -+ return 0; -+ + ssam_dbg(ctrl, "pm: notifying display off\n"); + + status = ssam_ssh_notif_display_off(ctrl, &response); @@ -9702,9 +9863,6 @@ index 000000000000..c279159e3aab + int status; + u8 response; + -+ if (!ctrl->caps.notif_display) -+ return 0; -+ + ssam_dbg(ctrl, "pm: notifying display on\n"); + + status = ssam_ssh_notif_display_on(ctrl, &response); @@ -9744,7 +9902,7 @@ index 000000000000..c279159e3aab + int status; + u8 response; + -+ if (!ctrl->caps.notif_d0exit) ++ if (!ctrl->caps.d3_closes_handle) + return 0; + + ssam_dbg(ctrl, "pm: notifying D0 exit\n"); @@ -9786,7 +9944,7 @@ index 000000000000..c279159e3aab + int status; + u8 response; + -+ if (!ctrl->caps.notif_d0exit) ++ if (!ctrl->caps.d3_closes_handle) + return 0; + + ssam_dbg(ctrl, "pm: notifying D0 entry\n"); @@ -10179,7 +10337,7 @@ index 000000000000..c279159e3aab + return irq; + + status = request_threaded_irq(irq, NULL, ssam_irq_handle, irqf, -+ "surface_sam_wakeup", ctrl); ++ "ssam_wakeup", ctrl); + if (status) + return status; + @@ -10260,16 +10418,16 @@ index 000000000000..c279159e3aab + } + disable_irq(ctrl->irq.num); +} -diff --git a/drivers/misc/surface_sam/controller.h b/drivers/misc/surface_sam/controller.h +diff --git a/drivers/misc/surface_aggregator/controller.h b/drivers/misc/surface_aggregator/controller.h new file mode 100644 -index 000000000000..2c328e21df68 +index 000000000000..b45b5b23cbd6 --- /dev/null -+++ b/drivers/misc/surface_sam/controller.h -@@ -0,0 +1,275 @@ ++++ b/drivers/misc/surface_aggregator/controller.h +@@ -0,0 +1,283 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + -+#ifndef _SSAM_CONTROLLER_H -+#define _SSAM_CONTROLLER_H ++#ifndef _SURFACE_AGGREGATOR_CONTROLLER_H ++#define _SURFACE_AGGREGATOR_CONTROLLER_H + +#include +#include @@ -10282,9 +10440,9 @@ index 000000000000..2c328e21df68 +#include +#include + -+#include ++#include ++#include + -+#include "ssh_protocol.h" +#include "ssh_request_layer.h" + + @@ -10428,12 +10586,20 @@ index 000000000000..2c328e21df68 + +/** + * struct ssam_controller_caps - Controller device capabilities. -+ * @notif_display: The controller supports display-on/-off notifications. -+ * @notif_d0exit: The controller supports D0-entry/D0-exit notifications ++ * @ssh_power_profile: SSH power profile. ++ * @ssh_buffer_size: SSH driver UART buffer size. ++ * @screen_on_sleep_idle_timeout: SAM UART screen-on sleep idle timeout. ++ * @screen_off_sleep_idle_timeout: SAM UART screen-off sleep idle timeout. ++ * @d3_closes_handle: SAM closes UART handle in D3. ++ * ++ * Controller and SSH device capabilities found in ACPI. + */ +struct ssam_controller_caps { -+ u32 notif_display:1; -+ u32 notif_d0exit:1; ++ u32 ssh_power_profile; ++ u32 ssh_buffer_size; ++ u32 screen_on_sleep_idle_timeout; ++ u32 screen_off_sleep_idle_timeout; ++ u32 d3_closes_handle:1; +}; + +/** @@ -10540,17 +10706,21 @@ index 000000000000..2c328e21df68 +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 ++#endif /* _SURFACE_AGGREGATOR_CONTROLLER_H */ +diff --git a/drivers/misc/surface_aggregator/core.c b/drivers/misc/surface_aggregator/core.c new file mode 100644 -index 000000000000..5164d13e52f8 +index 000000000000..957bdcc4e3d3 --- /dev/null -+++ b/drivers/misc/surface_sam/core.c -@@ -0,0 +1,771 @@ ++++ b/drivers/misc/surface_aggregator/core.c +@@ -0,0 +1,773 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Surface Serial Hub (SSH) driver for communication with the Surface/System -+ * Aggregator Module. ++ * Aggregator Module (SSAM/SAM). ++ * ++ * Provides access to a SAM-over-SSH connected EC via a controller device. ++ * Handles communication via requests as well as enabling, disabling, and ++ * relaying of events. + */ + +#include @@ -10559,16 +10729,17 @@ index 000000000000..5164d13e52f8 +#include +#include +#include ++#include +#include +#include + -+#include ++#include + +#include "bus.h" +#include "controller.h" + +#define CREATE_TRACE_POINTS -+#include "ssam_trace.h" ++#include "trace.h" + + +/* -- Static controller reference. ------------------------------------------ */ @@ -10841,9 +11012,9 @@ index 000000000000..5164d13e52f8 + } + + if (status) { -+ dev_err(&serdev->dev, "setup: failed to set parity (value: 0x%02x)\n", -+ uart->parity); -+ return status; ++ dev_err(&serdev->dev, "setup: failed to set parity (value: 0x%02x," ++ " error: %d)\n", uart->parity, status); ++ return AE_ERROR; + } + + return AE_CTRL_TERMINATE; // we've found the resource and are done @@ -10887,14 +11058,13 @@ index 000000000000..5164d13e52f8 + ssam_err(c, "pm: D0-exit notification failed: %d\n", status); +} + -+static int ssam_serial_hub_suspend(struct device *dev) ++static int ssam_serial_hub_pm_prepare(struct device *dev) +{ + struct ssam_controller *c = dev_get_drvdata(dev); + int status; + + /* -+ * Try to signal display-off and D0-exit, enable IRQ wakeup if -+ * specified. Abort on error. ++ * Try to signal display-off, This will quiesce events. + * + * Note: Signalling display-off/display-on should normally be done from + * some sort of display state notifier. As that is not available, signal @@ -10902,10 +11072,39 @@ index 000000000000..5164d13e52f8 + */ + + status = ssam_ctrl_notif_display_off(c); -+ if (status) { ++ if (status) + ssam_err(c, "pm: display-off notification failed: %d\n", status); -+ return status; -+ } ++ ++ return status; ++} ++ ++static void ssam_serial_hub_pm_complete(struct device *dev) ++{ ++ struct ssam_controller *c = dev_get_drvdata(dev); ++ int status; ++ ++ /* ++ * Try to signal display-on. This will restore events. ++ * ++ * Note: Signalling display-off/display-on should normally be done from ++ * some sort of display state notifier. As that is not available, signal ++ * it here. ++ */ ++ ++ status = ssam_ctrl_notif_display_on(c); ++ if (status) ++ ssam_err(c, "pm: display-on notification failed: %d\n", status); ++} ++ ++static int ssam_serial_hub_pm_suspend(struct device *dev) ++{ ++ struct ssam_controller *c = dev_get_drvdata(dev); ++ int status; ++ ++ /* ++ * Try to signal D0-exit, enable IRQ wakeup if specified. Abort on ++ * error. ++ */ + + status = ssam_ctrl_notif_d0_exit(c); + if (status) { @@ -10927,7 +11126,7 @@ index 000000000000..5164d13e52f8 + return status; +} + -+static int ssam_serial_hub_resume(struct device *dev) ++static int ssam_serial_hub_pm_resume(struct device *dev) +{ + struct ssam_controller *c = dev_get_drvdata(dev); + int status; @@ -10935,9 +11134,9 @@ index 000000000000..5164d13e52f8 + WARN_ON(ssam_controller_resume(c)); + + /* -+ * Try to disable IRQ wakeup (if specified), signal display-on and -+ * D0-entry. In case of errors, log them and try to restore normal -+ * operation state as far as possible. ++ * Try to disable IRQ wakeup (if specified) and signal D0-entry. In ++ * case of errors, log them and try to restore normal operation state ++ * as far as possible. + * + * Note: Signalling display-off/display-on should normally be done from + * some sort of display state notifier. As that is not available, signal @@ -10950,14 +11149,10 @@ index 000000000000..5164d13e52f8 + if (status) + ssam_err(c, "pm: D0-entry notification failed: %d\n", status); + -+ status = ssam_ctrl_notif_display_on(c); -+ if (status) -+ ssam_err(c, "pm: display-on notification failed: %d\n", status); -+ + return 0; +} + -+static int ssam_serial_hub_freeze(struct device *dev) ++static int ssam_serial_hub_pm_freeze(struct device *dev) +{ + struct ssam_controller *c = dev_get_drvdata(dev); + int status; @@ -10973,12 +11168,6 @@ index 000000000000..5164d13e52f8 + * process. + */ + -+ status = ssam_ctrl_notif_display_off(c); -+ if (status) { -+ ssam_err(c, "pm: display-off notification failed: %d\n", status); -+ return status; -+ } -+ + status = ssam_ctrl_notif_d0_exit(c); + if (status) { + ssam_err(c, "pm: D0-exit notification failed: %d\n", status); @@ -10990,7 +11179,7 @@ index 000000000000..5164d13e52f8 + return 0; +} + -+static int ssam_serial_hub_thaw(struct device *dev) ++static int ssam_serial_hub_pm_thaw(struct device *dev) +{ + struct ssam_controller *c = dev_get_drvdata(dev); + int status; @@ -10998,22 +11187,13 @@ index 000000000000..5164d13e52f8 + WARN_ON(ssam_controller_resume(c)); + + status = ssam_ctrl_notif_d0_entry(c); -+ if (status) { -+ ssam_err(c, "pm: D0-exit notification failed: %d\n", status); -+ -+ // try to restore as much as possible in case of failure -+ ssam_ctrl_notif_display_on(c); -+ return status; -+ } -+ -+ status = ssam_ctrl_notif_display_on(c); + if (status) -+ ssam_err(c, "pm: display-on notification failed: %d\n", status); ++ ssam_err(c, "pm: D0-exit notification failed: %d\n", status); + + return status; +} + -+static int ssam_serial_hub_poweroff(struct device *dev) ++static int ssam_serial_hub_pm_poweroff(struct device *dev) +{ + struct ssam_controller *c = dev_get_drvdata(dev); + int status; @@ -11041,29 +11221,18 @@ index 000000000000..5164d13e52f8 + return status; + } + -+ status = ssam_ctrl_notif_display_off(c); -+ if (status) { -+ ssam_err(c, "pm: display-off notification failed: %d\n", status); -+ goto err_dpnf; -+ } -+ + status = ssam_ctrl_notif_d0_exit(c); + if (status) { + ssam_err(c, "pm: D0-exit notification failed: %d\n", status); -+ goto err_d0nf; ++ ssam_notifier_restore_registered(c); ++ return status; + } + + WARN_ON(ssam_controller_suspend(c)); + return 0; -+ -+err_d0nf: -+ ssam_ctrl_notif_display_on(c); -+err_dpnf: -+ ssam_notifier_restore_registered(c); -+ return status; +} + -+static int ssam_serial_hub_restore(struct device *dev) ++static int ssam_serial_hub_pm_restore(struct device *dev) +{ + struct ssam_controller *c = dev_get_drvdata(dev); + int status; @@ -11080,21 +11249,19 @@ index 000000000000..5164d13e52f8 + if (status) + ssam_err(c, "pm: D0-entry notification failed: %d\n", status); + -+ status = ssam_ctrl_notif_display_on(c); -+ if (status) -+ ssam_err(c, "pm: display-on notification failed: %d\n", status); -+ + ssam_notifier_restore_registered(c); + return 0; +} + +static const struct dev_pm_ops ssam_serial_hub_pm_ops = { -+ .suspend = ssam_serial_hub_suspend, -+ .resume = ssam_serial_hub_resume, -+ .freeze = ssam_serial_hub_freeze, -+ .thaw = ssam_serial_hub_thaw, -+ .poweroff = ssam_serial_hub_poweroff, -+ .restore = ssam_serial_hub_restore, ++ .prepare = ssam_serial_hub_pm_prepare, ++ .complete = ssam_serial_hub_pm_complete, ++ .suspend = ssam_serial_hub_pm_suspend, ++ .resume = ssam_serial_hub_pm_resume, ++ .freeze = ssam_serial_hub_pm_freeze, ++ .thaw = ssam_serial_hub_pm_thaw, ++ .poweroff = ssam_serial_hub_pm_poweroff, ++ .restore = ssam_serial_hub_pm_restore, +}; + + @@ -11113,6 +11280,7 @@ index 000000000000..5164d13e52f8 +{ + struct ssam_controller *ctrl; + acpi_handle *ssh = ACPI_HANDLE(&serdev->dev); ++ acpi_status astatus; + int status; + + if (gpiod_count(&serdev->dev, NULL) < 0) @@ -11123,7 +11291,7 @@ index 000000000000..5164d13e52f8 + return status; + + // allocate controller -+ ctrl = kzalloc(sizeof(struct ssam_controller), GFP_KERNEL); ++ ctrl = kzalloc(sizeof(*ctrl), GFP_KERNEL); + if (!ctrl) + return -ENOMEM; + @@ -11139,9 +11307,11 @@ index 000000000000..5164d13e52f8 + if (status) + goto err_devopen; + -+ status = ssam_serdev_setup_via_acpi(ssh, serdev); -+ if (ACPI_FAILURE(status)) ++ astatus = ssam_serdev_setup_via_acpi(ssh, serdev); ++ if (ACPI_FAILURE(astatus)) { ++ status = -ENXIO; + goto err_devinit; ++ } + + // start controller + status = ssam_controller_start(ctrl); @@ -11192,7 +11362,6 @@ index 000000000000..5164d13e52f8 + serdev_device_close(serdev); +err_devopen: + ssam_controller_destroy(ctrl); -+ serdev_device_set_drvdata(serdev, NULL); +err_ctrl_init: + kfree(ctrl); + return status; @@ -11237,7 +11406,6 @@ index 000000000000..5164d13e52f8 + ssam_controller_put(ctrl); + + device_set_wakeup_capable(&serdev->dev, false); -+ serdev_device_set_drvdata(serdev, NULL); +} + + @@ -11251,7 +11419,7 @@ index 000000000000..5164d13e52f8 + .probe = ssam_serial_hub_probe, + .remove = ssam_serial_hub_remove, + .driver = { -+ .name = "surface_sam_ssh", ++ .name = "surface_serial_hub", + .acpi_match_table = ssam_serial_hub_match, + .pm = &ssam_serial_hub_pm_ops, + .shutdown = ssam_serial_hub_shutdown, @@ -11316,649 +11484,24 @@ index 000000000000..5164d13e52f8 +module_exit(ssam_core_exit); + +MODULE_AUTHOR("Maximilian Luz "); -+MODULE_DESCRIPTION("Surface Serial Hub Driver for 5th Generation Surface Devices"); ++MODULE_DESCRIPTION("Subsystem and Surface Serial Hub driver for Surface System Aggregator Module"); +MODULE_LICENSE("GPL"); -diff --git a/drivers/misc/surface_sam/ssam_trace.h b/drivers/misc/surface_sam/ssam_trace.h +diff --git a/drivers/misc/surface_aggregator/ssh_msgb.h b/drivers/misc/surface_aggregator/ssh_msgb.h new file mode 100644 -index 000000000000..ad1ce1cdd474 +index 000000000000..046e5e44a100 --- /dev/null -+++ b/drivers/misc/surface_sam/ssam_trace.h -@@ -0,0 +1,619 @@ -+/* SPDX-License-Identifier: GPL-2.0-or-later */ -+ -+#undef TRACE_SYSTEM -+#define TRACE_SYSTEM ssam -+ -+#if !defined(_SURFACE_SAM_SSH_TRACE_H) || defined(TRACE_HEADER_MULTI_READ) -+#define _SURFACE_SAM_SSH_TRACE_H -+ -+#include -+#include -+ -+ -+TRACE_DEFINE_ENUM(SSH_FRAME_TYPE_DATA_SEQ); -+TRACE_DEFINE_ENUM(SSH_FRAME_TYPE_DATA_NSQ); -+TRACE_DEFINE_ENUM(SSH_FRAME_TYPE_ACK); -+TRACE_DEFINE_ENUM(SSH_FRAME_TYPE_NAK); -+ -+TRACE_DEFINE_ENUM(SSH_PACKET_SF_LOCKED_BIT); -+TRACE_DEFINE_ENUM(SSH_PACKET_SF_QUEUED_BIT); -+TRACE_DEFINE_ENUM(SSH_PACKET_SF_PENDING_BIT); -+TRACE_DEFINE_ENUM(SSH_PACKET_SF_TRANSMITTING_BIT); -+TRACE_DEFINE_ENUM(SSH_PACKET_SF_TRANSMITTED_BIT); -+TRACE_DEFINE_ENUM(SSH_PACKET_SF_ACKED_BIT); -+TRACE_DEFINE_ENUM(SSH_PACKET_SF_CANCELED_BIT); -+TRACE_DEFINE_ENUM(SSH_PACKET_SF_COMPLETED_BIT); -+ -+TRACE_DEFINE_ENUM(SSH_PACKET_TY_FLUSH_BIT); -+TRACE_DEFINE_ENUM(SSH_PACKET_TY_SEQUENCED_BIT); -+TRACE_DEFINE_ENUM(SSH_PACKET_TY_BLOCKING_BIT); -+ -+TRACE_DEFINE_ENUM(SSH_PACKET_FLAGS_SF_MASK); -+TRACE_DEFINE_ENUM(SSH_PACKET_FLAGS_TY_MASK); -+ -+TRACE_DEFINE_ENUM(SSH_REQUEST_SF_LOCKED_BIT); -+TRACE_DEFINE_ENUM(SSH_REQUEST_SF_QUEUED_BIT); -+TRACE_DEFINE_ENUM(SSH_REQUEST_SF_PENDING_BIT); -+TRACE_DEFINE_ENUM(SSH_REQUEST_SF_TRANSMITTING_BIT); -+TRACE_DEFINE_ENUM(SSH_REQUEST_SF_TRANSMITTED_BIT); -+TRACE_DEFINE_ENUM(SSH_REQUEST_SF_RSPRCVD_BIT); -+TRACE_DEFINE_ENUM(SSH_REQUEST_SF_CANCELED_BIT); -+TRACE_DEFINE_ENUM(SSH_REQUEST_SF_COMPLETED_BIT); -+ -+TRACE_DEFINE_ENUM(SSH_REQUEST_TY_FLUSH_BIT); -+TRACE_DEFINE_ENUM(SSH_REQUEST_TY_HAS_RESPONSE_BIT); -+ -+TRACE_DEFINE_ENUM(SSH_REQUEST_FLAGS_SF_MASK); -+TRACE_DEFINE_ENUM(SSH_REQUEST_FLAGS_TY_MASK); -+ -+TRACE_DEFINE_ENUM(SSAM_SSH_TC_SAM); -+TRACE_DEFINE_ENUM(SSAM_SSH_TC_BAT); -+TRACE_DEFINE_ENUM(SSAM_SSH_TC_TMP); -+TRACE_DEFINE_ENUM(SSAM_SSH_TC_PMC); -+TRACE_DEFINE_ENUM(SSAM_SSH_TC_FAN); -+TRACE_DEFINE_ENUM(SSAM_SSH_TC_PoM); -+TRACE_DEFINE_ENUM(SSAM_SSH_TC_DBG); -+TRACE_DEFINE_ENUM(SSAM_SSH_TC_KBD); -+TRACE_DEFINE_ENUM(SSAM_SSH_TC_FWU); -+TRACE_DEFINE_ENUM(SSAM_SSH_TC_UNI); -+TRACE_DEFINE_ENUM(SSAM_SSH_TC_LPC); -+TRACE_DEFINE_ENUM(SSAM_SSH_TC_TCL); -+TRACE_DEFINE_ENUM(SSAM_SSH_TC_SFL); -+TRACE_DEFINE_ENUM(SSAM_SSH_TC_KIP); -+TRACE_DEFINE_ENUM(SSAM_SSH_TC_EXT); -+TRACE_DEFINE_ENUM(SSAM_SSH_TC_BLD); -+TRACE_DEFINE_ENUM(SSAM_SSH_TC_BAS); -+TRACE_DEFINE_ENUM(SSAM_SSH_TC_SEN); -+TRACE_DEFINE_ENUM(SSAM_SSH_TC_SRQ); -+TRACE_DEFINE_ENUM(SSAM_SSH_TC_MCU); -+TRACE_DEFINE_ENUM(SSAM_SSH_TC_HID); -+TRACE_DEFINE_ENUM(SSAM_SSH_TC_TCH); -+TRACE_DEFINE_ENUM(SSAM_SSH_TC_BKL); -+TRACE_DEFINE_ENUM(SSAM_SSH_TC_TAM); -+TRACE_DEFINE_ENUM(SSAM_SSH_TC_ACC); -+TRACE_DEFINE_ENUM(SSAM_SSH_TC_UFI); -+TRACE_DEFINE_ENUM(SSAM_SSH_TC_USC); -+TRACE_DEFINE_ENUM(SSAM_SSH_TC_PEN); -+TRACE_DEFINE_ENUM(SSAM_SSH_TC_VID); -+TRACE_DEFINE_ENUM(SSAM_SSH_TC_AUD); -+TRACE_DEFINE_ENUM(SSAM_SSH_TC_SMC); -+TRACE_DEFINE_ENUM(SSAM_SSH_TC_KPD); -+TRACE_DEFINE_ENUM(SSAM_SSH_TC_REG); -+ -+ -+#define SSAM_PTR_UID_LEN 9 -+#define SSAM_U8_FIELD_NOT_APPLICABLE ((u16)-1) -+#define SSAM_SEQ_NOT_APPLICABLE ((u16)-1) -+#define SSAM_RQID_NOT_APPLICABLE ((u32)-1) -+#define SSAM_SSH_TC_NOT_APPLICABLE 0 -+ -+ -+#ifndef _SURFACE_SAM_SSH_TRACE_HELPERS -+#define _SURFACE_SAM_SSH_TRACE_HELPERS -+ -+/** -+ * ssam_trace_ptr_uid() - Convert the pointer to a non-pointer UID string. -+ * @ptr: The pointer to convert. -+ * @uid_str: A buffer of length SSAM_PTR_UID_LEN where the UID will be stored. -+ * -+ * Converts the given pointer into a UID string that is safe to be shared -+ * with userspace and logs, i.e. doesn't give away the real memory location. -+ */ -+static inline void ssam_trace_ptr_uid(const void *ptr, char *uid_str) -+{ -+ char buf[2 * sizeof(void *) + 1]; -+ -+ snprintf(buf, ARRAY_SIZE(buf), "%p", ptr); -+ memcpy(uid_str, &buf[ARRAY_SIZE(buf) - SSAM_PTR_UID_LEN], -+ SSAM_PTR_UID_LEN); -+} -+ -+/** -+ * ssam_trace_get_packet_seq() - Read the packet's sequence ID. -+ * @p: The packet. -+ * -+ * Return: Returns the packet's sequence ID (SEQ) field if present, or -+ * %SSAM_SEQ_NOT_APPLICABLE if not (e.g. flush packet). -+ */ -+static inline u16 ssam_trace_get_packet_seq(const struct ssh_packet *p) -+{ -+ if (!p->data.ptr || p->data.len < SSH_MESSAGE_LENGTH(0)) -+ return SSAM_SEQ_NOT_APPLICABLE; -+ -+ return p->data.ptr[SSH_MSGOFFSET_FRAME(seq)]; -+} -+ -+/** -+ * ssam_trace_get_request_id() - Read the packet's request ID. -+ * @p: The packet. -+ * -+ * Return: Returns the packet's request ID (RQID) field if the packet -+ * represents a request with command data, or %SSAM_RQID_NOT_APPLICABLE if not -+ * (e.g. flush request, control packet). -+ */ -+static inline u32 ssam_trace_get_request_id(const struct ssh_packet *p) -+{ -+ if (!p->data.ptr || p->data.len < SSH_COMMAND_MESSAGE_LENGTH(0)) -+ return SSAM_RQID_NOT_APPLICABLE; -+ -+ return get_unaligned_le16(&p->data.ptr[SSH_MSGOFFSET_COMMAND(rqid)]); -+} -+ -+/** -+ * ssam_trace_get_request_tc() - Read the packet's request target category. -+ * @p: The packet. -+ * -+ * Return: Returns the packet's request target category (TC) field if the -+ * packet represents a request with command data, or %SSAM_TC_NOT_APPLICABLE -+ * if not (e.g. flush request, control packet). -+ */ -+static inline u32 ssam_trace_get_request_tc(const struct ssh_packet *p) -+{ -+ if (!p->data.ptr || p->data.len < SSH_COMMAND_MESSAGE_LENGTH(0)) -+ return SSAM_SSH_TC_NOT_APPLICABLE; -+ -+ return get_unaligned_le16(&p->data.ptr[SSH_MSGOFFSET_COMMAND(tc)]); -+} -+ -+#endif /* _SURFACE_SAM_SSH_TRACE_HELPERS */ -+ -+#define ssam_trace_get_command_field_u8(packet, field) \ -+ ((!packet || packet->data.len < SSH_COMMAND_MESSAGE_LENGTH(0)) \ -+ ? 0 : p->data.ptr[SSH_MSGOFFSET_COMMAND(field)]) -+ -+#define ssam_show_generic_u8_field(value) \ -+ __print_symbolic(value, \ -+ { SSAM_U8_FIELD_NOT_APPLICABLE, "N/A" } \ -+ ) -+ -+ -+#define ssam_show_frame_type(ty) \ -+ __print_symbolic(ty, \ -+ { SSH_FRAME_TYPE_DATA_SEQ, "DSEQ" }, \ -+ { SSH_FRAME_TYPE_DATA_NSQ, "DNSQ" }, \ -+ { SSH_FRAME_TYPE_ACK, "ACK" }, \ -+ { SSH_FRAME_TYPE_NAK, "NAK" } \ -+ ) -+ -+#define ssam_show_packet_type(type) \ -+ __print_flags(flags & SSH_PACKET_FLAGS_TY_MASK, "", \ -+ { BIT(SSH_PACKET_TY_FLUSH_BIT), "F" }, \ -+ { BIT(SSH_PACKET_TY_SEQUENCED_BIT), "S" }, \ -+ { BIT(SSH_PACKET_TY_BLOCKING_BIT), "B" } \ -+ ) -+ -+#define ssam_show_packet_state(state) \ -+ __print_flags(flags & SSH_PACKET_FLAGS_SF_MASK, "", \ -+ { BIT(SSH_PACKET_SF_LOCKED_BIT), "L" }, \ -+ { BIT(SSH_PACKET_SF_QUEUED_BIT), "Q" }, \ -+ { BIT(SSH_PACKET_SF_PENDING_BIT), "P" }, \ -+ { BIT(SSH_PACKET_SF_TRANSMITTING_BIT), "S" }, \ -+ { BIT(SSH_PACKET_SF_TRANSMITTED_BIT), "T" }, \ -+ { BIT(SSH_PACKET_SF_ACKED_BIT), "A" }, \ -+ { BIT(SSH_PACKET_SF_CANCELED_BIT), "C" }, \ -+ { BIT(SSH_PACKET_SF_COMPLETED_BIT), "F" } \ -+ ) -+ -+#define ssam_show_packet_seq(seq) \ -+ __print_symbolic(seq, \ -+ { SSAM_SEQ_NOT_APPLICABLE, "N/A" } \ -+ ) -+ -+ -+#define ssam_show_request_type(flags) \ -+ __print_flags(flags & SSH_REQUEST_FLAGS_TY_MASK, "", \ -+ { BIT(SSH_REQUEST_TY_FLUSH_BIT), "F" }, \ -+ { BIT(SSH_REQUEST_TY_HAS_RESPONSE_BIT), "R" } \ -+ ) -+ -+#define ssam_show_request_state(flags) \ -+ __print_flags(flags & SSH_REQUEST_FLAGS_SF_MASK, "", \ -+ { BIT(SSH_REQUEST_SF_LOCKED_BIT), "L" }, \ -+ { BIT(SSH_REQUEST_SF_QUEUED_BIT), "Q" }, \ -+ { BIT(SSH_REQUEST_SF_PENDING_BIT), "P" }, \ -+ { BIT(SSH_REQUEST_SF_TRANSMITTING_BIT), "S" }, \ -+ { BIT(SSH_REQUEST_SF_TRANSMITTED_BIT), "T" }, \ -+ { BIT(SSH_REQUEST_SF_RSPRCVD_BIT), "A" }, \ -+ { BIT(SSH_REQUEST_SF_CANCELED_BIT), "C" }, \ -+ { BIT(SSH_REQUEST_SF_COMPLETED_BIT), "F" } \ -+ ) -+ -+#define ssam_show_request_id(rqid) \ -+ __print_symbolic(rqid, \ -+ { SSAM_RQID_NOT_APPLICABLE, "N/A" } \ -+ ) -+ -+#define ssam_show_ssh_tc(rqid) \ -+ __print_symbolic(rqid, \ -+ { SSAM_SSH_TC_NOT_APPLICABLE, "N/A" }, \ -+ { SSAM_SSH_TC_SAM, "SAM" }, \ -+ { SSAM_SSH_TC_BAT, "BAT" }, \ -+ { SSAM_SSH_TC_TMP, "TMP" }, \ -+ { SSAM_SSH_TC_PMC, "PMC" }, \ -+ { SSAM_SSH_TC_FAN, "FAN" }, \ -+ { SSAM_SSH_TC_PoM, "PoM" }, \ -+ { SSAM_SSH_TC_DBG, "DBG" }, \ -+ { SSAM_SSH_TC_KBD, "KBD" }, \ -+ { SSAM_SSH_TC_FWU, "FWU" }, \ -+ { SSAM_SSH_TC_UNI, "UNI" }, \ -+ { SSAM_SSH_TC_LPC, "LPC" }, \ -+ { SSAM_SSH_TC_TCL, "TCL" }, \ -+ { SSAM_SSH_TC_SFL, "SFL" }, \ -+ { SSAM_SSH_TC_KIP, "KIP" }, \ -+ { SSAM_SSH_TC_EXT, "EXT" }, \ -+ { SSAM_SSH_TC_BLD, "BLD" }, \ -+ { SSAM_SSH_TC_BAS, "BAS" }, \ -+ { SSAM_SSH_TC_SEN, "SEN" }, \ -+ { SSAM_SSH_TC_SRQ, "SRQ" }, \ -+ { SSAM_SSH_TC_MCU, "MCU" }, \ -+ { SSAM_SSH_TC_HID, "HID" }, \ -+ { SSAM_SSH_TC_TCH, "TCH" }, \ -+ { SSAM_SSH_TC_BKL, "BKL" }, \ -+ { SSAM_SSH_TC_TAM, "TAM" }, \ -+ { SSAM_SSH_TC_ACC, "ACC" }, \ -+ { SSAM_SSH_TC_UFI, "UFI" }, \ -+ { SSAM_SSH_TC_USC, "USC" }, \ -+ { SSAM_SSH_TC_PEN, "PEN" }, \ -+ { SSAM_SSH_TC_VID, "VID" }, \ -+ { SSAM_SSH_TC_AUD, "AUD" }, \ -+ { SSAM_SSH_TC_SMC, "SMC" }, \ -+ { SSAM_SSH_TC_KPD, "KPD" }, \ -+ { SSAM_SSH_TC_REG, "REG" } \ -+ ) -+ -+ -+DECLARE_EVENT_CLASS(ssam_frame_class, -+ TP_PROTO(const struct ssh_frame *frame), -+ -+ TP_ARGS(frame), -+ -+ TP_STRUCT__entry( -+ __field(u8, type) -+ __field(u8, seq) -+ __field(u16, len) -+ ), -+ -+ TP_fast_assign( -+ __entry->type = frame->type; -+ __entry->seq = frame->seq; -+ __entry->len = get_unaligned_le16(&frame->len); -+ ), -+ -+ TP_printk("ty=%s, seq=0x%02x, len=%u", -+ ssam_show_frame_type(__entry->type), -+ __entry->seq, -+ __entry->len -+ ) -+); -+ -+#define DEFINE_SSAM_FRAME_EVENT(name) \ -+ DEFINE_EVENT(ssam_frame_class, ssam_##name, \ -+ TP_PROTO(const struct ssh_frame *frame), \ -+ TP_ARGS(frame) \ -+ ) -+ -+ -+DECLARE_EVENT_CLASS(ssam_command_class, -+ TP_PROTO(const struct ssh_command *cmd, u16 len), -+ -+ TP_ARGS(cmd, len), -+ -+ TP_STRUCT__entry( -+ __field(u16, rqid) -+ __field(u16, len) -+ __field(u8, tc) -+ __field(u8, cid) -+ __field(u8, iid) -+ ), -+ -+ TP_fast_assign( -+ __entry->rqid = get_unaligned_le16(&cmd->rqid); -+ __entry->tc = cmd->tc; -+ __entry->cid = cmd->cid; -+ __entry->iid = cmd->iid; -+ __entry->len = len; -+ ), -+ -+ TP_printk("rqid=0x%04x, tc=%s, cid=0x%02x, iid=0x%02x, len=%u", -+ __entry->rqid, -+ ssam_show_ssh_tc(__entry->tc), -+ __entry->cid, -+ __entry->iid, -+ __entry->len -+ ) -+); -+ -+#define DEFINE_SSAM_COMMAND_EVENT(name) \ -+ DEFINE_EVENT(ssam_command_class, ssam_##name, \ -+ TP_PROTO(const struct ssh_command *cmd, u16 len), \ -+ TP_ARGS(cmd, len) \ -+ ) -+ -+ -+DECLARE_EVENT_CLASS(ssam_packet_class, -+ TP_PROTO(const struct ssh_packet *packet), -+ -+ TP_ARGS(packet), -+ -+ TP_STRUCT__entry( -+ __array(char, uid, SSAM_PTR_UID_LEN) -+ __field(u8, priority) -+ __field(u16, length) -+ __field(unsigned long, state) -+ __field(u16, seq) -+ ), -+ -+ TP_fast_assign( -+ ssam_trace_ptr_uid(packet, __entry->uid); -+ __entry->priority = READ_ONCE(packet->priority); -+ __entry->length = packet->data.len; -+ __entry->state = READ_ONCE(packet->state); -+ __entry->seq = ssam_trace_get_packet_seq(packet); -+ ), -+ -+ TP_printk("uid=%s, seq=%s, ty=%s, pri=0x%02x, len=%u, sta=%s", -+ __entry->uid, -+ ssam_show_packet_seq(__entry->seq), -+ ssam_show_packet_type(__entry->state), -+ __entry->priority, -+ __entry->length, -+ ssam_show_packet_state(__entry->state) -+ ) -+); -+ -+#define DEFINE_SSAM_PACKET_EVENT(name) \ -+ DEFINE_EVENT(ssam_packet_class, ssam_##name, \ -+ TP_PROTO(const struct ssh_packet *packet), \ -+ TP_ARGS(packet) \ -+ ) -+ -+ -+DECLARE_EVENT_CLASS(ssam_packet_status_class, -+ TP_PROTO(const struct ssh_packet *packet, int status), -+ -+ TP_ARGS(packet, status), -+ -+ TP_STRUCT__entry( -+ __array(char, uid, SSAM_PTR_UID_LEN) -+ __field(u8, priority) -+ __field(u16, length) -+ __field(unsigned long, state) -+ __field(u16, seq) -+ __field(int, status) -+ ), -+ -+ TP_fast_assign( -+ ssam_trace_ptr_uid(packet, __entry->uid); -+ __entry->priority = READ_ONCE(packet->priority); -+ __entry->length = packet->data.len; -+ __entry->state = READ_ONCE(packet->state); -+ __entry->seq = ssam_trace_get_packet_seq(packet); -+ __entry->status = status; -+ ), -+ -+ TP_printk("uid=%s, seq=%s, ty=%s, pri=0x%02x, len=%u, sta=%s, status=%d", -+ __entry->uid, -+ ssam_show_packet_seq(__entry->seq), -+ ssam_show_packet_type(__entry->state), -+ __entry->priority, -+ __entry->length, -+ ssam_show_packet_state(__entry->state), -+ __entry->status -+ ) -+); -+ -+#define DEFINE_SSAM_PACKET_STATUS_EVENT(name) \ -+ DEFINE_EVENT(ssam_packet_status_class, ssam_##name, \ -+ TP_PROTO(const struct ssh_packet *packet, int status), \ -+ TP_ARGS(packet, status) \ -+ ) -+ -+ -+DECLARE_EVENT_CLASS(ssam_request_class, -+ TP_PROTO(const struct ssh_request *request), -+ -+ TP_ARGS(request), -+ -+ TP_STRUCT__entry( -+ __array(char, uid, SSAM_PTR_UID_LEN) -+ __field(unsigned long, state) -+ __field(u32, rqid) -+ __field(u8, tc) -+ __field(u16, cid) -+ __field(u16, iid) -+ ), -+ -+ TP_fast_assign( -+ const struct ssh_packet *p = &request->packet; -+ -+ // use packet for UID so we can match requests to packets -+ ssam_trace_ptr_uid(p, __entry->uid); -+ __entry->state = READ_ONCE(request->state); -+ __entry->rqid = ssam_trace_get_request_id(p); -+ __entry->tc = ssam_trace_get_request_tc(p); -+ __entry->cid = ssam_trace_get_command_field_u8(p, cid); -+ __entry->iid = ssam_trace_get_command_field_u8(p, iid); -+ ), -+ -+ TP_printk("uid=%s, rqid=%s, ty=%s, sta=%s, tc=%s, cid=%s, iid=%s", -+ __entry->uid, -+ ssam_show_request_id(__entry->rqid), -+ ssam_show_request_type(__entry->state), -+ ssam_show_request_state(__entry->state), -+ ssam_show_ssh_tc(__entry->tc), -+ ssam_show_generic_u8_field(__entry->cid), -+ ssam_show_generic_u8_field(__entry->iid) -+ ) -+); -+ -+#define DEFINE_SSAM_REQUEST_EVENT(name) \ -+ DEFINE_EVENT(ssam_request_class, ssam_##name, \ -+ TP_PROTO(const struct ssh_request *request), \ -+ TP_ARGS(request) \ -+ ) -+ -+ -+DECLARE_EVENT_CLASS(ssam_request_status_class, -+ TP_PROTO(const struct ssh_request *request, int status), -+ -+ TP_ARGS(request, status), -+ -+ TP_STRUCT__entry( -+ __array(char, uid, SSAM_PTR_UID_LEN) -+ __field(unsigned long, state) -+ __field(u32, rqid) -+ __field(u8, tc) -+ __field(u16, cid) -+ __field(u16, iid) -+ __field(int, status) -+ ), -+ -+ TP_fast_assign( -+ const struct ssh_packet *p = &request->packet; -+ -+ // use packet for UID so we can match requests to packets -+ ssam_trace_ptr_uid(p, __entry->uid); -+ __entry->state = READ_ONCE(request->state); -+ __entry->rqid = ssam_trace_get_request_id(p); -+ __entry->tc = ssam_trace_get_request_tc(p); -+ __entry->cid = ssam_trace_get_command_field_u8(p, cid); -+ __entry->iid = ssam_trace_get_command_field_u8(p, iid); -+ __entry->status = status; -+ ), -+ -+ TP_printk("uid=%s, rqid=%s, ty=%s, sta=%s, tc=%s, cid=%s, iid=%s, status=%d", -+ __entry->uid, -+ ssam_show_request_id(__entry->rqid), -+ ssam_show_request_type(__entry->state), -+ ssam_show_request_state(__entry->state), -+ ssam_show_ssh_tc(__entry->tc), -+ ssam_show_generic_u8_field(__entry->cid), -+ ssam_show_generic_u8_field(__entry->iid), -+ __entry->status -+ ) -+); -+ -+#define DEFINE_SSAM_REQUEST_STATUS_EVENT(name) \ -+ DEFINE_EVENT(ssam_request_status_class, ssam_##name, \ -+ TP_PROTO(const struct ssh_request *request, int status),\ -+ TP_ARGS(request, status) \ -+ ) -+ -+ -+DECLARE_EVENT_CLASS(ssam_alloc_class, -+ TP_PROTO(void *ptr, size_t len), -+ -+ TP_ARGS(ptr, len), -+ -+ TP_STRUCT__entry( -+ __array(char, uid, SSAM_PTR_UID_LEN) -+ __field(size_t, len) -+ ), -+ -+ TP_fast_assign( -+ ssam_trace_ptr_uid(ptr, __entry->uid); -+ __entry->len = len; -+ ), -+ -+ TP_printk("uid=%s, len=%zu", __entry->uid, __entry->len) -+); -+ -+#define DEFINE_SSAM_ALLOC_EVENT(name) \ -+ DEFINE_EVENT(ssam_alloc_class, ssam_##name, \ -+ TP_PROTO(void *ptr, size_t len), \ -+ TP_ARGS(ptr, len) \ -+ ) -+ -+ -+DECLARE_EVENT_CLASS(ssam_free_class, -+ TP_PROTO(void *ptr), -+ -+ TP_ARGS(ptr), -+ -+ TP_STRUCT__entry( -+ __array(char, uid, SSAM_PTR_UID_LEN) -+ __field(size_t, len) -+ ), -+ -+ TP_fast_assign( -+ ssam_trace_ptr_uid(ptr, __entry->uid); -+ ), -+ -+ TP_printk("uid=%s", __entry->uid) -+); -+ -+#define DEFINE_SSAM_FREE_EVENT(name) \ -+ DEFINE_EVENT(ssam_free_class, ssam_##name, \ -+ TP_PROTO(void *ptr), \ -+ TP_ARGS(ptr) \ -+ ) -+ -+ -+DECLARE_EVENT_CLASS(ssam_generic_uint_class, -+ TP_PROTO(const char *property, unsigned int value), -+ -+ TP_ARGS(property, value), -+ -+ TP_STRUCT__entry( -+ __string(property, property) -+ __field(unsigned int, value) -+ ), -+ -+ TP_fast_assign( -+ __assign_str(property, property); -+ __entry->value = value; -+ ), -+ -+ TP_printk("%s=%u", __get_str(property), __entry->value) -+); -+ -+#define DEFINE_SSAM_GENERIC_UINT_EVENT(name) \ -+ DEFINE_EVENT(ssam_generic_uint_class, ssam_##name, \ -+ TP_PROTO(const char *property, unsigned int value), \ -+ TP_ARGS(property, value) \ -+ ) -+ -+ -+DEFINE_SSAM_FRAME_EVENT(rx_frame_received); -+DEFINE_SSAM_COMMAND_EVENT(rx_response_received); -+DEFINE_SSAM_COMMAND_EVENT(rx_event_received); -+ -+DEFINE_SSAM_PACKET_EVENT(packet_release); -+DEFINE_SSAM_PACKET_EVENT(packet_submit); -+DEFINE_SSAM_PACKET_EVENT(packet_resubmit); -+DEFINE_SSAM_PACKET_EVENT(packet_timeout); -+DEFINE_SSAM_PACKET_EVENT(packet_cancel); -+DEFINE_SSAM_PACKET_STATUS_EVENT(packet_complete); -+DEFINE_SSAM_GENERIC_UINT_EVENT(ptl_timeout_reap); -+ -+DEFINE_SSAM_REQUEST_EVENT(request_submit); -+DEFINE_SSAM_REQUEST_EVENT(request_timeout); -+DEFINE_SSAM_REQUEST_EVENT(request_cancel); -+DEFINE_SSAM_REQUEST_STATUS_EVENT(request_complete); -+DEFINE_SSAM_GENERIC_UINT_EVENT(rtl_timeout_reap); -+ -+DEFINE_SSAM_PACKET_EVENT(ei_tx_drop_ack_packet); -+DEFINE_SSAM_PACKET_EVENT(ei_tx_drop_nak_packet); -+DEFINE_SSAM_PACKET_EVENT(ei_tx_drop_dsq_packet); -+DEFINE_SSAM_PACKET_STATUS_EVENT(ei_tx_fail_write); -+DEFINE_SSAM_PACKET_EVENT(ei_tx_corrupt_data); -+DEFINE_SSAM_GENERIC_UINT_EVENT(ei_rx_corrupt_syn); -+DEFINE_SSAM_FRAME_EVENT(ei_rx_corrupt_data); -+DEFINE_SSAM_REQUEST_EVENT(ei_rx_drop_response); -+ -+DEFINE_SSAM_ALLOC_EVENT(ctrl_packet_alloc); -+DEFINE_SSAM_FREE_EVENT(ctrl_packet_free); -+ -+DEFINE_SSAM_ALLOC_EVENT(event_item_alloc); -+DEFINE_SSAM_FREE_EVENT(event_item_free); -+ -+#endif /* _SURFACE_SAM_SSH_TRACE_H */ -+ -+/* This part must be outside protection */ -+#undef TRACE_INCLUDE_PATH -+#undef TRACE_INCLUDE_FILE -+ -+#define TRACE_INCLUDE_PATH . -+#define TRACE_INCLUDE_FILE ssam_trace -+ -+#include -diff --git a/drivers/misc/surface_sam/ssh_msgb.h b/drivers/misc/surface_sam/ssh_msgb.h -new file mode 100644 -index 000000000000..4b37c5577897 ---- /dev/null -+++ b/drivers/misc/surface_sam/ssh_msgb.h ++++ b/drivers/misc/surface_aggregator/ssh_msgb.h @@ -0,0 +1,196 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + -+#ifndef _SSAM_SSH_MSGB_H -+#define _SSAM_SSH_MSGB_H ++#ifndef _SURFACE_AGGREGATOR_SSH_MSGB_H ++#define _SURFACE_AGGREGATOR_SSH_MSGB_H + +#include +#include + -+#include -+#include "ssh_protocol.h" ++#include ++#include + + +/** @@ -12144,13 +11687,13 @@ index 000000000000..4b37c5577897 + 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 ++#endif /* _SURFACE_AGGREGATOR_SSH_MSGB_H */ +diff --git a/drivers/misc/surface_aggregator/ssh_packet_layer.c b/drivers/misc/surface_aggregator/ssh_packet_layer.c new file mode 100644 -index 000000000000..21a8f7e4473b +index 000000000000..3cc1e9e24a07 --- /dev/null -+++ b/drivers/misc/surface_sam/ssh_packet_layer.c -@@ -0,0 +1,2001 @@ ++++ b/drivers/misc/surface_aggregator/ssh_packet_layer.c +@@ -0,0 +1,2002 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#include @@ -12167,12 +11710,13 @@ index 000000000000..21a8f7e4473b +#include +#include + -+#include ++#include + ++#include "ssh_msgb.h" +#include "ssh_packet_layer.h" -+#include "ssh_protocol.h" ++#include "ssh_parser.h" + -+#include "ssam_trace.h" ++#include "trace.h" + + +/* @@ -12360,7 +11904,7 @@ index 000000000000..21a8f7e4473b +#define SSH_PTL_RX_FIFO_LEN 4096 + + -+#ifdef CONFIG_SURFACE_SAM_SSH_ERROR_INJECTION ++#ifdef CONFIG_SURFACE_AGGREGATOR_ERROR_INJECTION + +/** + * ssh_ptl_should_drop_ack_packet() - Error injection hook to drop ACK packets. @@ -12460,7 +12004,7 @@ index 000000000000..21a8f7e4473b +ALLOW_ERROR_INJECTION(ssh_ptl_should_corrupt_rx_data, TRUE); + + -+static inline bool __ssh_ptl_should_drop_ack_packet(struct ssh_packet *packet) ++static bool __ssh_ptl_should_drop_ack_packet(struct ssh_packet *packet) +{ + if (likely(!ssh_ptl_should_drop_ack_packet())) + return false; @@ -12472,7 +12016,7 @@ index 000000000000..21a8f7e4473b + return true; +} + -+static inline bool __ssh_ptl_should_drop_nak_packet(struct ssh_packet *packet) ++static bool __ssh_ptl_should_drop_nak_packet(struct ssh_packet *packet) +{ + if (likely(!ssh_ptl_should_drop_nak_packet())) + return false; @@ -12484,7 +12028,7 @@ index 000000000000..21a8f7e4473b + return true; +} + -+static inline bool __ssh_ptl_should_drop_dsq_packet(struct ssh_packet *packet) ++static bool __ssh_ptl_should_drop_dsq_packet(struct ssh_packet *packet) +{ + if (likely(!ssh_ptl_should_drop_dsq_packet())) + return false; @@ -12610,7 +12154,7 @@ index 000000000000..21a8f7e4473b + frame->ptr[frame->len - 2] = ~frame->ptr[frame->len - 2]; +} + -+#else /* CONFIG_SURFACE_SAM_SSH_ERROR_INJECTION */ ++#else /* CONFIG_SURFACE_AGGREGATOR_ERROR_INJECTION */ + +static inline bool ssh_ptl_should_drop_packet(struct ssh_packet *packet) +{ @@ -12639,7 +12183,7 @@ index 000000000000..21a8f7e4473b +{ +} + -+#endif /* CONFIG_SURFACE_SAM_SSH_ERROR_INJECTION */ ++#endif /* CONFIG_SURFACE_AGGREGATOR_ERROR_INJECTION */ + + +static void __ssh_ptl_packet_release(struct kref *kref) @@ -12684,7 +12228,7 @@ index 000000000000..21a8f7e4473b +} +EXPORT_SYMBOL_GPL(ssh_packet_put); + -+static inline u8 ssh_packet_get_seq(struct ssh_packet *packet) ++static u8 ssh_packet_get_seq(struct ssh_packet *packet) +{ + return packet->data.ptr[SSH_MSGOFFSET_FRAME(seq)]; +} @@ -13896,7 +13440,7 @@ index 000000000000..21a8f7e4473b + return 0; +} + -+static inline void ssh_ptl_rx_wakeup(struct ssh_ptl *ptl) ++static void ssh_ptl_rx_wakeup(struct ssh_ptl *ptl) +{ + wake_up(&ptl->rx.wq); +} @@ -14152,31 +13696,28 @@ index 000000000000..21a8f7e4473b + 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 +diff --git a/drivers/misc/surface_aggregator/ssh_packet_layer.h b/drivers/misc/surface_aggregator/ssh_packet_layer.h new file mode 100644 -index 000000000000..2e2d961c5c04 +index 000000000000..413388d256ff --- /dev/null -+++ b/drivers/misc/surface_sam/ssh_packet_layer.h -@@ -0,0 +1,173 @@ ++++ b/drivers/misc/surface_aggregator/ssh_packet_layer.h +@@ -0,0 +1,170 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + -+#ifndef _SSAM_SSH_PACKET_LAYER_H -+#define _SSAM_SSH_PACKET_LAYER_H ++#ifndef _SURFACE_AGGREGATOR_SSH_PACKET_LAYER_H ++#define _SURFACE_AGGREGATOR_SSH_PACKET_LAYER_H + +#include +#include +#include +#include -+#include +#include +#include +#include +#include +#include + -+#include -+ -+#include "ssh_msgb.h" ++#include +#include "ssh_parser.h" + + @@ -14330,19 +13871,22 @@ index 000000000000..2e2d961c5c04 +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 ++#endif /* _SURFACE_AGGREGATOR_SSH_PACKET_LAYER_H */ +diff --git a/drivers/misc/surface_aggregator/ssh_parser.c b/drivers/misc/surface_aggregator/ssh_parser.c new file mode 100644 -index 000000000000..3e28d502d5eb +index 000000000000..7d9f7de74100 --- /dev/null -+++ b/drivers/misc/surface_sam/ssh_parser.c -@@ -0,0 +1,221 @@ ++++ b/drivers/misc/surface_aggregator/ssh_parser.c +@@ -0,0 +1,224 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + ++#include +#include ++#include ++#include + ++#include +#include "ssh_parser.h" -+#include "ssh_protocol.h" + + +/** @@ -14358,7 +13902,7 @@ index 000000000000..3e28d502d5eb + * Return: Returns %true iff the computed CRC matches the stored CRC, %false + * otherwise. + */ -+static inline bool sshp_validate_crc(const struct ssam_span *src, const u8 *crc) ++static 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); @@ -14370,7 +13914,7 @@ index 000000000000..3e28d502d5eb + * sshp_starts_with_syn() - Check if the given data starts with SSH SYN bytes. + * @src: The data span to check the start of. + */ -+static inline bool sshp_starts_with_syn(const struct ssam_span *src) ++static bool sshp_starts_with_syn(const struct ssam_span *src) +{ + return src->len >= 2 && get_unaligned_le16(src->ptr) == SSH_MSG_SYN; +} @@ -14558,22 +14102,23 @@ index 000000000000..3e28d502d5eb + + return 0; +} -diff --git a/drivers/misc/surface_sam/ssh_parser.h b/drivers/misc/surface_sam/ssh_parser.h +diff --git a/drivers/misc/surface_aggregator/ssh_parser.h b/drivers/misc/surface_aggregator/ssh_parser.h new file mode 100644 -index 000000000000..1ee455de7898 +index 000000000000..a766223b18e7 --- /dev/null -+++ b/drivers/misc/surface_sam/ssh_parser.h -@@ -0,0 +1,151 @@ ++++ b/drivers/misc/surface_aggregator/ssh_parser.h +@@ -0,0 +1,152 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + -+#ifndef _SURFACE_SAM_SSH_PARSER_H -+#define _SURFACE_SAM_SSH_PARSER_H ++#ifndef _SURFACE_AGGREGATOR_SSH_PARSER_H ++#define _SURFACE_AGGREGATOR_SSH_PARSER_H + +#include +#include +#include ++#include + -+#include ++#include + + +/** @@ -14714,122 +14259,13 @@ index 000000000000..1ee455de7898 + 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 ++#endif /* _SURFACE_AGGREGATOR_SSH_PARSER_h */ +diff --git a/drivers/misc/surface_aggregator/ssh_request_layer.c b/drivers/misc/surface_aggregator/ssh_request_layer.c new file mode 100644 -index 000000000000..4f35ce93f02e +index 000000000000..bd80503872d0 --- /dev/null -+++ b/drivers/misc/surface_sam/ssh_protocol.h -@@ -0,0 +1,103 @@ -+/* SPDX-License-Identifier: GPL-2.0-or-later */ -+ -+#ifndef _SSAM_SSH_PROTOCOL_H -+#define _SSAM_SSH_PROTOCOL_H -+ -+#include -+#include -+#include -+ -+ -+/* -+ * SSH_NUM_EVENTS - The number of reserved event IDs. -+ * -+ * 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 -+ -+/* -+ * SSH_NUM_TARGETS - The number of communication targets used in the protocol. -+ */ -+#define SSH_NUM_TARGETS 2 -+ -+/* -+ * SSH_MSG_SYN - SSH message synchronization (SYN) bytes as u16. -+ */ -+#define SSH_MSG_SYN ((u16)0x55aa) -+ -+ -+/** -+ * ssh_crc() - Compute CRC for SSH messages. -+ * @buf: The pointer pointing to the data for which the CRC should be computed. -+ * @len: The length of the data for which the CRC should be computed. -+ * -+ * Return: Returns the CRC computed on the provided data, as used for SSH -+ * messages. -+ */ -+static inline u16 ssh_crc(const u8 *buf, size_t len) -+{ -+ return crc_ccitt_false(0xffff, buf, len); -+} -+ -+/** -+ * ssh_rqid_next_valid() - Return the next valid request ID. -+ * @rqid: The current request ID. -+ * -+ * Return: Returns the next valid request ID, following the current request ID -+ * provided to this function. This function skips any request IDs reserved for -+ * events. -+ */ -+static inline u16 ssh_rqid_next_valid(u16 rqid) -+{ -+ return rqid > 0 ? rqid + 1u : rqid + SSH_NUM_EVENTS + 1u; -+} -+ -+/** -+ * ssh_rqid_to_event() - Convert request ID to its corresponding event ID. -+ * @rqid: The request ID to convert. -+ */ -+static inline u16 ssh_rqid_to_event(u16 rqid) -+{ -+ return rqid - 1u; -+} -+ -+/** -+ * ssh_rqid_is_event() - Check if given request ID is a valid event ID. -+ * @rqid: The request ID to check. -+ */ -+static inline bool ssh_rqid_is_event(u16 rqid) -+{ -+ return ssh_rqid_to_event(rqid) < SSH_NUM_EVENTS; -+} -+ -+/** -+ * ssh_tc_to_rqid() - Convert target category to its corresponding request ID. -+ * @tc: The target category to convert. -+ */ -+static inline u16 ssh_tc_to_rqid(u8 tc) -+{ -+ return tc; -+} -+ -+/** -+ * ssh_tid_to_index() - Convert target ID to its corresponding target index. -+ * @tid: The target ID to convert. -+ */ -+static inline u8 ssh_tid_to_index(u8 tid) -+{ -+ return tid - 1u; -+} -+ -+/** -+ * ssh_tid_is_valid() - Check if target ID is valid/supported. -+ * @tid: The target ID to check. -+ */ -+static inline bool ssh_tid_is_valid(u8 tid) -+{ -+ return ssh_tid_to_index(tid) < SSH_NUM_TARGETS; -+} -+ -+#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 000000000000..24aaf6da5b54 ---- /dev/null -+++ b/drivers/misc/surface_sam/ssh_request_layer.c -@@ -0,0 +1,1246 @@ ++++ b/drivers/misc/surface_aggregator/ssh_request_layer.c +@@ -0,0 +1,1247 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#include @@ -14841,12 +14277,13 @@ index 000000000000..24aaf6da5b54 +#include +#include + -+#include ++#include ++#include + +#include "ssh_packet_layer.h" +#include "ssh_request_layer.h" + -+#include "ssam_trace.h" ++#include "trace.h" + + +/* @@ -14877,7 +14314,7 @@ index 000000000000..24aaf6da5b54 +#define SSH_RTL_MAX_PENDING 3 + + -+#ifdef CONFIG_SURFACE_SAM_SSH_ERROR_INJECTION ++#ifdef CONFIG_SURFACE_AGGREGATOR_ERROR_INJECTION + +/** + * ssh_rtl_should_drop_response() - Error injection hook to drop request @@ -14902,13 +14339,13 @@ index 000000000000..24aaf6da5b54 +#endif + + -+static inline u16 ssh_request_get_rqid(struct ssh_request *rqst) ++static 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) ++static u32 ssh_request_get_rqid_safe(struct ssh_request *rqst) +{ + if (!rqst->packet.data.ptr) + return (u32)-1; @@ -16076,16 +15513,16 @@ index 000000000000..24aaf6da5b54 + ssh_request_put(r); + } +} -diff --git a/drivers/misc/surface_sam/ssh_request_layer.h b/drivers/misc/surface_sam/ssh_request_layer.h +diff --git a/drivers/misc/surface_aggregator/ssh_request_layer.h b/drivers/misc/surface_aggregator/ssh_request_layer.h new file mode 100644 -index 000000000000..954f571862a6 +index 000000000000..cfcfeb0c2c56 --- /dev/null -+++ b/drivers/misc/surface_sam/ssh_request_layer.h -@@ -0,0 +1,136 @@ ++++ b/drivers/misc/surface_aggregator/ssh_request_layer.h +@@ -0,0 +1,137 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + -+#ifndef _SSAM_SSH_REQUEST_LAYER_H -+#define _SSAM_SSH_REQUEST_LAYER_H ++#ifndef _SURFACE_AGGREGATOR_SSH_REQUEST_LAYER_H ++#define _SURFACE_AGGREGATOR_SSH_REQUEST_LAYER_H + +#include +#include @@ -16093,7 +15530,8 @@ index 000000000000..954f571862a6 +#include +#include + -+#include ++#include ++#include + +#include "ssh_packet_layer.h" + @@ -16217,7 +15655,634 @@ index 000000000000..954f571862a6 +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 */ ++#endif /* _SURFACE_AGGREGATOR_SSH_REQUEST_LAYER_H */ +diff --git a/drivers/misc/surface_aggregator/trace.h b/drivers/misc/surface_aggregator/trace.h +new file mode 100644 +index 000000000000..e99e35a2d939 +--- /dev/null ++++ b/drivers/misc/surface_aggregator/trace.h +@@ -0,0 +1,621 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++ ++#undef TRACE_SYSTEM ++#define TRACE_SYSTEM surface_aggregator ++ ++#if !defined(_SURFACE_AGGREGATOR_TRACE_H) || defined(TRACE_HEADER_MULTI_READ) ++#define _SURFACE_AGGREGATOR_TRACE_H ++ ++#include ++ ++#include ++#include ++ ++ ++TRACE_DEFINE_ENUM(SSH_FRAME_TYPE_DATA_SEQ); ++TRACE_DEFINE_ENUM(SSH_FRAME_TYPE_DATA_NSQ); ++TRACE_DEFINE_ENUM(SSH_FRAME_TYPE_ACK); ++TRACE_DEFINE_ENUM(SSH_FRAME_TYPE_NAK); ++ ++TRACE_DEFINE_ENUM(SSH_PACKET_SF_LOCKED_BIT); ++TRACE_DEFINE_ENUM(SSH_PACKET_SF_QUEUED_BIT); ++TRACE_DEFINE_ENUM(SSH_PACKET_SF_PENDING_BIT); ++TRACE_DEFINE_ENUM(SSH_PACKET_SF_TRANSMITTING_BIT); ++TRACE_DEFINE_ENUM(SSH_PACKET_SF_TRANSMITTED_BIT); ++TRACE_DEFINE_ENUM(SSH_PACKET_SF_ACKED_BIT); ++TRACE_DEFINE_ENUM(SSH_PACKET_SF_CANCELED_BIT); ++TRACE_DEFINE_ENUM(SSH_PACKET_SF_COMPLETED_BIT); ++ ++TRACE_DEFINE_ENUM(SSH_PACKET_TY_FLUSH_BIT); ++TRACE_DEFINE_ENUM(SSH_PACKET_TY_SEQUENCED_BIT); ++TRACE_DEFINE_ENUM(SSH_PACKET_TY_BLOCKING_BIT); ++ ++TRACE_DEFINE_ENUM(SSH_PACKET_FLAGS_SF_MASK); ++TRACE_DEFINE_ENUM(SSH_PACKET_FLAGS_TY_MASK); ++ ++TRACE_DEFINE_ENUM(SSH_REQUEST_SF_LOCKED_BIT); ++TRACE_DEFINE_ENUM(SSH_REQUEST_SF_QUEUED_BIT); ++TRACE_DEFINE_ENUM(SSH_REQUEST_SF_PENDING_BIT); ++TRACE_DEFINE_ENUM(SSH_REQUEST_SF_TRANSMITTING_BIT); ++TRACE_DEFINE_ENUM(SSH_REQUEST_SF_TRANSMITTED_BIT); ++TRACE_DEFINE_ENUM(SSH_REQUEST_SF_RSPRCVD_BIT); ++TRACE_DEFINE_ENUM(SSH_REQUEST_SF_CANCELED_BIT); ++TRACE_DEFINE_ENUM(SSH_REQUEST_SF_COMPLETED_BIT); ++ ++TRACE_DEFINE_ENUM(SSH_REQUEST_TY_FLUSH_BIT); ++TRACE_DEFINE_ENUM(SSH_REQUEST_TY_HAS_RESPONSE_BIT); ++ ++TRACE_DEFINE_ENUM(SSH_REQUEST_FLAGS_SF_MASK); ++TRACE_DEFINE_ENUM(SSH_REQUEST_FLAGS_TY_MASK); ++ ++TRACE_DEFINE_ENUM(SSAM_SSH_TC_SAM); ++TRACE_DEFINE_ENUM(SSAM_SSH_TC_BAT); ++TRACE_DEFINE_ENUM(SSAM_SSH_TC_TMP); ++TRACE_DEFINE_ENUM(SSAM_SSH_TC_PMC); ++TRACE_DEFINE_ENUM(SSAM_SSH_TC_FAN); ++TRACE_DEFINE_ENUM(SSAM_SSH_TC_PoM); ++TRACE_DEFINE_ENUM(SSAM_SSH_TC_DBG); ++TRACE_DEFINE_ENUM(SSAM_SSH_TC_KBD); ++TRACE_DEFINE_ENUM(SSAM_SSH_TC_FWU); ++TRACE_DEFINE_ENUM(SSAM_SSH_TC_UNI); ++TRACE_DEFINE_ENUM(SSAM_SSH_TC_LPC); ++TRACE_DEFINE_ENUM(SSAM_SSH_TC_TCL); ++TRACE_DEFINE_ENUM(SSAM_SSH_TC_SFL); ++TRACE_DEFINE_ENUM(SSAM_SSH_TC_KIP); ++TRACE_DEFINE_ENUM(SSAM_SSH_TC_EXT); ++TRACE_DEFINE_ENUM(SSAM_SSH_TC_BLD); ++TRACE_DEFINE_ENUM(SSAM_SSH_TC_BAS); ++TRACE_DEFINE_ENUM(SSAM_SSH_TC_SEN); ++TRACE_DEFINE_ENUM(SSAM_SSH_TC_SRQ); ++TRACE_DEFINE_ENUM(SSAM_SSH_TC_MCU); ++TRACE_DEFINE_ENUM(SSAM_SSH_TC_HID); ++TRACE_DEFINE_ENUM(SSAM_SSH_TC_TCH); ++TRACE_DEFINE_ENUM(SSAM_SSH_TC_BKL); ++TRACE_DEFINE_ENUM(SSAM_SSH_TC_TAM); ++TRACE_DEFINE_ENUM(SSAM_SSH_TC_ACC); ++TRACE_DEFINE_ENUM(SSAM_SSH_TC_UFI); ++TRACE_DEFINE_ENUM(SSAM_SSH_TC_USC); ++TRACE_DEFINE_ENUM(SSAM_SSH_TC_PEN); ++TRACE_DEFINE_ENUM(SSAM_SSH_TC_VID); ++TRACE_DEFINE_ENUM(SSAM_SSH_TC_AUD); ++TRACE_DEFINE_ENUM(SSAM_SSH_TC_SMC); ++TRACE_DEFINE_ENUM(SSAM_SSH_TC_KPD); ++TRACE_DEFINE_ENUM(SSAM_SSH_TC_REG); ++ ++ ++#define SSAM_PTR_UID_LEN 9 ++#define SSAM_U8_FIELD_NOT_APPLICABLE ((u16)-1) ++#define SSAM_SEQ_NOT_APPLICABLE ((u16)-1) ++#define SSAM_RQID_NOT_APPLICABLE ((u32)-1) ++#define SSAM_SSH_TC_NOT_APPLICABLE 0 ++ ++ ++#ifndef _SURFACE_AGGREGATOR_TRACE_HELPERS ++#define _SURFACE_AGGREGATOR_TRACE_HELPERS ++ ++/** ++ * ssam_trace_ptr_uid() - Convert the pointer to a non-pointer UID string. ++ * @ptr: The pointer to convert. ++ * @uid_str: A buffer of length SSAM_PTR_UID_LEN where the UID will be stored. ++ * ++ * Converts the given pointer into a UID string that is safe to be shared ++ * with userspace and logs, i.e. doesn't give away the real memory location. ++ */ ++static inline void ssam_trace_ptr_uid(const void *ptr, char *uid_str) ++{ ++ char buf[2 * sizeof(void *) + 1]; ++ ++ snprintf(buf, ARRAY_SIZE(buf), "%p", ptr); ++ memcpy(uid_str, &buf[ARRAY_SIZE(buf) - SSAM_PTR_UID_LEN], ++ SSAM_PTR_UID_LEN); ++} ++ ++/** ++ * ssam_trace_get_packet_seq() - Read the packet's sequence ID. ++ * @p: The packet. ++ * ++ * Return: Returns the packet's sequence ID (SEQ) field if present, or ++ * %SSAM_SEQ_NOT_APPLICABLE if not (e.g. flush packet). ++ */ ++static inline u16 ssam_trace_get_packet_seq(const struct ssh_packet *p) ++{ ++ if (!p->data.ptr || p->data.len < SSH_MESSAGE_LENGTH(0)) ++ return SSAM_SEQ_NOT_APPLICABLE; ++ ++ return p->data.ptr[SSH_MSGOFFSET_FRAME(seq)]; ++} ++ ++/** ++ * ssam_trace_get_request_id() - Read the packet's request ID. ++ * @p: The packet. ++ * ++ * Return: Returns the packet's request ID (RQID) field if the packet ++ * represents a request with command data, or %SSAM_RQID_NOT_APPLICABLE if not ++ * (e.g. flush request, control packet). ++ */ ++static inline u32 ssam_trace_get_request_id(const struct ssh_packet *p) ++{ ++ if (!p->data.ptr || p->data.len < SSH_COMMAND_MESSAGE_LENGTH(0)) ++ return SSAM_RQID_NOT_APPLICABLE; ++ ++ return get_unaligned_le16(&p->data.ptr[SSH_MSGOFFSET_COMMAND(rqid)]); ++} ++ ++/** ++ * ssam_trace_get_request_tc() - Read the packet's request target category. ++ * @p: The packet. ++ * ++ * Return: Returns the packet's request target category (TC) field if the ++ * packet represents a request with command data, or %SSAM_TC_NOT_APPLICABLE ++ * if not (e.g. flush request, control packet). ++ */ ++static inline u32 ssam_trace_get_request_tc(const struct ssh_packet *p) ++{ ++ if (!p->data.ptr || p->data.len < SSH_COMMAND_MESSAGE_LENGTH(0)) ++ return SSAM_SSH_TC_NOT_APPLICABLE; ++ ++ return get_unaligned_le16(&p->data.ptr[SSH_MSGOFFSET_COMMAND(tc)]); ++} ++ ++#endif /* _SURFACE_AGGREGATOR_TRACE_HELPERS */ ++ ++#define ssam_trace_get_command_field_u8(packet, field) \ ++ ((!packet || packet->data.len < SSH_COMMAND_MESSAGE_LENGTH(0)) \ ++ ? 0 : p->data.ptr[SSH_MSGOFFSET_COMMAND(field)]) ++ ++#define ssam_show_generic_u8_field(value) \ ++ __print_symbolic(value, \ ++ { SSAM_U8_FIELD_NOT_APPLICABLE, "N/A" } \ ++ ) ++ ++ ++#define ssam_show_frame_type(ty) \ ++ __print_symbolic(ty, \ ++ { SSH_FRAME_TYPE_DATA_SEQ, "DSEQ" }, \ ++ { SSH_FRAME_TYPE_DATA_NSQ, "DNSQ" }, \ ++ { SSH_FRAME_TYPE_ACK, "ACK" }, \ ++ { SSH_FRAME_TYPE_NAK, "NAK" } \ ++ ) ++ ++#define ssam_show_packet_type(type) \ ++ __print_flags(flags & SSH_PACKET_FLAGS_TY_MASK, "", \ ++ { BIT(SSH_PACKET_TY_FLUSH_BIT), "F" }, \ ++ { BIT(SSH_PACKET_TY_SEQUENCED_BIT), "S" }, \ ++ { BIT(SSH_PACKET_TY_BLOCKING_BIT), "B" } \ ++ ) ++ ++#define ssam_show_packet_state(state) \ ++ __print_flags(flags & SSH_PACKET_FLAGS_SF_MASK, "", \ ++ { BIT(SSH_PACKET_SF_LOCKED_BIT), "L" }, \ ++ { BIT(SSH_PACKET_SF_QUEUED_BIT), "Q" }, \ ++ { BIT(SSH_PACKET_SF_PENDING_BIT), "P" }, \ ++ { BIT(SSH_PACKET_SF_TRANSMITTING_BIT), "S" }, \ ++ { BIT(SSH_PACKET_SF_TRANSMITTED_BIT), "T" }, \ ++ { BIT(SSH_PACKET_SF_ACKED_BIT), "A" }, \ ++ { BIT(SSH_PACKET_SF_CANCELED_BIT), "C" }, \ ++ { BIT(SSH_PACKET_SF_COMPLETED_BIT), "F" } \ ++ ) ++ ++#define ssam_show_packet_seq(seq) \ ++ __print_symbolic(seq, \ ++ { SSAM_SEQ_NOT_APPLICABLE, "N/A" } \ ++ ) ++ ++ ++#define ssam_show_request_type(flags) \ ++ __print_flags(flags & SSH_REQUEST_FLAGS_TY_MASK, "", \ ++ { BIT(SSH_REQUEST_TY_FLUSH_BIT), "F" }, \ ++ { BIT(SSH_REQUEST_TY_HAS_RESPONSE_BIT), "R" } \ ++ ) ++ ++#define ssam_show_request_state(flags) \ ++ __print_flags(flags & SSH_REQUEST_FLAGS_SF_MASK, "", \ ++ { BIT(SSH_REQUEST_SF_LOCKED_BIT), "L" }, \ ++ { BIT(SSH_REQUEST_SF_QUEUED_BIT), "Q" }, \ ++ { BIT(SSH_REQUEST_SF_PENDING_BIT), "P" }, \ ++ { BIT(SSH_REQUEST_SF_TRANSMITTING_BIT), "S" }, \ ++ { BIT(SSH_REQUEST_SF_TRANSMITTED_BIT), "T" }, \ ++ { BIT(SSH_REQUEST_SF_RSPRCVD_BIT), "A" }, \ ++ { BIT(SSH_REQUEST_SF_CANCELED_BIT), "C" }, \ ++ { BIT(SSH_REQUEST_SF_COMPLETED_BIT), "F" } \ ++ ) ++ ++#define ssam_show_request_id(rqid) \ ++ __print_symbolic(rqid, \ ++ { SSAM_RQID_NOT_APPLICABLE, "N/A" } \ ++ ) ++ ++#define ssam_show_ssh_tc(rqid) \ ++ __print_symbolic(rqid, \ ++ { SSAM_SSH_TC_NOT_APPLICABLE, "N/A" }, \ ++ { SSAM_SSH_TC_SAM, "SAM" }, \ ++ { SSAM_SSH_TC_BAT, "BAT" }, \ ++ { SSAM_SSH_TC_TMP, "TMP" }, \ ++ { SSAM_SSH_TC_PMC, "PMC" }, \ ++ { SSAM_SSH_TC_FAN, "FAN" }, \ ++ { SSAM_SSH_TC_PoM, "PoM" }, \ ++ { SSAM_SSH_TC_DBG, "DBG" }, \ ++ { SSAM_SSH_TC_KBD, "KBD" }, \ ++ { SSAM_SSH_TC_FWU, "FWU" }, \ ++ { SSAM_SSH_TC_UNI, "UNI" }, \ ++ { SSAM_SSH_TC_LPC, "LPC" }, \ ++ { SSAM_SSH_TC_TCL, "TCL" }, \ ++ { SSAM_SSH_TC_SFL, "SFL" }, \ ++ { SSAM_SSH_TC_KIP, "KIP" }, \ ++ { SSAM_SSH_TC_EXT, "EXT" }, \ ++ { SSAM_SSH_TC_BLD, "BLD" }, \ ++ { SSAM_SSH_TC_BAS, "BAS" }, \ ++ { SSAM_SSH_TC_SEN, "SEN" }, \ ++ { SSAM_SSH_TC_SRQ, "SRQ" }, \ ++ { SSAM_SSH_TC_MCU, "MCU" }, \ ++ { SSAM_SSH_TC_HID, "HID" }, \ ++ { SSAM_SSH_TC_TCH, "TCH" }, \ ++ { SSAM_SSH_TC_BKL, "BKL" }, \ ++ { SSAM_SSH_TC_TAM, "TAM" }, \ ++ { SSAM_SSH_TC_ACC, "ACC" }, \ ++ { SSAM_SSH_TC_UFI, "UFI" }, \ ++ { SSAM_SSH_TC_USC, "USC" }, \ ++ { SSAM_SSH_TC_PEN, "PEN" }, \ ++ { SSAM_SSH_TC_VID, "VID" }, \ ++ { SSAM_SSH_TC_AUD, "AUD" }, \ ++ { SSAM_SSH_TC_SMC, "SMC" }, \ ++ { SSAM_SSH_TC_KPD, "KPD" }, \ ++ { SSAM_SSH_TC_REG, "REG" } \ ++ ) ++ ++ ++DECLARE_EVENT_CLASS(ssam_frame_class, ++ TP_PROTO(const struct ssh_frame *frame), ++ ++ TP_ARGS(frame), ++ ++ TP_STRUCT__entry( ++ __field(u8, type) ++ __field(u8, seq) ++ __field(u16, len) ++ ), ++ ++ TP_fast_assign( ++ __entry->type = frame->type; ++ __entry->seq = frame->seq; ++ __entry->len = get_unaligned_le16(&frame->len); ++ ), ++ ++ TP_printk("ty=%s, seq=0x%02x, len=%u", ++ ssam_show_frame_type(__entry->type), ++ __entry->seq, ++ __entry->len ++ ) ++); ++ ++#define DEFINE_SSAM_FRAME_EVENT(name) \ ++ DEFINE_EVENT(ssam_frame_class, ssam_##name, \ ++ TP_PROTO(const struct ssh_frame *frame), \ ++ TP_ARGS(frame) \ ++ ) ++ ++ ++DECLARE_EVENT_CLASS(ssam_command_class, ++ TP_PROTO(const struct ssh_command *cmd, u16 len), ++ ++ TP_ARGS(cmd, len), ++ ++ TP_STRUCT__entry( ++ __field(u16, rqid) ++ __field(u16, len) ++ __field(u8, tc) ++ __field(u8, cid) ++ __field(u8, iid) ++ ), ++ ++ TP_fast_assign( ++ __entry->rqid = get_unaligned_le16(&cmd->rqid); ++ __entry->tc = cmd->tc; ++ __entry->cid = cmd->cid; ++ __entry->iid = cmd->iid; ++ __entry->len = len; ++ ), ++ ++ TP_printk("rqid=0x%04x, tc=%s, cid=0x%02x, iid=0x%02x, len=%u", ++ __entry->rqid, ++ ssam_show_ssh_tc(__entry->tc), ++ __entry->cid, ++ __entry->iid, ++ __entry->len ++ ) ++); ++ ++#define DEFINE_SSAM_COMMAND_EVENT(name) \ ++ DEFINE_EVENT(ssam_command_class, ssam_##name, \ ++ TP_PROTO(const struct ssh_command *cmd, u16 len), \ ++ TP_ARGS(cmd, len) \ ++ ) ++ ++ ++DECLARE_EVENT_CLASS(ssam_packet_class, ++ TP_PROTO(const struct ssh_packet *packet), ++ ++ TP_ARGS(packet), ++ ++ TP_STRUCT__entry( ++ __array(char, uid, SSAM_PTR_UID_LEN) ++ __field(u8, priority) ++ __field(u16, length) ++ __field(unsigned long, state) ++ __field(u16, seq) ++ ), ++ ++ TP_fast_assign( ++ ssam_trace_ptr_uid(packet, __entry->uid); ++ __entry->priority = READ_ONCE(packet->priority); ++ __entry->length = packet->data.len; ++ __entry->state = READ_ONCE(packet->state); ++ __entry->seq = ssam_trace_get_packet_seq(packet); ++ ), ++ ++ TP_printk("uid=%s, seq=%s, ty=%s, pri=0x%02x, len=%u, sta=%s", ++ __entry->uid, ++ ssam_show_packet_seq(__entry->seq), ++ ssam_show_packet_type(__entry->state), ++ __entry->priority, ++ __entry->length, ++ ssam_show_packet_state(__entry->state) ++ ) ++); ++ ++#define DEFINE_SSAM_PACKET_EVENT(name) \ ++ DEFINE_EVENT(ssam_packet_class, ssam_##name, \ ++ TP_PROTO(const struct ssh_packet *packet), \ ++ TP_ARGS(packet) \ ++ ) ++ ++ ++DECLARE_EVENT_CLASS(ssam_packet_status_class, ++ TP_PROTO(const struct ssh_packet *packet, int status), ++ ++ TP_ARGS(packet, status), ++ ++ TP_STRUCT__entry( ++ __array(char, uid, SSAM_PTR_UID_LEN) ++ __field(u8, priority) ++ __field(u16, length) ++ __field(unsigned long, state) ++ __field(u16, seq) ++ __field(int, status) ++ ), ++ ++ TP_fast_assign( ++ ssam_trace_ptr_uid(packet, __entry->uid); ++ __entry->priority = READ_ONCE(packet->priority); ++ __entry->length = packet->data.len; ++ __entry->state = READ_ONCE(packet->state); ++ __entry->seq = ssam_trace_get_packet_seq(packet); ++ __entry->status = status; ++ ), ++ ++ TP_printk("uid=%s, seq=%s, ty=%s, pri=0x%02x, len=%u, sta=%s, status=%d", ++ __entry->uid, ++ ssam_show_packet_seq(__entry->seq), ++ ssam_show_packet_type(__entry->state), ++ __entry->priority, ++ __entry->length, ++ ssam_show_packet_state(__entry->state), ++ __entry->status ++ ) ++); ++ ++#define DEFINE_SSAM_PACKET_STATUS_EVENT(name) \ ++ DEFINE_EVENT(ssam_packet_status_class, ssam_##name, \ ++ TP_PROTO(const struct ssh_packet *packet, int status), \ ++ TP_ARGS(packet, status) \ ++ ) ++ ++ ++DECLARE_EVENT_CLASS(ssam_request_class, ++ TP_PROTO(const struct ssh_request *request), ++ ++ TP_ARGS(request), ++ ++ TP_STRUCT__entry( ++ __array(char, uid, SSAM_PTR_UID_LEN) ++ __field(unsigned long, state) ++ __field(u32, rqid) ++ __field(u8, tc) ++ __field(u16, cid) ++ __field(u16, iid) ++ ), ++ ++ TP_fast_assign( ++ const struct ssh_packet *p = &request->packet; ++ ++ // use packet for UID so we can match requests to packets ++ ssam_trace_ptr_uid(p, __entry->uid); ++ __entry->state = READ_ONCE(request->state); ++ __entry->rqid = ssam_trace_get_request_id(p); ++ __entry->tc = ssam_trace_get_request_tc(p); ++ __entry->cid = ssam_trace_get_command_field_u8(p, cid); ++ __entry->iid = ssam_trace_get_command_field_u8(p, iid); ++ ), ++ ++ TP_printk("uid=%s, rqid=%s, ty=%s, sta=%s, tc=%s, cid=%s, iid=%s", ++ __entry->uid, ++ ssam_show_request_id(__entry->rqid), ++ ssam_show_request_type(__entry->state), ++ ssam_show_request_state(__entry->state), ++ ssam_show_ssh_tc(__entry->tc), ++ ssam_show_generic_u8_field(__entry->cid), ++ ssam_show_generic_u8_field(__entry->iid) ++ ) ++); ++ ++#define DEFINE_SSAM_REQUEST_EVENT(name) \ ++ DEFINE_EVENT(ssam_request_class, ssam_##name, \ ++ TP_PROTO(const struct ssh_request *request), \ ++ TP_ARGS(request) \ ++ ) ++ ++ ++DECLARE_EVENT_CLASS(ssam_request_status_class, ++ TP_PROTO(const struct ssh_request *request, int status), ++ ++ TP_ARGS(request, status), ++ ++ TP_STRUCT__entry( ++ __array(char, uid, SSAM_PTR_UID_LEN) ++ __field(unsigned long, state) ++ __field(u32, rqid) ++ __field(u8, tc) ++ __field(u16, cid) ++ __field(u16, iid) ++ __field(int, status) ++ ), ++ ++ TP_fast_assign( ++ const struct ssh_packet *p = &request->packet; ++ ++ // use packet for UID so we can match requests to packets ++ ssam_trace_ptr_uid(p, __entry->uid); ++ __entry->state = READ_ONCE(request->state); ++ __entry->rqid = ssam_trace_get_request_id(p); ++ __entry->tc = ssam_trace_get_request_tc(p); ++ __entry->cid = ssam_trace_get_command_field_u8(p, cid); ++ __entry->iid = ssam_trace_get_command_field_u8(p, iid); ++ __entry->status = status; ++ ), ++ ++ TP_printk("uid=%s, rqid=%s, ty=%s, sta=%s, tc=%s, cid=%s, iid=%s, status=%d", ++ __entry->uid, ++ ssam_show_request_id(__entry->rqid), ++ ssam_show_request_type(__entry->state), ++ ssam_show_request_state(__entry->state), ++ ssam_show_ssh_tc(__entry->tc), ++ ssam_show_generic_u8_field(__entry->cid), ++ ssam_show_generic_u8_field(__entry->iid), ++ __entry->status ++ ) ++); ++ ++#define DEFINE_SSAM_REQUEST_STATUS_EVENT(name) \ ++ DEFINE_EVENT(ssam_request_status_class, ssam_##name, \ ++ TP_PROTO(const struct ssh_request *request, int status),\ ++ TP_ARGS(request, status) \ ++ ) ++ ++ ++DECLARE_EVENT_CLASS(ssam_alloc_class, ++ TP_PROTO(void *ptr, size_t len), ++ ++ TP_ARGS(ptr, len), ++ ++ TP_STRUCT__entry( ++ __array(char, uid, SSAM_PTR_UID_LEN) ++ __field(size_t, len) ++ ), ++ ++ TP_fast_assign( ++ ssam_trace_ptr_uid(ptr, __entry->uid); ++ __entry->len = len; ++ ), ++ ++ TP_printk("uid=%s, len=%zu", __entry->uid, __entry->len) ++); ++ ++#define DEFINE_SSAM_ALLOC_EVENT(name) \ ++ DEFINE_EVENT(ssam_alloc_class, ssam_##name, \ ++ TP_PROTO(void *ptr, size_t len), \ ++ TP_ARGS(ptr, len) \ ++ ) ++ ++ ++DECLARE_EVENT_CLASS(ssam_free_class, ++ TP_PROTO(void *ptr), ++ ++ TP_ARGS(ptr), ++ ++ TP_STRUCT__entry( ++ __array(char, uid, SSAM_PTR_UID_LEN) ++ __field(size_t, len) ++ ), ++ ++ TP_fast_assign( ++ ssam_trace_ptr_uid(ptr, __entry->uid); ++ ), ++ ++ TP_printk("uid=%s", __entry->uid) ++); ++ ++#define DEFINE_SSAM_FREE_EVENT(name) \ ++ DEFINE_EVENT(ssam_free_class, ssam_##name, \ ++ TP_PROTO(void *ptr), \ ++ TP_ARGS(ptr) \ ++ ) ++ ++ ++DECLARE_EVENT_CLASS(ssam_generic_uint_class, ++ TP_PROTO(const char *property, unsigned int value), ++ ++ TP_ARGS(property, value), ++ ++ TP_STRUCT__entry( ++ __string(property, property) ++ __field(unsigned int, value) ++ ), ++ ++ TP_fast_assign( ++ __assign_str(property, property); ++ __entry->value = value; ++ ), ++ ++ TP_printk("%s=%u", __get_str(property), __entry->value) ++); ++ ++#define DEFINE_SSAM_GENERIC_UINT_EVENT(name) \ ++ DEFINE_EVENT(ssam_generic_uint_class, ssam_##name, \ ++ TP_PROTO(const char *property, unsigned int value), \ ++ TP_ARGS(property, value) \ ++ ) ++ ++ ++DEFINE_SSAM_FRAME_EVENT(rx_frame_received); ++DEFINE_SSAM_COMMAND_EVENT(rx_response_received); ++DEFINE_SSAM_COMMAND_EVENT(rx_event_received); ++ ++DEFINE_SSAM_PACKET_EVENT(packet_release); ++DEFINE_SSAM_PACKET_EVENT(packet_submit); ++DEFINE_SSAM_PACKET_EVENT(packet_resubmit); ++DEFINE_SSAM_PACKET_EVENT(packet_timeout); ++DEFINE_SSAM_PACKET_EVENT(packet_cancel); ++DEFINE_SSAM_PACKET_STATUS_EVENT(packet_complete); ++DEFINE_SSAM_GENERIC_UINT_EVENT(ptl_timeout_reap); ++ ++DEFINE_SSAM_REQUEST_EVENT(request_submit); ++DEFINE_SSAM_REQUEST_EVENT(request_timeout); ++DEFINE_SSAM_REQUEST_EVENT(request_cancel); ++DEFINE_SSAM_REQUEST_STATUS_EVENT(request_complete); ++DEFINE_SSAM_GENERIC_UINT_EVENT(rtl_timeout_reap); ++ ++DEFINE_SSAM_PACKET_EVENT(ei_tx_drop_ack_packet); ++DEFINE_SSAM_PACKET_EVENT(ei_tx_drop_nak_packet); ++DEFINE_SSAM_PACKET_EVENT(ei_tx_drop_dsq_packet); ++DEFINE_SSAM_PACKET_STATUS_EVENT(ei_tx_fail_write); ++DEFINE_SSAM_PACKET_EVENT(ei_tx_corrupt_data); ++DEFINE_SSAM_GENERIC_UINT_EVENT(ei_rx_corrupt_syn); ++DEFINE_SSAM_FRAME_EVENT(ei_rx_corrupt_data); ++DEFINE_SSAM_REQUEST_EVENT(ei_rx_drop_response); ++ ++DEFINE_SSAM_ALLOC_EVENT(ctrl_packet_alloc); ++DEFINE_SSAM_FREE_EVENT(ctrl_packet_free); ++ ++DEFINE_SSAM_ALLOC_EVENT(event_item_alloc); ++DEFINE_SSAM_FREE_EVENT(event_item_free); ++ ++#endif /* _SURFACE_AGGREGATOR_TRACE_H */ ++ ++/* This part must be outside protection */ ++#undef TRACE_INCLUDE_PATH ++#undef TRACE_INCLUDE_FILE ++ ++#define TRACE_INCLUDE_PATH . ++#define TRACE_INCLUDE_FILE trace ++ ++#include diff --git a/drivers/tty/serdev/core.c b/drivers/tty/serdev/core.c index a9719858c950..ce5309d00280 100644 --- a/drivers/tty/serdev/core.c @@ -16374,10 +16439,10 @@ index a9719858c950..ce5309d00280 100644 if (!ctrl->serdev) return -ENODEV; diff --git a/include/linux/mod_devicetable.h b/include/linux/mod_devicetable.h -index 4c56404e53a7..1ef76f488a58 100644 +index 4c56404e53a7..3e2fe1c6631c 100644 --- a/include/linux/mod_devicetable.h +++ b/include/linux/mod_devicetable.h -@@ -827,4 +827,21 @@ struct wmi_device_id { +@@ -827,4 +827,22 @@ struct wmi_device_id { const void *context; }; @@ -16390,6 +16455,7 @@ index 4c56404e53a7..1ef76f488a58 100644 +struct ssam_device_id { + __u8 match_flags; + ++ __u8 domain; + __u8 category; + __u8 target; + __u8 instance; @@ -16401,25 +16467,23 @@ index 4c56404e53a7..1ef76f488a58 100644 #endif /* LINUX_MOD_DEVICETABLE_H */ diff --git a/include/linux/surface_acpi_notify.h b/include/linux/surface_acpi_notify.h new file mode 100644 -index 000000000000..aa69fa508140 +index 000000000000..ee5e04f2eb48 --- /dev/null +++ b/include/linux/surface_acpi_notify.h -@@ -0,0 +1,39 @@ +@@ -0,0 +1,37 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* -+ * Interface for Surface ACPI/Notify (SAN). ++ * Interface for Surface ACPI Notify (SAN) driver. + * -+ * The SAN is the main interface between the Surface Serial Hub (SSH) and the -+ * Surface/System Aggregator Module (SAM). It allows requests to be translated -+ * from ACPI to SSH/SAM. It also interfaces with the discrete GPU hot-plug -+ * driver. ++ * Provides access to discrete GPU notifications sent from ACPI via the SAN ++ * driver, which are not handled by this driver directly. + */ + -+#ifndef _SURFACE_SAM_SAN_H -+#define _SURFACE_SAM_SAN_H ++#ifndef _LINUX_SURFACE_ACPI_NOTIFY_H ++#define _LINUX_SURFACE_ACPI_NOTIFY_H + -+#include +#include ++#include + +/** + * struct san_dgpu_event - Discrete GPU ACPI event. @@ -16443,592 +16507,34 @@ index 000000000000..aa69fa508140 +int san_dgpu_notifier_register(struct notifier_block *nb); +int san_dgpu_notifier_unregister(struct notifier_block *nb); + -+#endif /* _SURFACE_SAM_SAN_H */ -diff --git a/include/linux/surface_aggregator_module.h b/include/linux/surface_aggregator_module.h ++#endif /* _LINUX_SURFACE_ACPI_NOTIFY_H */ +diff --git a/include/linux/surface_aggregator/controller.h b/include/linux/surface_aggregator/controller.h new file mode 100644 -index 000000000000..2763805c283a +index 000000000000..2fac2cbf8816 --- /dev/null -+++ b/include/linux/surface_aggregator_module.h -@@ -0,0 +1,1744 @@ ++++ b/include/linux/surface_aggregator/controller.h +@@ -0,0 +1,812 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* -+ * Interface for Surface System Aggregator Module (SSAM) via Surface Serial -+ * Hub (SSH). ++ * Surface System Aggregator Module (SSAM) controller interface. + * -+ * 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. ++ * Main communication interface for the SSAM EC. Provides a controller ++ * managing access and communication to and from the SSAM EC, as well as main ++ * communication structures and definitions. + */ + -+#ifndef _SURFACE_AGGREGATOR_MODULE_H -+#define _SURFACE_AGGREGATOR_MODULE_H ++#ifndef _LINUX_SURFACE_AGGREGATOR_CONTROLLER_H ++#define _LINUX_SURFACE_AGGREGATOR_CONTROLLER_H + +#include -+#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 ``struct 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; -+ -+static_assert(sizeof(struct ssh_frame) == 4); -+ -+/* -+ * SSH_FRAME_MAX_PAYLOAD_SIZE - 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. -+ * @tid_out: Output target ID. Should be zero if this an incoming (EC to host) -+ * message. -+ * @tid_in: Input target ID. Should be zero if this is an outgoing (host 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 tid_out; -+ u8 tid_in; -+ u8 iid; -+ __le16 rqid; -+ u8 cid; -+} __packed; -+ -+static_assert(sizeof(struct ssh_command) == 8); -+ -+/* -+ * SSH_COMMAND_MAX_PAYLOAD_SIZE - 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)) -+ -+/* -+ * SSH_MSG_LEN_BASE - 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)) -+ -+/* -+ * SSH_MSG_LEN_CTRL - Length of a SSH control message. -+ * -+ * This is the length of a SSH control message, which is equal to a SSH -+ * message without any payload. -+ */ -+#define SSH_MSG_LEN_CTRL SSH_MSG_LEN_BASE -+ -+/** -+ * SSH_MESSAGE_LENGTH() - Comute lenght of SSH message. -+ * @payload_size: Length of the payload inside the SSH frame. -+ * -+ * Return: Returns the length of a SSH message with payload of specified size. -+ */ -+#define SSH_MESSAGE_LENGTH(payload_size) (SSH_MSG_LEN_BASE + payload_size) -+ -+/** -+ * SSH_COMMAND_MESSAGE_LENGTH() - Compute length of SSH command message. -+ * @payload_size: Length of the command payload. -+ * -+ * Return: Returns the 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) -+ -+/** -+ * SSH_MSGOFFSET_FRAME() - Compute offset in SSH message to specified field in -+ * frame. -+ * @field: The field for which the offset should be computed. -+ * -+ * Return: Returns the offset of the specified &struct ssh_frame field in the -+ * raw SSH message data as. -+ */ -+#define SSH_MSGOFFSET_FRAME(field) \ -+ (sizeof(u16) + offsetof(struct ssh_frame, field)) -+ -+/** -+ * SSH_MSGOFFSET_COMMAND() - Compute offset in SSH message to specified field -+ * in command. -+ * @field: The field for which the offset should be computed. -+ * -+ * Return: Returns the 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_base_priority - Base priorities for &struct ssh_packet. -+ * @SSH_PACKET_PRIORITY_FLUSH: Base priority for flush packets. -+ * @SSH_PACKET_PRIORITY_DATA: Base priority for normal data paackets. -+ * @SSH_PACKET_PRIORITY_NAK: Base priority for NAK packets. -+ * @SSH_PACKET_PRIORITY_ACK: Base priority for ACK packets. -+ */ -+enum ssh_packet_base_priority { -+ SSH_PACKET_PRIORITY_FLUSH = 0, /* same as DATA to sequence flush */ -+ SSH_PACKET_PRIORITY_DATA = 0, -+ SSH_PACKET_PRIORITY_NAK = 1, -+ SSH_PACKET_PRIORITY_ACK = 2, -+}; -+ -+/* -+ * Same as SSH_PACKET_PRIORITY() below, only with actual values. -+ */ -+#define __SSH_PACKET_PRIORITY(base, try) \ -+ (((base) << 4) | ((try) & 0x0f)) -+ -+/** -+ * SSH_PACKET_PRIORITY() - Compute packet priority from base priority and -+ * number of tries. -+ * @base: The base priority as suffix of &enum ssh_packet_base_priority, e.g. -+ * ``FLUSH``, ``DATA``, ``ACK``, or ``NAK``. -+ * @try: The number of tries (must be less than 16). -+ * -+ * Compute the combined packet priority. The combined priority is dominated by -+ * the base priority, whereas the number of (re-)tries decides the precedence -+ * of packets with the same base priority, giving higher priority to packets -+ * that already have more tries. -+ * -+ * Return: Returns the computed priority as value fitting inside a &u8. A -+ * higher number means a higher priority. -+ */ -+#define SSH_PACKET_PRIORITY(base, try) \ -+ __SSH_PACKET_PRIORITY(SSH_PACKET_PRIORITY_##base, (try)) -+ -+/** -+ * ssh_packet_priority_get_try() - Get number of tries from packet priority. -+ * @priority: The packet priority. -+ * -+ * Return: Returns the number of tries encoded in the specified packet -+ * priority. -+ */ -+static inline u8 ssh_packet_priority_get_try(u8 priority) -+{ -+ return priority & 0x0f; -+} -+ -+/** -+ * ssh_packet_priority_get_base - Get base priority from packet priority. -+ * @priority: The packet priority. -+ * -+ * Return: Returns the base priority encoded in the given packet priority. -+ */ -+static inline u8 ssh_packet_priority_get_base(u8 priority) -+{ -+ return (priority & 0xf0) >> 4; -+} -+ -+ -+enum ssh_packet_flags { -+ /* state 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, -+ -+ /* type flags */ -+ SSH_PACKET_TY_FLUSH_BIT, -+ SSH_PACKET_TY_SEQUENCED_BIT, -+ SSH_PACKET_TY_BLOCKING_BIT, -+ -+ /* mask for state flags */ -+ 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), -+ -+ /* mask for type flags */ -+ 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 - Callback operations for a SSH packet. -+ * @release: Function called when the packet reference count reaches zero. -+ * This callback must be relied upon to ensure that the packet has -+ * left the transport system(s). -+ * @complete: Function called when the packet is completed, either with -+ * success or failure. In case of failure, the reason for the -+ * failure is indicated by the value of the provided status code -+ * argument. This value will be zero in case of success. Note that -+ * a call to this callback does not guarantee that the packet is -+ * not in use by the transport system any more. -+ */ -+struct ssh_packet_ops { -+ void (*release)(struct ssh_packet *p); -+ void (*complete)(struct ssh_packet *p, int status); -+}; -+ -+/** -+ * struct ssh_packet - SSH transport packet. -+ * @ptl: Pointer to the packet transport layer. May be %NULL if the packet -+ * (or enclosing request) has not been submitted yet. -+ * @refcnt: Reference count of the packet. -+ * @priority: Priority of the packet. Must be computed via -+ * SSH_PACKET_PRIORITY(). -+ * @data: Raw message data. -+ * @data.len: Length of the raw message data. -+ * @data.ptr: Pointer to the raw message data buffer. -+ * @state: State and type flags describing current packet state (dynamic) -+ * and type (static). See &enum ssh_packet_flags for possible -+ * options. -+ * @timestamp: Timestamp specifying when the latest transmission of a -+ * currently pending packet has been started. May be %KTIME_MAX -+ * before or in-between transmission attempts. Used for the packet -+ * timeout implementation. -+ * @queue_node: The list node for the packet queue. -+ * @pending_node: The list node for the set of pending packets. -+ * @ops: Packet operations. -+ */ -+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; -+}; -+ -+struct ssh_packet *ssh_packet_get(struct ssh_packet *p); -+void ssh_packet_put(struct ssh_packet *p); -+ -+/** -+ * ssh_packet_set_data() - Set raw message data of packet. -+ * @p: The packet for which the message data should be set. -+ * @ptr: Pointer to the memory holding the message data. -+ * @len: Length of the message data. -+ * -+ * Sets the raw message data buffer of the packet to the provided memory. The -+ * memory is not copied. Instead, the caller is responsible for management -+ * (i.e. allocation and deallocation) of the memory. The caller must ensure -+ * that the provided memory is valid and contains a valid SSH message, -+ * starting from the time of submission of the packet until the ``release`` -+ * callback has been called. During this time, the memory may not be altered -+ * in any way. -+ */ -+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 { -+ /* state 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, -+ -+ /* type flags */ -+ SSH_REQUEST_TY_FLUSH_BIT, -+ SSH_REQUEST_TY_HAS_RESPONSE_BIT, -+ -+ /* mask for state flags */ -+ 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), -+ -+ /* mask for type flags */ -+ 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 - Callback operations for a SSH request. -+ * @release: Function called when the request's reference count reaches zero. -+ * This callback must be relied upon to ensure that the request has -+ * left the transport systems (both, packet an request systems). -+ * @complete: Function called when the request is completed, either with -+ * success or failure. The command data for the request response -+ * is provided via the &struct ssh_command parameter (``cmd``), -+ * the command payload of the request response via the &struct -+ * ssh_span parameter (``data``). -+ * -+ * If the request does not have any response or has not been -+ * completed with success, both ``cmd`` and ``data`` parameters will -+ * be NULL. If the request response does not have any command -+ * payload, the ``data`` span will be an empty (zero-length) span. -+ * -+ * In case of failure, the reason for the failure is indicated by -+ * the value of the provided status code argument (``status``). This -+ * value will be zero in case of success. -+ * -+ * Note that a call to this callback does not guarantee that the -+ * request is not in use by the transport systems any more. -+ */ -+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 - SSH transport request. -+ * @packet: The underlying SSH transport packet. -+ * @node: List node for the request queue and pending set. -+ * @state: State and type flags describing current request state (dynamic) -+ * and type (static). See &enum ssh_request_flags for possible -+ * options. -+ * @timestamp: Timestamp specifying when we start waiting on the respnse of the -+ * request. This is set once the underlying packet has been completed -+ * and may be %KTIME_MAX before that, or when the request does not -+ * expect a response. Used for the request timeout implementation. -+ * @ops: Request Operations. -+ */ -+struct ssh_request { -+ struct ssh_packet packet; -+ struct list_head node; -+ -+ unsigned long state; -+ ktime_t timestamp; -+ -+ const struct ssh_request_ops *ops; -+}; -+ -+/** -+ * to_ssh_request() - Cast a SSH packet to its enclosing SSH request. -+ * @p: The packet to cast. -+ * -+ * Casts the given &struct ssh_packet to its enclosing &struct ssh_request. -+ * The caller is responsible for making sure that the packet is actually -+ * wrapped in a &struct ssh_request. -+ * -+ * Return: Returns the &struct ssh_request wrapping the provided packet. -+ */ -+static inline struct ssh_request *to_ssh_request(struct ssh_packet *p) -+{ -+ return container_of(p, struct ssh_request, packet); -+} -+ -+/** -+ * ssh_request_get() - Increment reference count of request. -+ * @r: The request to increment the reference count of. -+ * -+ * Increments the reference count of the given request by incrementing the -+ * reference count of the underlying &struct ssh_packet, enclosed in it. -+ * -+ * See also ssh_request_put(), ssh_packet_get(). -+ * -+ * Return: Returns the request provided as input. -+ */ -+static inline struct ssh_request *ssh_request_get(struct ssh_request *r) -+{ -+ ssh_packet_get(&r->packet); -+ return r; -+} -+ -+/** -+ * ssh_request_put() - Decrement reference count of request. -+ * @r: The request to decrement the reference count of. -+ * -+ * Decrements the reference count of the given request by decrementing the -+ * reference count of the underlying &struct ssh_packet, enclosed in it. If -+ * the reference count reaches zero, the ``release`` callback specified in the -+ * request's &struct ssh_request_ops, i.e. ``r->ops->release``, will be -+ * called. -+ * -+ * See also ssh_request_get(), ssh_packet_put(). -+ */ -+static inline void ssh_request_put(struct ssh_request *r) -+{ -+ ssh_packet_put(&r->packet); -+} -+ -+/** -+ * ssh_request_set_data() - Set raw message data of request. -+ * @r: The request for which the message data should be set. -+ * @ptr: Pointer to the memory holding the message data. -+ * @len: Length of the message data. -+ * -+ * Sets the raw message data buffer of the underlying packet to the specified -+ * buffer. Does not copy the actual message data, just sets the buffer pointer -+ * and length. Refer to ssh_packet_set_data() for more details. -+ */ -+static inline void ssh_request_set_data(struct ssh_request *r, u8 *ptr, size_t len) -+{ -+ ssh_packet_set_data(&r->packet, ptr, len); -+} ++#include + + +/* -- Main data types and definitions --------------------------------------- */ + -+enum ssam_ssh_tc { -+ /* Known SSH/EC target categories. */ -+ // category 0x00 is invalid for EC use -+ 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, -+ -+ /* Special values. For driver use only, do not use with EC. */ -+ SSAM_SSH_TC__HUB = 0x00, // not an actual category, used in for hubs -+}; -+ -+struct ssam_controller; -+ +/** + * enum ssam_event_flags - Flags for enabling/disabling SAM events + * @SSAM_EVENT_SEQUENCED: The event will be sent via a sequenced data frame. @@ -17052,7 +16558,7 @@ index 000000000000..2763805c283a + u8 command_id; + u8 instance_id; + u16 length; -+ u8 data[0]; ++ u8 data[]; +}; + +/** @@ -17111,6 +16617,8 @@ index 000000000000..2763805c283a + u8 *pointer; +}; + ++struct ssam_controller; ++ + +struct ssam_controller *ssam_get_controller(void); +int ssam_client_link(struct ssam_controller *ctrl, struct device *client); @@ -17817,11 +17325,56 @@ index 000000000000..2763805c283a +int ssam_notifier_unregister(struct ssam_controller *ctrl, + struct ssam_event_notifier *n); + ++#endif /* _LINUX_SURFACE_AGGREGATOR_CONTROLLER_H */ +diff --git a/include/linux/surface_aggregator/device.h b/include/linux/surface_aggregator/device.h +new file mode 100644 +index 000000000000..9c5f691a957e +--- /dev/null ++++ b/include/linux/surface_aggregator/device.h +@@ -0,0 +1,408 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* ++ * Surface System Aggregator Module (SSAM) bus and client-device subsystem. ++ * ++ * Main interface for the surface-aggregator bus, surface-aggregator client ++ * devices, and respective drivers building on top of the SSAM controller. ++ * Provides support for non-platform/non-ACPI SSAM clients via dedicated ++ * subsystem. ++ */ ++ ++#ifndef _LINUX_SURFACE_AGGREGATOR_DEVICE_H ++#define _LINUX_SURFACE_AGGREGATOR_DEVICE_H ++ ++#include ++#include ++#include ++ ++#include ++ + +/* -- Surface System Aggregator Module Bus. --------------------------------- */ + +/** ++ * enum ssam_device_domain - SAM device domain. ++ * @SSAM_DOMAIN_VIRTUAL: Virtual device. ++ * @SSAM_DOMAIN_SERIALHUB: Physical dovice connected via Surface Serial Hub. ++ */ ++enum ssam_device_domain { ++ SSAM_DOMAIN_VIRTUAL = 0x00, ++ SSAM_DOMAIN_SERIALHUB = 0x01, ++}; ++ ++/** ++ * enum ssam_virtual_tc - Target categories for the virtual SAM domain. ++ * @SSAM_VIRTUAL_TC_HUB: Device hub category. ++ */ ++enum ssam_virtual_tc { ++ SSAM_VIRTUAL_TC_HUB = 0x00, ++}; ++ ++/** + * struct ssam_device_uid - Unique identifier for SSAM device. ++ * @domain: Domain of the device. + * @category: Target category of the device. + * @target: Target ID of the device. + * @instance: Instance ID of the device. @@ -17831,34 +17384,13 @@ index 000000000000..2763805c283a + * such functionality. + */ +struct ssam_device_uid { ++ u8 domain; + u8 category; + u8 target; + u8 instance; + u8 function; +}; + -+/** -+ * SSAM_DUID() - Define a &struct ssam_device_uid. -+ * @cat: Target category of the device. -+ * @tid: Target ID of the device. -+ * @iid: Instance ID of the device. -+ * @fun: Sub-function of the (virtual) device. -+ * -+ * Return: The &struct ssam_device_uid specified by the given parameters. -+ */ -+#define SSAM_DUID(cat, tid, iid, fun) \ -+ ((struct ssam_device_uid) { \ -+ .category = SSAM_SSH_TC_##cat, \ -+ .target = (tid), \ -+ .instance = (iid), \ -+ .function = (fun) \ -+ }) -+ -+/* -+ * SSAM_DUID_NULL - Null device UID. -+ */ -+#define SSAM_DUID_NULL ((struct ssam_device_uid) { 0 }) -+ +/* + * Special values for device matching. + */ @@ -17869,6 +17401,7 @@ index 000000000000..2763805c283a +/** + * SSAM_DEVICE() - Initialize a &struct ssam_device_id with the given + * parameters. ++ * @d: Domain of the device. + * @cat: Target category of the device. + * @tid: Target ID of the device. + * @iid: Instance ID of the device. @@ -17881,39 +17414,52 @@ index 000000000000..2763805c283a + * respectively. This macro initializes the ``match_flags`` field based on the + * given parameters. + */ -+#define SSAM_DEVICE(cat, tid, iid, fun) \ ++#define SSAM_DEVICE(d, cat, tid, iid, fun) \ + .match_flags = (((tid) != SSAM_ANY_TID) ? SSAM_MATCH_TARGET : 0) \ + | (((iid) != SSAM_ANY_IID) ? SSAM_MATCH_INSTANCE : 0) \ + | (((fun) != SSAM_ANY_FUN) ? SSAM_MATCH_FUNCTION : 0), \ -+ .category = SSAM_SSH_TC_##cat, \ ++ .domain = d, \ ++ .category = cat, \ + .target = ((tid) != SSAM_ANY_TID) ? (tid) : 0, \ + .instance = ((iid) != SSAM_ANY_IID) ? (iid) : 0, \ + .function = ((fun) != SSAM_ANY_FUN) ? (fun) : 0 \ + ++/** ++ * SSAM_VDEV() - Initialize a &struct ssam_device_id as virtual device with ++ * the given parameters. ++ * @cat: Target category of the device. ++ * @tid: Target ID of the device. ++ * @iid: Instance ID of the device. ++ * @fun: Sub-function of the device. ++ * ++ * Initializes a &struct ssam_device_id with the given parameters in the ++ * virtual domain. See &struct ssam_device_uid for details regarding the ++ * parameters. The special values %SSAM_ANY_TID, %SSAM_ANY_IID, and ++ * %SSAM_ANY_FUN can be used to specify that matching should ignore target ID, ++ * instance ID, and/or sub-function, respectively. This macro initializes the ++ * ``match_flags`` field based on the given parameters. ++ */ ++#define SSAM_VDEV(cat, tid, iid, fun) \ ++ SSAM_DEVICE(SSAM_DOMAIN_VIRTUAL, SSAM_VIRTUAL_TC_##cat, tid, iid, fun) ++ + +/** -+ * ssam_device_uid_equal() - Compare SSAM device UIDs for equality. -+ * @u1: The first UID. -+ * @u2: The second UID. ++ * SSAM_SDEV() - Initialize a &struct ssam_device_id as physical SSH device ++ * with the given parameters. ++ * @cat: Target category of the device. ++ * @tid: Target ID of the device. ++ * @iid: Instance ID of the device. ++ * @fun: Sub-function of the device. + * -+ * Return: Returns %true iff both UIDs are equal, %false otherwise. ++ * Initializes a &struct ssam_device_id with the given parameters in the SSH ++ * domain. See &struct ssam_device_uid for details regarding the parameters. ++ * The special values %SSAM_ANY_TID, %SSAM_ANY_IID, and %SSAM_ANY_FUN can be ++ * used to specify that matching should ignore target ID, instance ID, and/or ++ * sub-function, respectively. This macro initializes the ``match_flags`` ++ * field based on the given parameters. + */ -+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; -+} -+ -+/** -+ * ssam_device_uid_is_null() - Check if a SSAM device UID is null. -+ * @uid: The UID to check. -+ * -+ * Return: Returns %true iff the given UID is null, %false otherwise. -+ */ -+static inline bool ssam_device_uid_is_null(const struct ssam_device_uid uid) -+{ -+ return ssam_device_uid_equal(uid, (struct ssam_device_uid){}); -+} ++#define SSAM_SDEV(cat, tid, iid, fun) \ ++ SSAM_DEVICE(SSAM_DOMAIN_SERIALHUB, SSAM_SSH_TC_##cat, tid, iid, fun) + + +/** @@ -18193,17 +17739,681 @@ index 000000000000..2763805c283a + sdev->uid.instance, ret); \ + } + -+#endif /* _SURFACE_AGGREGATOR_MODULE_H */ ++#endif /* _LINUX_SURFACE_AGGREGATOR_DEVICE_H */ +diff --git a/include/linux/surface_aggregator/serial_hub.h b/include/linux/surface_aggregator/serial_hub.h +new file mode 100644 +index 000000000000..01abd2b78172 +--- /dev/null ++++ b/include/linux/surface_aggregator/serial_hub.h +@@ -0,0 +1,657 @@ ++/* SPDX-License-Identifier: GPL-2.0-or-later */ ++/* ++ * Surface Serial Hub (SSH) protocol and communication interface. ++ * ++ * Lower-level communication layers and SSH protocol definitions for the ++ * Surface System Aggregator Module (SSAM). Provides the interface for basic ++ * packet- and request-based communication with the SSAM EC via SSH. ++ */ ++ ++#ifndef _LINUX_SURFACE_AGGREGATOR_SERIAL_HUB_H ++#define _LINUX_SURFACE_AGGREGATOR_SERIAL_HUB_H ++ ++#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 ``struct 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; ++ ++static_assert(sizeof(struct ssh_frame) == 4); ++ ++/* ++ * SSH_FRAME_MAX_PAYLOAD_SIZE - 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. ++ * @tid_out: Output target ID. Should be zero if this an incoming (EC to host) ++ * message. ++ * @tid_in: Input target ID. Should be zero if this is an outgoing (host 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 tid_out; ++ u8 tid_in; ++ u8 iid; ++ __le16 rqid; ++ u8 cid; ++} __packed; ++ ++static_assert(sizeof(struct ssh_command) == 8); ++ ++/* ++ * SSH_COMMAND_MAX_PAYLOAD_SIZE - 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)) ++ ++/* ++ * SSH_MSG_LEN_BASE - 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)) ++ ++/* ++ * SSH_MSG_LEN_CTRL - Length of a SSH control message. ++ * ++ * This is the length of a SSH control message, which is equal to a SSH ++ * message without any payload. ++ */ ++#define SSH_MSG_LEN_CTRL SSH_MSG_LEN_BASE ++ ++/** ++ * SSH_MESSAGE_LENGTH() - Comute lenght of SSH message. ++ * @payload_size: Length of the payload inside the SSH frame. ++ * ++ * Return: Returns the length of a SSH message with payload of specified size. ++ */ ++#define SSH_MESSAGE_LENGTH(payload_size) (SSH_MSG_LEN_BASE + payload_size) ++ ++/** ++ * SSH_COMMAND_MESSAGE_LENGTH() - Compute length of SSH command message. ++ * @payload_size: Length of the command payload. ++ * ++ * Return: Returns the 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) ++ ++/** ++ * SSH_MSGOFFSET_FRAME() - Compute offset in SSH message to specified field in ++ * frame. ++ * @field: The field for which the offset should be computed. ++ * ++ * Return: Returns the offset of the specified &struct ssh_frame field in the ++ * raw SSH message data as. ++ */ ++#define SSH_MSGOFFSET_FRAME(field) \ ++ (sizeof(u16) + offsetof(struct ssh_frame, field)) ++ ++/** ++ * SSH_MSGOFFSET_COMMAND() - Compute offset in SSH message to specified field ++ * in command. ++ * @field: The field for which the offset should be computed. ++ * ++ * Return: Returns the 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)) ++ ++/* ++ * SSH_MSG_SYN - SSH message synchronization (SYN) bytes as u16. ++ */ ++#define SSH_MSG_SYN ((u16)0x55aa) ++ ++/** ++ * ssh_crc() - Compute CRC for SSH messages. ++ * @buf: The pointer pointing to the data for which the CRC should be computed. ++ * @len: The length of the data for which the CRC should be computed. ++ * ++ * Return: Returns the CRC computed on the provided data, as used for SSH ++ * messages. ++ */ ++static inline u16 ssh_crc(const u8 *buf, size_t len) ++{ ++ return crc_ccitt_false(0xffff, buf, len); ++} ++ ++/* ++ * SSH_NUM_EVENTS - The number of reserved event IDs. ++ * ++ * 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 ++ ++/* ++ * SSH_NUM_TARGETS - The number of communication targets used in the protocol. ++ */ ++#define SSH_NUM_TARGETS 2 ++ ++/** ++ * ssh_rqid_next_valid() - Return the next valid request ID. ++ * @rqid: The current request ID. ++ * ++ * Return: Returns the next valid request ID, following the current request ID ++ * provided to this function. This function skips any request IDs reserved for ++ * events. ++ */ ++static inline u16 ssh_rqid_next_valid(u16 rqid) ++{ ++ return rqid > 0 ? rqid + 1u : rqid + SSH_NUM_EVENTS + 1u; ++} ++ ++/** ++ * ssh_rqid_to_event() - Convert request ID to its corresponding event ID. ++ * @rqid: The request ID to convert. ++ */ ++static inline u16 ssh_rqid_to_event(u16 rqid) ++{ ++ return rqid - 1u; ++} ++ ++/** ++ * ssh_rqid_is_event() - Check if given request ID is a valid event ID. ++ * @rqid: The request ID to check. ++ */ ++static inline bool ssh_rqid_is_event(u16 rqid) ++{ ++ return ssh_rqid_to_event(rqid) < SSH_NUM_EVENTS; ++} ++ ++/** ++ * ssh_tc_to_rqid() - Convert target category to its corresponding request ID. ++ * @tc: The target category to convert. ++ */ ++static inline u16 ssh_tc_to_rqid(u8 tc) ++{ ++ return tc; ++} ++ ++/** ++ * ssh_tid_to_index() - Convert target ID to its corresponding target index. ++ * @tid: The target ID to convert. ++ */ ++static inline u8 ssh_tid_to_index(u8 tid) ++{ ++ return tid - 1u; ++} ++ ++/** ++ * ssh_tid_is_valid() - Check if target ID is valid/supported. ++ * @tid: The target ID to check. ++ */ ++static inline bool ssh_tid_is_valid(u8 tid) ++{ ++ return ssh_tid_to_index(tid) < SSH_NUM_TARGETS; ++} ++ ++/** ++ * 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; ++}; ++ ++enum ssam_ssh_tc { ++ /* Known SSH/EC target categories. */ ++ // category 0x00 is invalid for EC use ++ 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, ++}; ++ ++ ++/* -- Packet transport layer (ptl). ----------------------------------------- */ ++ ++/** ++ * enum ssh_packet_base_priority - Base priorities for &struct ssh_packet. ++ * @SSH_PACKET_PRIORITY_FLUSH: Base priority for flush packets. ++ * @SSH_PACKET_PRIORITY_DATA: Base priority for normal data paackets. ++ * @SSH_PACKET_PRIORITY_NAK: Base priority for NAK packets. ++ * @SSH_PACKET_PRIORITY_ACK: Base priority for ACK packets. ++ */ ++enum ssh_packet_base_priority { ++ SSH_PACKET_PRIORITY_FLUSH = 0, /* same as DATA to sequence flush */ ++ SSH_PACKET_PRIORITY_DATA = 0, ++ SSH_PACKET_PRIORITY_NAK = 1, ++ SSH_PACKET_PRIORITY_ACK = 2, ++}; ++ ++/* ++ * Same as SSH_PACKET_PRIORITY() below, only with actual values. ++ */ ++#define __SSH_PACKET_PRIORITY(base, try) \ ++ (((base) << 4) | ((try) & 0x0f)) ++ ++/** ++ * SSH_PACKET_PRIORITY() - Compute packet priority from base priority and ++ * number of tries. ++ * @base: The base priority as suffix of &enum ssh_packet_base_priority, e.g. ++ * ``FLUSH``, ``DATA``, ``ACK``, or ``NAK``. ++ * @try: The number of tries (must be less than 16). ++ * ++ * Compute the combined packet priority. The combined priority is dominated by ++ * the base priority, whereas the number of (re-)tries decides the precedence ++ * of packets with the same base priority, giving higher priority to packets ++ * that already have more tries. ++ * ++ * Return: Returns the computed priority as value fitting inside a &u8. A ++ * higher number means a higher priority. ++ */ ++#define SSH_PACKET_PRIORITY(base, try) \ ++ __SSH_PACKET_PRIORITY(SSH_PACKET_PRIORITY_##base, (try)) ++ ++/** ++ * ssh_packet_priority_get_try() - Get number of tries from packet priority. ++ * @priority: The packet priority. ++ * ++ * Return: Returns the number of tries encoded in the specified packet ++ * priority. ++ */ ++static inline u8 ssh_packet_priority_get_try(u8 priority) ++{ ++ return priority & 0x0f; ++} ++ ++/** ++ * ssh_packet_priority_get_base - Get base priority from packet priority. ++ * @priority: The packet priority. ++ * ++ * Return: Returns the base priority encoded in the given packet priority. ++ */ ++static inline u8 ssh_packet_priority_get_base(u8 priority) ++{ ++ return (priority & 0xf0) >> 4; ++} ++ ++ ++enum ssh_packet_flags { ++ /* state 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, ++ ++ /* type flags */ ++ SSH_PACKET_TY_FLUSH_BIT, ++ SSH_PACKET_TY_SEQUENCED_BIT, ++ SSH_PACKET_TY_BLOCKING_BIT, ++ ++ /* mask for state flags */ ++ 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), ++ ++ /* mask for type flags */ ++ 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 - Callback operations for a SSH packet. ++ * @release: Function called when the packet reference count reaches zero. ++ * This callback must be relied upon to ensure that the packet has ++ * left the transport system(s). ++ * @complete: Function called when the packet is completed, either with ++ * success or failure. In case of failure, the reason for the ++ * failure is indicated by the value of the provided status code ++ * argument. This value will be zero in case of success. Note that ++ * a call to this callback does not guarantee that the packet is ++ * not in use by the transport system any more. ++ */ ++struct ssh_packet_ops { ++ void (*release)(struct ssh_packet *p); ++ void (*complete)(struct ssh_packet *p, int status); ++}; ++ ++/** ++ * struct ssh_packet - SSH transport packet. ++ * @ptl: Pointer to the packet transport layer. May be %NULL if the packet ++ * (or enclosing request) has not been submitted yet. ++ * @refcnt: Reference count of the packet. ++ * @priority: Priority of the packet. Must be computed via ++ * SSH_PACKET_PRIORITY(). ++ * @data: Raw message data. ++ * @data.len: Length of the raw message data. ++ * @data.ptr: Pointer to the raw message data buffer. ++ * @state: State and type flags describing current packet state (dynamic) ++ * and type (static). See &enum ssh_packet_flags for possible ++ * options. ++ * @timestamp: Timestamp specifying when the latest transmission of a ++ * currently pending packet has been started. May be %KTIME_MAX ++ * before or in-between transmission attempts. Used for the packet ++ * timeout implementation. ++ * @queue_node: The list node for the packet queue. ++ * @pending_node: The list node for the set of pending packets. ++ * @ops: Packet operations. ++ */ ++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; ++}; ++ ++struct ssh_packet *ssh_packet_get(struct ssh_packet *p); ++void ssh_packet_put(struct ssh_packet *p); ++ ++/** ++ * ssh_packet_set_data() - Set raw message data of packet. ++ * @p: The packet for which the message data should be set. ++ * @ptr: Pointer to the memory holding the message data. ++ * @len: Length of the message data. ++ * ++ * Sets the raw message data buffer of the packet to the provided memory. The ++ * memory is not copied. Instead, the caller is responsible for management ++ * (i.e. allocation and deallocation) of the memory. The caller must ensure ++ * that the provided memory is valid and contains a valid SSH message, ++ * starting from the time of submission of the packet until the ``release`` ++ * callback has been called. During this time, the memory may not be altered ++ * in any way. ++ */ ++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 { ++ /* state 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, ++ ++ /* type flags */ ++ SSH_REQUEST_TY_FLUSH_BIT, ++ SSH_REQUEST_TY_HAS_RESPONSE_BIT, ++ ++ /* mask for state flags */ ++ 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), ++ ++ /* mask for type flags */ ++ 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 - Callback operations for a SSH request. ++ * @release: Function called when the request's reference count reaches zero. ++ * This callback must be relied upon to ensure that the request has ++ * left the transport systems (both, packet an request systems). ++ * @complete: Function called when the request is completed, either with ++ * success or failure. The command data for the request response ++ * is provided via the &struct ssh_command parameter (``cmd``), ++ * the command payload of the request response via the &struct ++ * ssh_span parameter (``data``). ++ * ++ * If the request does not have any response or has not been ++ * completed with success, both ``cmd`` and ``data`` parameters will ++ * be NULL. If the request response does not have any command ++ * payload, the ``data`` span will be an empty (zero-length) span. ++ * ++ * In case of failure, the reason for the failure is indicated by ++ * the value of the provided status code argument (``status``). This ++ * value will be zero in case of success. ++ * ++ * Note that a call to this callback does not guarantee that the ++ * request is not in use by the transport systems any more. ++ */ ++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 - SSH transport request. ++ * @packet: The underlying SSH transport packet. ++ * @node: List node for the request queue and pending set. ++ * @state: State and type flags describing current request state (dynamic) ++ * and type (static). See &enum ssh_request_flags for possible ++ * options. ++ * @timestamp: Timestamp specifying when we start waiting on the respnse of the ++ * request. This is set once the underlying packet has been completed ++ * and may be %KTIME_MAX before that, or when the request does not ++ * expect a response. Used for the request timeout implementation. ++ * @ops: Request Operations. ++ */ ++struct ssh_request { ++ struct ssh_packet packet; ++ struct list_head node; ++ ++ unsigned long state; ++ ktime_t timestamp; ++ ++ const struct ssh_request_ops *ops; ++}; ++ ++/** ++ * to_ssh_request() - Cast a SSH packet to its enclosing SSH request. ++ * @p: The packet to cast. ++ * ++ * Casts the given &struct ssh_packet to its enclosing &struct ssh_request. ++ * The caller is responsible for making sure that the packet is actually ++ * wrapped in a &struct ssh_request. ++ * ++ * Return: Returns the &struct ssh_request wrapping the provided packet. ++ */ ++static inline struct ssh_request *to_ssh_request(struct ssh_packet *p) ++{ ++ return container_of(p, struct ssh_request, packet); ++} ++ ++/** ++ * ssh_request_get() - Increment reference count of request. ++ * @r: The request to increment the reference count of. ++ * ++ * Increments the reference count of the given request by incrementing the ++ * reference count of the underlying &struct ssh_packet, enclosed in it. ++ * ++ * See also ssh_request_put(), ssh_packet_get(). ++ * ++ * Return: Returns the request provided as input. ++ */ ++static inline struct ssh_request *ssh_request_get(struct ssh_request *r) ++{ ++ ssh_packet_get(&r->packet); ++ return r; ++} ++ ++/** ++ * ssh_request_put() - Decrement reference count of request. ++ * @r: The request to decrement the reference count of. ++ * ++ * Decrements the reference count of the given request by decrementing the ++ * reference count of the underlying &struct ssh_packet, enclosed in it. If ++ * the reference count reaches zero, the ``release`` callback specified in the ++ * request's &struct ssh_request_ops, i.e. ``r->ops->release``, will be ++ * called. ++ * ++ * See also ssh_request_get(), ssh_packet_put(). ++ */ ++static inline void ssh_request_put(struct ssh_request *r) ++{ ++ ssh_packet_put(&r->packet); ++} ++ ++/** ++ * ssh_request_set_data() - Set raw message data of request. ++ * @r: The request for which the message data should be set. ++ * @ptr: Pointer to the memory holding the message data. ++ * @len: Length of the message data. ++ * ++ * Sets the raw message data buffer of the underlying packet to the specified ++ * buffer. Does not copy the actual message data, just sets the buffer pointer ++ * and length. Refer to ssh_packet_set_data() for more details. ++ */ ++static inline void ssh_request_set_data(struct ssh_request *r, u8 *ptr, size_t len) ++{ ++ ssh_packet_set_data(&r->packet, ptr, len); ++} ++ ++#endif /* _LINUX_SURFACE_AGGREGATOR_SERIAL_HUB_H */ diff --git a/scripts/mod/devicetable-offsets.c b/scripts/mod/devicetable-offsets.c -index 054405b90ba4..74d6395bbe91 100644 +index 054405b90ba4..0d66f07989c4 100644 --- a/scripts/mod/devicetable-offsets.c +++ b/scripts/mod/devicetable-offsets.c -@@ -231,5 +231,12 @@ int main(void) +@@ -231,5 +231,13 @@ int main(void) DEVID(wmi_device_id); DEVID_FIELD(wmi_device_id, guid_string); + DEVID(ssam_device_id); + DEVID_FIELD(ssam_device_id, match_flags); ++ DEVID_FIELD(ssam_device_id, domain); + DEVID_FIELD(ssam_device_id, category); + DEVID_FIELD(ssam_device_id, target); + DEVID_FIELD(ssam_device_id, instance); @@ -18212,10 +18422,10 @@ index 054405b90ba4..74d6395bbe91 100644 return 0; } diff --git a/scripts/mod/file2alias.c b/scripts/mod/file2alias.c -index c91eba751804..5406ad2f4fab 100644 +index c91eba751804..ae2f55aa6472 100644 --- a/scripts/mod/file2alias.c +++ b/scripts/mod/file2alias.c -@@ -1335,6 +1335,27 @@ static int do_wmi_entry(const char *filename, void *symval, char *alias) +@@ -1335,6 +1335,28 @@ static int do_wmi_entry(const char *filename, void *symval, char *alias) return 1; } @@ -18227,12 +18437,13 @@ index c91eba751804..5406ad2f4fab 100644 +static int do_ssam_entry(const char *filename, void *symval, char *alias) +{ + DEF_FIELD(symval, ssam_device_id, match_flags); ++ DEF_FIELD(symval, ssam_device_id, domain); + DEF_FIELD(symval, ssam_device_id, category); + DEF_FIELD(symval, ssam_device_id, target); + DEF_FIELD(symval, ssam_device_id, instance); + DEF_FIELD(symval, ssam_device_id, function); + -+ sprintf(alias, "ssam:c%02X", category); ++ sprintf(alias, "ssam:d%02Xc%02X", domain, category); + ADD(alias, "t", match_flags & SSAM_MATCH_TARGET, target); + ADD(alias, "i", match_flags & SSAM_MATCH_INSTANCE, instance); + ADD(alias, "f", match_flags & SSAM_MATCH_FUNCTION, function); @@ -18243,7 +18454,7 @@ index c91eba751804..5406ad2f4fab 100644 /* Does namelen bytes of name exactly match the symbol? */ static bool sym_is(const char *name, unsigned namelen, const char *symbol) { -@@ -1407,6 +1428,7 @@ static const struct devtable devtable[] = { +@@ -1407,6 +1429,7 @@ static const struct devtable devtable[] = { {"typec", SIZE_typec_device_id, do_typec_entry}, {"tee", SIZE_tee_client_device_id, do_tee_entry}, {"wmi", SIZE_wmi_device_id, do_wmi_entry}, diff --git a/patches/5.4/0006-surface-sam-over-hid.patch b/patches/5.4/0006-surface-sam-over-hid.patch index d373ee948..363fd4097 100644 --- a/patches/5.4/0006-surface-sam-over-hid.patch +++ b/patches/5.4/0006-surface-sam-over-hid.patch @@ -1,4 +1,4 @@ -From 09acd33442aaf0bafa6b2cefeb9c2b1d170c9888 Mon Sep 17 00:00:00 2001 +From 8561306d5bcf7c1a58c3036b514f571cf14f618b Mon Sep 17 00:00:00 2001 From: Maximilian Luz Date: Sat, 25 Jul 2020 17:19:53 +0200 Subject: [PATCH 6/7] surface-sam-over-hid diff --git a/patches/5.4/0007-surface-gpe.patch b/patches/5.4/0007-surface-gpe.patch index 47f69ff22..41035f220 100644 --- a/patches/5.4/0007-surface-gpe.patch +++ b/patches/5.4/0007-surface-gpe.patch @@ -1,4 +1,4 @@ -From 19330628c39dba2105c59ca2d3c2b427283ea917 Mon Sep 17 00:00:00 2001 +From ed99f20ce9c57d393a3c8ef85dc1509bdefe7ce3 Mon Sep 17 00:00:00 2001 From: Maximilian Luz Date: Sun, 16 Aug 2020 23:39:56 +0200 Subject: [PATCH 7/7] surface-gpe @@ -6,8 +6,8 @@ Subject: [PATCH 7/7] surface-gpe --- drivers/platform/x86/Kconfig | 9 + drivers/platform/x86/Makefile | 1 + - drivers/platform/x86/surface_gpe.c | 303 +++++++++++++++++++++++++++++ - 3 files changed, 313 insertions(+) + drivers/platform/x86/surface_gpe.c | 307 +++++++++++++++++++++++++++++ + 3 files changed, 317 insertions(+) create mode 100644 drivers/platform/x86/surface_gpe.c diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig @@ -44,16 +44,19 @@ index b3e0a2c6924b..c6e934a73a8d 100644 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 000000000000..10e563f253b9 +index 000000000000..2857e3862ca4 --- /dev/null +++ b/drivers/platform/x86/surface_gpe.c -@@ -0,0 +1,303 @@ +@@ -0,0 +1,307 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Surface GPE/Lid driver to enable wakeup from suspend via the lid by -+ * properly configuring the respective GPEs. ++ * properly configuring the respective GPEs. Required for wakeup via lid on ++ * newer Intel-based Microsoft Surface devices. + */ + ++#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt ++ +#include +#include +#include @@ -205,8 +208,9 @@ index 000000000000..10e563f253b9 + 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); ++ if (ACPI_FAILURE(status)) { ++ dev_err(dev, "failed to set GPE wake mask: %s\n", ++ acpi_format_exception(status)); + return -EINVAL; + } + @@ -229,40 +233,41 @@ index 000000000000..10e563f253b9 +{ + struct surface_lid_device *lid; + u32 gpe_number; -+ int status; ++ acpi_status status; ++ int ret; + -+ status = device_property_read_u32(&pdev->dev, "gpe", &gpe_number); -+ if (status) -+ return -ENODEV; -+ -+ status = acpi_mark_gpe_for_wake(NULL, gpe_number); -+ if (status) { -+ dev_err(&pdev->dev, "failed to mark GPE for wake: %d\n", status); -+ return -EINVAL; ++ ret = device_property_read_u32(&pdev->dev, "gpe", &gpe_number); ++ if (ret) { ++ dev_err(&pdev->dev, "failed to read 'gpe' property: %d\n", ret); ++ return ret; + } + -+ status = acpi_enable_gpe(NULL, gpe_number); -+ if (status) { -+ dev_err(&pdev->dev, "failed to enable GPE: %d\n", status); -+ return -EINVAL; -+ } -+ -+ lid = devm_kzalloc(&pdev->dev, sizeof(struct surface_lid_device), -+ GFP_KERNEL); ++ lid = devm_kzalloc(&pdev->dev, sizeof(*lid), GFP_KERNEL); + if (!lid) + return -ENOMEM; + + lid->gpe_number = gpe_number; + platform_set_drvdata(pdev, lid); + -+ status = surface_lid_enable_wakeup(&pdev->dev, false); -+ if (status) { -+ acpi_disable_gpe(NULL, gpe_number); -+ platform_set_drvdata(pdev, NULL); -+ return status; ++ status = acpi_mark_gpe_for_wake(NULL, gpe_number); ++ if (ACPI_FAILURE(status)) { ++ dev_err(&pdev->dev, "failed to mark GPE for wake: %s\n", ++ acpi_format_exception(status)); ++ return -EINVAL; + } + -+ return 0; ++ status = acpi_enable_gpe(NULL, gpe_number); ++ if (ACPI_FAILURE(status)) { ++ dev_err(&pdev->dev, "failed to enable GPE: %s\n", ++ acpi_format_exception(status)); ++ return -EINVAL; ++ } ++ ++ ret = surface_lid_enable_wakeup(&pdev->dev, false); ++ if (ret) ++ acpi_disable_gpe(NULL, gpe_number); ++ ++ return ret; +} + +static int surface_gpe_remove(struct platform_device *pdev) @@ -273,7 +278,6 @@ index 000000000000..10e563f253b9 + surface_lid_enable_wakeup(&pdev->dev, false); + acpi_disable_gpe(NULL, lid->gpe_number); + -+ platform_set_drvdata(pdev, NULL); + return 0; +} + @@ -292,58 +296,58 @@ index 000000000000..10e563f253b9 +static int __init surface_gpe_init(void) +{ + const struct dmi_system_id *match; -+ const struct property_entry *props; + struct platform_device *pdev; + struct fwnode_handle *fwnode; + int status; + + match = dmi_first_match(dmi_lid_device_table); + if (!match) { -+ pr_info(KBUILD_MODNAME": no device detected, exiting\n"); -+ return 0; ++ pr_info("no compatible Microsoft Surface device found, exiting\n"); ++ return -ENODEV; + } + -+ props = 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; ++ fwnode = fwnode_create_software_node(match->driver_data, NULL); ++ if (IS_ERR(fwnode)) { ++ status = PTR_ERR(fwnode); ++ goto err_node; + } + -+ fwnode = fwnode_create_software_node(props, NULL); -+ if (IS_ERR(fwnode)) { -+ platform_device_put(pdev); -+ platform_driver_unregister(&surface_gpe_driver); -+ return PTR_ERR(fwnode); ++ pdev = platform_device_alloc("surface_gpe", PLATFORM_DEVID_NONE); ++ if (!pdev) { ++ status = -ENOMEM; ++ goto err_alloc; + } + + pdev->dev.fwnode = fwnode; + + status = platform_device_add(pdev); -+ if (status) { -+ platform_device_put(pdev); -+ platform_driver_unregister(&surface_gpe_driver); -+ return status; -+ } ++ if (status) ++ goto err_add; + + surface_gpe_device = pdev; + return 0; ++ ++err_add: ++ platform_device_put(pdev); ++err_alloc: ++ fwnode_remove_software_node(fwnode); ++err_node: ++ platform_driver_unregister(&surface_gpe_driver); ++ return status; +} +module_init(surface_gpe_init); + +static void __exit surface_gpe_exit(void) +{ -+ if (!surface_gpe_device) -+ return; ++ struct fwnode_handle *fwnode = surface_gpe_device->dev.fwnode; + -+ fwnode_remove_software_node(surface_gpe_device->dev.fwnode); + platform_device_unregister(surface_gpe_device); + platform_driver_unregister(&surface_gpe_driver); ++ fwnode_remove_software_node(fwnode); +} +module_exit(surface_gpe_exit); +