From d1d4fa2d2dbe31410e3198062232b2bced0e33ee Mon Sep 17 00:00:00 2001 From: Maximilian Luz Date: Thu, 8 Oct 2020 22:34:44 +0200 Subject: [PATCH] Update v5.4 patches Changes: - SAM: - Fix bug in SAN driver, triggered by thermal events. - Rename Surface Laptop / Book 3 HID input devices. - Implement Surface Laptop 1 and 2 caps-lock light. - Continued cleanup and misc. fixes. - Update docs. Links: - SAM: https://github.com/linux-surface/surface-aggregator-module/commit/73ea41f1ec29225718f58e53c1203033641af0c2 - kernel: https://github.com/linux-surface/kernel/commit/b2f2899bd3fb97f25a832a5f12f3d321fd86c51d --- patches/5.8/0001-surface3-oemb.patch | 2 +- patches/5.8/0002-wifi.patch | 41 +- patches/5.8/0003-ipts.patch | 2 +- patches/5.8/0004-surface-sam.patch | 1641 +++++++++++-------- patches/5.8/0005-surface-sam-over-hid.patch | 2 +- patches/5.8/0006-surface-gpe.patch | 2 +- pkg/arch/kernel/PKGBUILD | 12 +- 7 files changed, 974 insertions(+), 728 deletions(-) diff --git a/patches/5.8/0001-surface3-oemb.patch b/patches/5.8/0001-surface3-oemb.patch index 6b0a7592d..012a50656 100644 --- a/patches/5.8/0001-surface3-oemb.patch +++ b/patches/5.8/0001-surface3-oemb.patch @@ -1,4 +1,4 @@ -From a04d5c9616f422239b00675f1929f3392ba5b224 Mon Sep 17 00:00:00 2001 +From 463107b4f3a4b794406270c8333e66619bdadf05 Mon Sep 17 00:00:00 2001 From: Chih-Wei Huang Date: Tue, 18 Sep 2018 11:01:37 +0800 Subject: [PATCH 1/6] surface3-oemb diff --git a/patches/5.8/0002-wifi.patch b/patches/5.8/0002-wifi.patch index 640ec3e3f..d60050b65 100644 --- a/patches/5.8/0002-wifi.patch +++ b/patches/5.8/0002-wifi.patch @@ -1,4 +1,4 @@ -From 9b82f30679795c4c1ca55d7095cdd67e6e06fcde Mon Sep 17 00:00:00 2001 +From e9076b84d446d05265717b73de4f77e302e78598 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 2/6] wifi @@ -6,15 +6,13 @@ Subject: [PATCH 2/6] wifi --- drivers/net/wireless/marvell/mwifiex/Makefile | 1 + .../net/wireless/marvell/mwifiex/cfg80211.c | 26 ++ - drivers/net/wireless/marvell/mwifiex/fw.h | 2 +- drivers/net/wireless/marvell/mwifiex/main.c | 6 +- drivers/net/wireless/marvell/mwifiex/pcie.c | 84 ++++-- drivers/net/wireless/marvell/mwifiex/pcie.h | 3 + .../wireless/marvell/mwifiex/pcie_quirks.c | 255 ++++++++++++++++++ .../wireless/marvell/mwifiex/pcie_quirks.h | 17 ++ .../net/wireless/marvell/mwifiex/sta_cmd.c | 14 +- - .../wireless/marvell/mwifiex/sta_cmdresp.c | 4 +- - 10 files changed, 377 insertions(+), 35 deletions(-) + 8 files changed, 374 insertions(+), 32 deletions(-) create mode 100644 drivers/net/wireless/marvell/mwifiex/pcie_quirks.c create mode 100644 drivers/net/wireless/marvell/mwifiex/pcie_quirks.h @@ -74,19 +72,6 @@ index 4e4f59c17ded..528eedfbf41c 100644 return mwifiex_drv_set_power(priv, &ps_mode); } -diff --git a/drivers/net/wireless/marvell/mwifiex/fw.h b/drivers/net/wireless/marvell/mwifiex/fw.h -index 8047e307892e..d9f8bdbc817b 100644 ---- a/drivers/net/wireless/marvell/mwifiex/fw.h -+++ b/drivers/net/wireless/marvell/mwifiex/fw.h -@@ -954,7 +954,7 @@ struct mwifiex_tkip_param { - struct mwifiex_aes_param { - u8 pn[WPA_PN_SIZE]; - __le16 key_len; -- u8 key[WLAN_KEY_LEN_CCMP]; -+ u8 key[WLAN_KEY_LEN_CCMP_256]; - } __packed; - - struct mwifiex_wapi_param { diff --git a/drivers/net/wireless/marvell/mwifiex/main.c b/drivers/net/wireless/marvell/mwifiex/main.c index 529099137644..a26eb66865e2 100644 --- a/drivers/net/wireless/marvell/mwifiex/main.c @@ -640,28 +625,6 @@ index 8bd355d7974e..484ba60b51ab 100644 if (drcs) { adapter->drcs_enabled = true; -diff --git a/drivers/net/wireless/marvell/mwifiex/sta_cmdresp.c b/drivers/net/wireless/marvell/mwifiex/sta_cmdresp.c -index 962d8bfe6f10..119ccacd1fcc 100644 ---- a/drivers/net/wireless/marvell/mwifiex/sta_cmdresp.c -+++ b/drivers/net/wireless/marvell/mwifiex/sta_cmdresp.c -@@ -619,7 +619,7 @@ static int mwifiex_ret_802_11_key_material_v2(struct mwifiex_private *priv, - key_v2 = &resp->params.key_material_v2; - - len = le16_to_cpu(key_v2->key_param_set.key_params.aes.key_len); -- if (len > WLAN_KEY_LEN_CCMP) -+ if (len > sizeof(key_v2->key_param_set.key_params.aes.key)) - return -EINVAL; - - if (le16_to_cpu(key_v2->action) == HostCmd_ACT_GEN_SET) { -@@ -635,7 +635,7 @@ static int mwifiex_ret_802_11_key_material_v2(struct mwifiex_private *priv, - return 0; - - memset(priv->aes_key_v2.key_param_set.key_params.aes.key, 0, -- WLAN_KEY_LEN_CCMP); -+ sizeof(key_v2->key_param_set.key_params.aes.key)); - priv->aes_key_v2.key_param_set.key_params.aes.key_len = - cpu_to_le16(len); - memcpy(priv->aes_key_v2.key_param_set.key_params.aes.key, -- 2.28.0 diff --git a/patches/5.8/0003-ipts.patch b/patches/5.8/0003-ipts.patch index 93588f333..80acab962 100644 --- a/patches/5.8/0003-ipts.patch +++ b/patches/5.8/0003-ipts.patch @@ -1,4 +1,4 @@ -From 5ad0753795c3808b796a78bd31efbe7c6edb031e Mon Sep 17 00:00:00 2001 +From 64af858e6b6b7ae3f2c477119221da33344f24c7 Mon Sep 17 00:00:00 2001 From: Dorian Stoll Date: Thu, 30 Jul 2020 13:21:53 +0200 Subject: [PATCH 3/6] ipts diff --git a/patches/5.8/0004-surface-sam.patch b/patches/5.8/0004-surface-sam.patch index a1eec5e40..8e307b362 100644 --- a/patches/5.8/0004-surface-sam.patch +++ b/patches/5.8/0004-surface-sam.patch @@ -1,4 +1,4 @@ -From 46b062660d497e28753879aaa71f11ed7cebbfaa Mon Sep 17 00:00:00 2001 +From 0eec025cb7b92013244f93c750c94359356be834 Mon Sep 17 00:00:00 2001 From: Maximilian Luz Date: Mon, 17 Aug 2020 01:23:20 +0200 Subject: [PATCH 4/6] surface-sam @@ -7,7 +7,7 @@ Subject: [PATCH 4/6] surface-sam Documentation/driver-api/index.rst | 1 + .../surface_aggregator/client-api.rst | 38 + .../driver-api/surface_aggregator/client.rst | 394 +++ - .../surface_aggregator/clients/dbgdev.rst | 130 + + .../surface_aggregator/clients/cdev.rst | 85 + .../surface_aggregator/clients/index.rst | 21 + .../surface_aggregator/clients/san.rst | 44 + .../driver-api/surface_aggregator/index.rst | 21 + @@ -26,11 +26,11 @@ Subject: [PATCH 4/6] surface-sam .../clients/surface_acpi_notify.c | 884 ++++++ .../clients/surface_aggregator_cdev.c | 228 ++ .../clients/surface_aggregator_registry.c | 646 +++++ - .../clients/surface_battery.c | 1195 ++++++++ + .../clients/surface_battery.c | 1196 ++++++++ .../surface_aggregator/clients/surface_dtx.c | 591 ++++ - .../surface_aggregator/clients/surface_hid.c | 495 ++++ + .../surface_aggregator/clients/surface_hid.c | 554 ++++ .../clients/surface_hotplug.c | 1285 +++++++++ - .../clients/surface_keyboard.c | 333 +++ + .../clients/surface_keyboard.c | 601 ++++ .../clients/surface_perfmode.c | 122 + drivers/misc/surface_aggregator/controller.c | 2554 +++++++++++++++++ drivers/misc/surface_aggregator/controller.h | 288 ++ @@ -51,10 +51,10 @@ Subject: [PATCH 4/6] surface-sam include/uapi/linux/surface_aggregator/cdev.h | 58 + scripts/mod/devicetable-offsets.c | 8 + scripts/mod/file2alias.c | 23 + - 47 files changed, 18183 insertions(+) + 47 files changed, 18466 insertions(+) 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/cdev.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 @@ -108,11 +108,11 @@ index 6567187e7687..e36363f0972b 100644 vfio-mediated-device 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..b93608a1be38 +index 000000000000..a1117d57036a --- /dev/null +++ b/Documentation/driver-api/surface_aggregator/client-api.rst @@ -0,0 +1,38 @@ -+.. SPDX-License-Identifier: GPL-2.0 ++.. SPDX-License-Identifier: GPL-2.0+ + +=============================== +Client Driver API Documentation @@ -152,11 +152,11 @@ index 000000000000..b93608a1be38 + :export: diff --git a/Documentation/driver-api/surface_aggregator/client.rst b/Documentation/driver-api/surface_aggregator/client.rst new file mode 100644 -index 000000000000..c12343f599b2 +index 000000000000..41c17bb63bef --- /dev/null +++ b/Documentation/driver-api/surface_aggregator/client.rst @@ -0,0 +1,394 @@ -+.. SPDX-License-Identifier: GPL-2.0 ++.. SPDX-License-Identifier: GPL-2.0+ + +.. |ssam_controller| replace:: :c:type:`struct ssam_controller ` +.. |ssam_device| replace:: :c:type:`struct ssam_device ` @@ -550,35 +550,33 @@ index 000000000000..c12343f599b2 +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/surface_aggregator/clients/dbgdev.rst b/Documentation/driver-api/surface_aggregator/clients/dbgdev.rst +diff --git a/Documentation/driver-api/surface_aggregator/clients/cdev.rst b/Documentation/driver-api/surface_aggregator/clients/cdev.rst new file mode 100644 -index 000000000000..e45d7e7fd13f +index 000000000000..63b5afcb89b5 --- /dev/null -+++ b/Documentation/driver-api/surface_aggregator/clients/dbgdev.rst -@@ -0,0 +1,130 @@ -+.. SPDX-License-Identifier: GPL-2.0 ++++ b/Documentation/driver-api/surface_aggregator/clients/cdev.rst +@@ -0,0 +1,85 @@ ++.. SPDX-License-Identifier: GPL-2.0+ + +.. |u8| replace:: :c:type:`u8 ` +.. |u16| replace:: :c:type:`u16 ` -+.. |ssam_dbg_request| replace:: :c:type:`struct ssam_dbg_request ` ++.. |ssam_cdev_request| replace:: :c:type:`struct ssam_cdev_request ` +.. |ssam_request_flags| replace:: :c:type:`enum ssam_request_flags ` + -+======================================= -+SSAM Debug Device and DebugFS Interface -+======================================= ++============================== ++User-Space EC Interface (cdev) ++============================== + -+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 ``surface_aggregator_cdev`` module provides a misc-device 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 -+``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. ++The provided interface is accessible through the ``/dev/surface/aggregator`` ++device-file. All functionality of this interface is provided via IOCTLs. ++These IOCTLs and their respective input/output parameter structs are defined in ++``include/uapi/linux/surface_aggregator/cdev.h``. + + +Controller IOCTLs @@ -597,62 +595,19 @@ index 000000000000..e45d7e7fd13f + - Description + + * - ``0xA5`` -+ - ``0`` -+ - ``R`` -+ - ``GETVERSION`` -+ - Get DebugFS controller interface version. -+ -+ * - ``0xA5`` + - ``1`` + - ``WR`` + - ``REQUEST`` + - Perform synchronous SAM request. + + -+``GETVERSION`` -+-------------- -+ -+Defined as ``_IOR(0xA5, 0, __u32)``. -+ -+Gets the current interface version. This should be used to check for changes -+in the interface and determine if certain functionality is available. While -+the interface should under normal circumstances kept backward compatible, as -+this is a debug interface, backwards compatibility is not guaranteed. -+ -+The version number follows the semantic versioning scheme, roughly meaning -+that an increment in the highest non-zero version number signals a breaking -+change. It can be decomposed as follows: -+ -+.. flat-table:: Version Number Format -+ :widths: 2 1 3 -+ :header-rows: 1 -+ -+ * - Offset (bytes) -+ - Type -+ - Description -+ -+ * - ``0`` -+ - |u8| -+ - Major -+ -+ * - ``1`` -+ - |u8| -+ - Minor -+ -+ * - ``2`` -+ - |u16| -+ - Patch -+ -+The interface version is currently ``0.1.0``, i.e. ``0x00010000``. -+ -+ +``REQUEST`` +----------- + -+Defined as ``_IOWR(0xA5, 1, struct ssam_dbg_request)``. ++Defined as ``_IOWR(0xA5, 1, struct ssam_cdev_request)``. + +Executes a synchronous SAM request. The request specification is passed in -+as argument of type |ssam_dbg_request|, which is then written to/modified ++as argument of type |ssam_cdev_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 @@ -684,15 +639,15 @@ index 000000000000..e45d7e7fd13f + +A full definition of the argument struct is provided below: + -+.. kernel-doc:: drivers/misc/surface_aggregator/clients/surface_aggregator_debugfs.c -+ :functions: ssam_dbg_request ++.. kernel-doc:: include/uapi/linux/surface_aggregator/cdev.h ++ :functions: ssam_cdev_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 +index 000000000000..3ccabce23271 --- /dev/null +++ b/Documentation/driver-api/surface_aggregator/clients/index.rst @@ -0,0 +1,21 @@ -+.. SPDX-License-Identifier: GPL-2.0 ++.. SPDX-License-Identifier: GPL-2.0+ + +=========================== +Client Driver Documentation @@ -704,7 +659,7 @@ index 000000000000..7cd91fc75e91 +.. toctree:: + :maxdepth: 1 + -+ dbgdev ++ cdev + san + +.. only:: subproject and html @@ -715,11 +670,11 @@ index 000000000000..7cd91fc75e91 + * :ref:`genindex` 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..f91c0a7ab884 +index 000000000000..1bf830ad367d --- /dev/null +++ b/Documentation/driver-api/surface_aggregator/clients/san.rst @@ -0,0 +1,44 @@ -+.. SPDX-License-Identifier: GPL-2.0 ++.. SPDX-License-Identifier: GPL-2.0+ + +.. |san_client_link| replace:: :c:func:`san_client_link` +.. |san_dgpu_notifier_register| replace:: :c:func:`san_dgpu_notifier_register` @@ -765,11 +720,11 @@ index 000000000000..f91c0a7ab884 + :export: 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 +index 000000000000..9fa70eedca59 --- /dev/null +++ b/Documentation/driver-api/surface_aggregator/index.rst @@ -0,0 +1,21 @@ -+.. SPDX-License-Identifier: GPL-2.0 ++.. SPDX-License-Identifier: GPL-2.0+ + +======================================= +Surface System Aggregator Module (SSAM) @@ -792,11 +747,11 @@ index 000000000000..5eff57c1836d + * :ref:`genindex` 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..910fa9ec736c +index 000000000000..db6a70119f49 --- /dev/null +++ b/Documentation/driver-api/surface_aggregator/internal-api.rst @@ -0,0 +1,67 @@ -+.. SPDX-License-Identifier: GPL-2.0 ++.. SPDX-License-Identifier: GPL-2.0+ + +========================== +Internal API Documentation @@ -865,11 +820,11 @@ index 000000000000..910fa9ec736c +.. 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 +index 000000000000..6c020b87ad62 --- /dev/null +++ b/Documentation/driver-api/surface_aggregator/internal.rst @@ -0,0 +1,50 @@ -+.. SPDX-License-Identifier: GPL-2.0 ++.. SPDX-License-Identifier: GPL-2.0+ + +===================== +Core Driver Internals @@ -921,11 +876,11 @@ index 000000000000..0504247d8786 +API and interface options for other kernel drivers. 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 +index 000000000000..06d49ce001e7 --- /dev/null +++ b/Documentation/driver-api/surface_aggregator/overview.rst @@ -0,0 +1,76 @@ -+.. SPDX-License-Identifier: GPL-2.0 ++.. SPDX-License-Identifier: GPL-2.0+ + +======== +Overview @@ -1003,11 +958,11 @@ index 000000000000..7b7a6d9e8e22 +See :doc:`ssh` for a more technical protocol documentation. 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 +index 000000000000..0b68228010e9 --- /dev/null +++ b/Documentation/driver-api/surface_aggregator/ssh.rst @@ -0,0 +1,343 @@ -+.. SPDX-License-Identifier: GPL-2.0 ++.. SPDX-License-Identifier: GPL-2.0+ + +.. |u8| replace:: :c:type:`u8 ` +.. |u16| replace:: :c:type:`u16 ` @@ -2111,7 +2066,7 @@ index 000000000000..6d1707bc5705 +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..ddc27a196fec +index 000000000000..25b8866ed81f --- /dev/null +++ b/drivers/misc/surface_aggregator/clients/surface_acpi_notify.c @@ -0,0 +1,884 @@ @@ -2515,7 +2470,7 @@ index 000000000000..ddc27a196fec +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); ++ struct san_data *d = to_san_data(nf, nf_tmp); + + return san_evt_tmp(event, d->dev) ? SSAM_NOTIF_HANDLED : 0; +} @@ -3887,10 +3842,10 @@ index 000000000000..869816f61d38 +MODULE_LICENSE("GPL"); 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..4b63713bcd1e +index 000000000000..9eae87410b16 --- /dev/null +++ b/drivers/misc/surface_aggregator/clients/surface_battery.c -@@ -0,0 +1,1195 @@ +@@ -0,0 +1,1196 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Surface battery and AC device driver. @@ -3969,10 +3924,10 @@ index 000000000000..4b63713bcd1e + __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]; ++ __u8 model[21]; ++ __u8 serial[11]; ++ __u8 type[5]; ++ __u8 oem_info[21]; +} __packed; + +static_assert(sizeof(struct spwr_bix) == 119); @@ -4303,7 +4258,7 @@ index 000000000000..4b63713bcd1e + + mutex_lock(&bat->lock); + unit = get_unaligned_le32(&bat->bix.power_unit); -+ present = spwr_battery_present(bat); ++ present = spwr_battery_present(bat); + + status = spwr_battery_update_bix_unlocked(bat); + if (status) @@ -4859,6 +4814,7 @@ index 000000000000..4b63713bcd1e + + 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; @@ -5685,10 +5641,10 @@ index 000000000000..d667f605b74d +MODULE_LICENSE("GPL"); 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..1c029345cde6 +index 000000000000..0dfc22568c66 --- /dev/null +++ b/drivers/misc/surface_aggregator/clients/surface_hid.c -@@ -0,0 +1,495 @@ +@@ -0,0 +1,554 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Surface System Aggregator Module (SSAM) HID device driver. @@ -5696,492 +5652,551 @@ index 000000000000..1c029345cde6 + * Provides support for HID input devices connected via the Surface System + * Aggregator Module. + * -+ * Copyright (C) 2019-2020 Blaž Hrastnik ++ * Copyright (C) 2019-2020 Blaž Hrastnik , ++ * Maximilian Luz + */ + ++#include +#include +#include +#include +#include -+#include +#include ++#include + -+#include ++#include ++#include ++ ++#define SHID_RETRY 3 ++#define shid_retry(fn, args...) ssam_retry(fn, SHID_RETRY, args) + + -+#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; ++enum surface_hid_descriptor_entry { ++ SURFACE_HID_DESC_HID = 0, ++ SURFACE_HID_DESC_REPORT = 1, ++ SURFACE_HID_DESC_ATTRS = 2, +}; + -+struct sid_vhf { -+ struct ssam_device *sdev; ++struct surface_hid_descriptor { ++ __u8 desc_len; // = 9 ++ __u8 desc_type; // = HID_DT_HID ++ __le16 hid_version; ++ __u8 country_code; ++ __u8 num_descriptors; // = 1 ++ ++ __u8 report_desc_type; // = HID_DT_REPORT ++ __le16 report_desc_len; ++} __packed; ++ ++static_assert(sizeof(struct surface_hid_descriptor) == 9); ++ ++struct surface_hid_attributes { ++ __le32 length; ++ __le16 vendor; ++ __le16 product; ++ __le16 version; ++ __u8 _unknown[22]; ++} __packed; ++ ++static_assert(sizeof(struct surface_hid_attributes) == 32); ++ ++struct surface_hid_buffer_slice { ++ __u8 entry; ++ __le32 offset; ++ __le32 length; ++ __u8 end; ++ __u8 data[]; ++} __packed; ++ ++static_assert(sizeof(struct surface_hid_buffer_slice) == 10); ++ ++enum surface_hid_cid { ++ SURFACE_HID_CID_OUTPUT_REPORT = 0x01, ++ SURFACE_HID_CID_GET_FEATURE_REPORT = 0x02, ++ SURFACE_HID_CID_SET_FEATURE_REPORT = 0x03, ++ SURFACE_HID_CID_GET_DESCRIPTOR = 0x04, ++}; ++ ++struct surface_hid_device { ++ struct device *dev; ++ struct ssam_controller *ctrl; ++ struct ssam_device_uid uid; ++ ++ struct surface_hid_descriptor hid_desc; ++ struct surface_hid_attributes attrs; ++ + struct ssam_event_notifier notif; -+ + struct hid_device *hid; -+ unsigned long state; +}; + + -+static int sid_vhf_hid_start(struct hid_device *hid) ++/* -- SAM interface. -------------------------------------------------------- */ ++ ++static int ssam_hid_get_descriptor(struct surface_hid_device *shid, u8 entry, ++ u8 *buf, size_t len) +{ -+ 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 = {}; ++ u8 buffer[sizeof(struct surface_hid_buffer_slice) + 0x76]; ++ struct surface_hid_buffer_slice *slice; + struct ssam_request rqst; + struct ssam_response rsp; ++ u32 buffer_len, offset, length; + int status; + -+ data.rqst.id = 2; -+ data.rqst.offset = 0; -+ data.rqst.length = 0x76; -+ data.rqst.end = 0; ++ /* ++ * Note: The 0x76 above has been chosen because that's what's used by ++ * the Windows driver. Together with the header, this leads to a 128 ++ * byte payload in total. ++ */ + -+ rqst.target_category = sdev->uid.category; -+ rqst.target_id = sdev->uid.target; -+ rqst.command_id = 0x04; -+ rqst.instance_id = sdev->uid.instance; ++ buffer_len = ARRAY_SIZE(buffer) - sizeof(struct surface_hid_buffer_slice); ++ ++ rqst.target_category = shid->uid.category; ++ rqst.target_id = shid->uid.target; ++ rqst.command_id = SURFACE_HID_CID_GET_DESCRIPTOR; ++ rqst.instance_id = shid->uid.instance; + rqst.flags = SSAM_REQUEST_HAS_RESPONSE; -+ rqst.length = sizeof(struct surface_sam_sid_vhf_meta_rqst); -+ rqst.payload = (u8 *)&data.rqst; ++ rqst.length = sizeof(struct surface_hid_buffer_slice); ++ rqst.payload = buffer; + -+ rsp.capacity = sizeof(struct surface_sam_sid_vhf_meta_resp); -+ rsp.length = 0; -+ rsp.pointer = (u8 *)&data; ++ rsp.capacity = ARRAY_SIZE(buffer); ++ rsp.pointer = buffer; + -+ status = ssam_request_sync(sdev->ctrl, &rqst, &rsp); -+ if (status) -+ return status; ++ slice = (struct surface_hid_buffer_slice *)buffer; ++ slice->entry = entry; ++ slice->end = 0; + -+ *meta = data.data.meta; ++ offset = 0; ++ length = buffer_len; + -+ return 0; -+} ++ while (!slice->end && offset < len) { ++ put_unaligned_le32(offset, &slice->offset); ++ put_unaligned_le32(length, &slice->length); + -+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; ++ rsp.length = 0; + -+ 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); ++ status = shid_retry(ssam_request_sync, shid->ctrl, &rqst, &rsp); ++ if (status) + return status; -+ } -+ memcpy(buf + data.rqst.offset, data.data.pld, data.rqst.length); + -+ data.rqst.offset += data.rqst.length; ++ offset = get_unaligned_le32(&slice->offset); ++ length = get_unaligned_le32(&slice->length); ++ ++ // don't mess stuff up in case we receive garbage ++ if (length > buffer_len || offset > len) ++ return -EPROTO; ++ ++ if (offset + length > len) ++ length = len - offset; ++ ++ memcpy(buf + offset, &slice->data[0], length); ++ ++ offset += length; ++ length = buffer_len; + } + -+ *desc = buf; -+ *size = len; ++ if (offset != len) { ++ dev_err(shid->dev, "unexpected descriptor length: got %u, " ++ "expected %zu\n", offset, len); ++ return -EPROTO; ++ } + + return 0; +} + -+static int sid_vhf_hid_parse(struct hid_device *hid) ++static int ssam_hid_set_raw_report(struct surface_hid_device *shid, ++ u8 report_id, bool feature, u8 *buf, ++ size_t len) +{ -+ 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); ++ if (feature) ++ cid = SURFACE_HID_CID_SET_FEATURE_REPORT; ++ else ++ cid = SURFACE_HID_CID_OUTPUT_REPORT; + -+ // 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.target_category = shid->uid.category; ++ rqst.target_id = shid->uid.target; ++ rqst.instance_id = shid->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.flags = 0; ++ rqst.length = len; + rqst.payload = buf; + ++ buf[0] = report_id; ++ ++ return shid_retry(ssam_request_sync, shid->ctrl, &rqst, NULL); ++} ++ ++static int ssam_hid_get_raw_report(struct surface_hid_device *shid, ++ u8 report_id, u8 *buf, size_t len) ++{ ++ struct ssam_request rqst; ++ struct ssam_response rsp; ++ ++ rqst.target_category = shid->uid.category; ++ rqst.target_id = shid->uid.target; ++ rqst.instance_id = shid->uid.instance; ++ rqst.command_id = SURFACE_HID_CID_GET_FEATURE_REPORT; ++ rqst.flags = 0; ++ rqst.length = sizeof(u8); ++ rqst.payload = &report_id; ++ + 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; ++ return shid_retry(ssam_request_sync, shid->ctrl, &rqst, &rsp); +} + -+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) ++static u32 ssam_hid_event_fn(struct ssam_event_notifier *nf, ++ const struct ssam_event *event) +{ -+ 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); ++ struct surface_hid_device *shid; + int status; + -+ if (event->command_id != 0x00 && event->command_id != 0x03 && event->command_id != 0x04) ++ shid = container_of(nf, struct surface_hid_device, notif); ++ ++ if (event->command_id != 0x00) + 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(shid->hid, HID_INPUT_REPORT, ++ (u8 *)&event->data[0], event->length, 0); + -+ 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; +} + + ++/* -- Device descriptor access. --------------------------------------------- */ ++ ++static int surface_hid_load_hid_descriptor(struct surface_hid_device *shid) ++{ ++ int status; ++ ++ status = ssam_hid_get_descriptor(shid, SURFACE_HID_DESC_HID, ++ (u8 *)&shid->hid_desc, sizeof(shid->hid_desc)); ++ if (status) ++ return status; ++ ++ if (shid->hid_desc.desc_len != sizeof(shid->hid_desc)) { ++ dev_err(shid->dev, "unexpected HID descriptor length: got %u, " ++ "expected %zu\n", shid->hid_desc.desc_len, ++ sizeof(shid->hid_desc)); ++ return -EPROTO; ++ } ++ ++ if (shid->hid_desc.desc_type != HID_DT_HID) { ++ dev_err(shid->dev, "unexpected HID descriptor type: got 0x%x, " ++ "expected 0x%x\n", shid->hid_desc.desc_type, ++ HID_DT_HID); ++ return -EPROTO; ++ } ++ ++ if (shid->hid_desc.num_descriptors != 1) { ++ dev_err(shid->dev, "unexpected number of descriptors: got %u, " ++ "expected 1\n", shid->hid_desc.num_descriptors); ++ return -EPROTO; ++ } ++ ++ if (shid->hid_desc.report_desc_type != HID_DT_REPORT) { ++ dev_err(shid->dev, "unexpected report descriptor type: got 0x%x, " ++ "expected 0x%x\n", shid->hid_desc.report_desc_type, ++ HID_DT_REPORT); ++ return -EPROTO; ++ } ++ ++ return 0; ++} ++ ++static int surface_hid_load_device_attributes(struct surface_hid_device *shid) ++{ ++ int status; ++ ++ status = ssam_hid_get_descriptor(shid, SURFACE_HID_DESC_ATTRS, ++ (u8 *)&shid->attrs, sizeof(shid->attrs)); ++ if (status) ++ return status; ++ ++ if (get_unaligned_le32(&shid->attrs.length) != sizeof(shid->attrs)) { ++ dev_err(shid->dev, "unexpected attribute length: got %u, " ++ "expected %zu\n", get_unaligned_le32(&shid->attrs.length), ++ sizeof(shid->attrs)); ++ return -EPROTO; ++ } ++ ++ return 0; ++} ++ ++ ++/* -- Transport driver. ----------------------------------------------------- */ ++ ++static int shid_output_report(struct surface_hid_device *shid, u8 report_id, ++ u8 *data, size_t len) ++{ ++ int status; ++ ++ status = ssam_hid_set_raw_report(shid, report_id, false, data, len); ++ return status >= 0 ? len : status; ++} ++ ++static int shid_get_feature_report(struct surface_hid_device *shid, ++ u8 report_id, u8 *data, size_t len) ++{ ++ int status; ++ ++ status = ssam_hid_get_raw_report(shid, report_id, data, len); ++ return status >= 0 ? len : status; ++} ++ ++static int shid_set_feature_report(struct surface_hid_device *shid, ++ u8 report_id, u8 *data, size_t len) ++{ ++ int status; ++ ++ status = ssam_hid_set_raw_report(shid, report_id, true, data, len); ++ return status >= 0 ? len : status; ++} ++ ++ ++static int surface_hid_start(struct hid_device *hid) ++{ ++ struct surface_hid_device *shid = hid->driver_data; ++ ++ return ssam_notifier_register(shid->ctrl, &shid->notif); ++} ++ ++static void surface_hid_stop(struct hid_device *hid) ++{ ++ struct surface_hid_device *shid = hid->driver_data; ++ ++ // Note: This call will log errors for us, so ignore them here. ++ ssam_notifier_unregister(shid->ctrl, &shid->notif); ++} ++ ++static int surface_hid_open(struct hid_device *hid) ++{ ++ return 0; ++} ++ ++static void surface_hid_close(struct hid_device *hid) ++{ ++} ++ ++static int surface_hid_parse(struct hid_device *hid) ++{ ++ struct surface_hid_device *shid = hid->driver_data; ++ size_t len = get_unaligned_le16(&shid->hid_desc.report_desc_len); ++ u8 *buf; ++ int status; ++ ++ buf = kzalloc(len, GFP_KERNEL); ++ if (!buf) ++ return -ENOMEM; ++ ++ status = ssam_hid_get_descriptor(shid, SURFACE_HID_DESC_REPORT, buf, len); ++ if (!status) ++ status = hid_parse_report(hid, buf, len); ++ ++ kfree(buf); ++ return status; ++} ++ ++static int surface_hid_raw_request(struct hid_device *hid, ++ unsigned char reportnum, u8 *buf, size_t len, ++ unsigned char rtype, int reqtype) ++{ ++ struct surface_hid_device *shid = hid->driver_data; ++ ++ if (rtype == HID_OUTPUT_REPORT && reqtype == HID_REQ_SET_REPORT) ++ return shid_output_report(shid, reportnum, buf, len); ++ ++ else if (rtype == HID_FEATURE_REPORT && reqtype == HID_REQ_GET_REPORT) ++ return shid_get_feature_report(shid, reportnum, buf, len); ++ ++ else if (rtype == HID_FEATURE_REPORT && reqtype == HID_REQ_SET_REPORT) ++ return shid_set_feature_report(shid, reportnum, buf, len); ++ ++ return -EIO; ++} ++ ++static struct hid_ll_driver surface_hid_ll_driver = { ++ .start = surface_hid_start, ++ .stop = surface_hid_stop, ++ .open = surface_hid_open, ++ .close = surface_hid_close, ++ .parse = surface_hid_parse, ++ .raw_request = surface_hid_raw_request, ++}; ++ ++ ++/* -- Common device setup. -------------------------------------------------- */ ++ ++static int surface_hid_device_add(struct surface_hid_device *shid) ++{ ++ int status; ++ ++ status = surface_hid_load_hid_descriptor(shid); ++ if (status) ++ return status; ++ ++ status = surface_hid_load_device_attributes(shid); ++ if (status) ++ return status; ++ ++ shid->hid = hid_allocate_device(); ++ if (IS_ERR(shid->hid)) ++ return PTR_ERR(shid->hid); ++ ++ shid->hid->dev.parent = shid->dev; ++ shid->hid->bus = BUS_VIRTUAL; // TODO: BUS_SURFACE ++ shid->hid->vendor = cpu_to_le16(shid->attrs.vendor); ++ shid->hid->product = cpu_to_le16(shid->attrs.product); ++ shid->hid->version = cpu_to_le16(shid->hid_desc.hid_version); ++ shid->hid->country = shid->hid_desc.country_code; ++ ++ snprintf(shid->hid->name, sizeof(shid->hid->name), ++ "Microsoft Surface %04X:%04X", ++ shid->hid->vendor, shid->hid->product); ++ ++ strlcpy(shid->hid->phys, dev_name(shid->dev), sizeof(shid->hid->phys)); ++ ++ shid->hid->driver_data = shid; ++ shid->hid->ll_driver = &surface_hid_ll_driver; ++ ++ status = hid_add_device(shid->hid); ++ if (status) ++ hid_destroy_device(shid->hid); ++ ++ return status; ++} ++ ++static void surface_hid_device_destroy(struct surface_hid_device *shid) ++{ ++ hid_destroy_device(shid->hid); ++} ++ ++ ++/* -- PM ops. --------------------------------------------------------------- */ ++ +#ifdef CONFIG_PM + -+static int surface_sam_sid_vhf_suspend(struct device *dev) ++static int surface_hid_suspend(struct device *dev) +{ -+ struct sid_vhf *vhf = dev_get_drvdata(dev); ++ struct surface_hid_device *d = dev_get_drvdata(dev); + -+ if (vhf->hid->driver && vhf->hid->driver->suspend) -+ return vhf->hid->driver->suspend(vhf->hid, PMSG_SUSPEND); ++ if (d->hid->driver && d->hid->driver->suspend) ++ return d->hid->driver->suspend(d->hid, PMSG_SUSPEND); + + return 0; +} + -+static int surface_sam_sid_vhf_resume(struct device *dev) ++static int surface_hid_resume(struct device *dev) +{ -+ struct sid_vhf *vhf = dev_get_drvdata(dev); ++ struct surface_hid_device *d = dev_get_drvdata(dev); + -+ if (vhf->hid->driver && vhf->hid->driver->resume) -+ return vhf->hid->driver->resume(vhf->hid); ++ if (d->hid->driver && d->hid->driver->resume) ++ return d->hid->driver->resume(d->hid); + + return 0; +} + -+static int surface_sam_sid_vhf_freeze(struct device *dev) ++static int surface_hid_freeze(struct device *dev) +{ -+ struct sid_vhf *vhf = dev_get_drvdata(dev); ++ struct surface_hid_device *d = dev_get_drvdata(dev); + -+ if (vhf->hid->driver && vhf->hid->driver->suspend) -+ return vhf->hid->driver->suspend(vhf->hid, PMSG_FREEZE); ++ if (d->hid->driver && d->hid->driver->suspend) ++ return d->hid->driver->suspend(d->hid, PMSG_FREEZE); + + return 0; +} + -+static int surface_sam_sid_vhf_poweroff(struct device *dev) ++static int surface_hid_poweroff(struct device *dev) +{ -+ struct sid_vhf *vhf = dev_get_drvdata(dev); ++ struct surface_hid_device *d = dev_get_drvdata(dev); + -+ if (vhf->hid->driver && vhf->hid->driver->suspend) -+ return vhf->hid->driver->suspend(vhf->hid, PMSG_HIBERNATE); ++ if (d->hid->driver && d->hid->driver->suspend) ++ return d->hid->driver->suspend(d->hid, PMSG_HIBERNATE); + + return 0; +} + -+static int surface_sam_sid_vhf_restore(struct device *dev) ++static int surface_hid_restore(struct device *dev) +{ -+ struct sid_vhf *vhf = dev_get_drvdata(dev); ++ struct surface_hid_device *d = dev_get_drvdata(dev); + -+ if (vhf->hid->driver && vhf->hid->driver->reset_resume) -+ return vhf->hid->driver->reset_resume(vhf->hid); ++ if (d->hid->driver && d->hid->driver->reset_resume) ++ return d->hid->driver->reset_resume(d->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, ++const struct dev_pm_ops surface_hid_pm_ops = { ++ .freeze = surface_hid_freeze, ++ .thaw = surface_hid_resume, ++ .suspend = surface_hid_suspend, ++ .resume = surface_hid_resume, ++ .poweroff = surface_hid_poweroff, ++ .restore = surface_hid_restore, +}; + +#else /* CONFIG_PM */ + -+struct dev_pm_ops surface_sam_sid_vhf_pm_ops = { }; ++const struct dev_pm_ops surface_hid_pm_ops = { }; + +#endif /* CONFIG_PM */ + + -+static int surface_sam_sid_vhf_probe(struct ssam_device *sdev) ++/* -- Driver setup. --------------------------------------------------------- */ ++ ++static int surface_hid_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; ++ struct surface_hid_device *shid; + -+ p = ssam_device_get_match_data(sdev); -+ if (!p) -+ return -ENODEV; -+ -+ vhf = devm_kzalloc(&sdev->dev, sizeof(*vhf), GFP_KERNEL); -+ if (!vhf) ++ shid = devm_kzalloc(&sdev->dev, sizeof(*shid), GFP_KERNEL); ++ if (!shid) + return -ENOMEM; + -+ status = vhf_get_metadata(sdev, &meta); -+ if (status) -+ return status; ++ shid->dev = &sdev->dev; ++ shid->ctrl = sdev->ctrl; ++ shid->uid = sdev->uid; + -+ hid = sid_vhf_create_hid_device(sdev, &meta); -+ if (IS_ERR(hid)) -+ return PTR_ERR(hid); ++ shid->notif.base.priority = 1; ++ shid->notif.base.fn = ssam_hid_event_fn; ++ shid->notif.event.reg = SSAM_EVENT_REGISTRY_REG, ++ shid->notif.event.id.target_category = sdev->uid.category; ++ shid->notif.event.id.instance = sdev->uid.instance; ++ shid->notif.event.mask = SSAM_EVENT_MASK_STRICT; ++ shid->notif.event.flags = 0; + -+ 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; ++ ssam_device_set_drvdata(sdev, shid); ++ return surface_hid_device_add(shid); +} + -+static void surface_sam_sid_vhf_remove(struct ssam_device *sdev) ++static void surface_hid_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); ++ surface_hid_device_destroy(ssam_device_get_drvdata(sdev)); +} + -+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 -+ }, ++static const struct ssam_device_id surface_hid_match[] = { ++ { SSAM_SDEV(HID, 0x02, SSAM_ANY_IID, 0x00) }, + { }, +}; -+MODULE_DEVICE_TABLE(ssam, surface_sam_sid_vhf_match); ++MODULE_DEVICE_TABLE(ssam, surface_hid_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, ++static struct ssam_device_driver surface_hid_driver = { ++ .probe = surface_hid_probe, ++ .remove = surface_hid_remove, ++ .match_table = surface_hid_match, + .driver = { + .name = "surface_hid", -+ .pm = &surface_sam_sid_vhf_pm_ops, ++ .pm = &surface_hid_pm_ops, + .probe_type = PROBE_PREFER_ASYNCHRONOUS, + }, +}; -+module_ssam_device_driver(surface_sam_sid_vhf); ++module_ssam_device_driver(surface_hid_driver); + +MODULE_AUTHOR("Blaž Hrastnik "); ++MODULE_AUTHOR("Maximilian Luz "); +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 @@ -7477,10 +7492,10 @@ index 000000000000..f18cc17d019d +MODULE_LICENSE("GPL"); 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..3d1269229d92 +index 000000000000..df3664e0341c --- /dev/null +++ b/drivers/misc/surface_aggregator/clients/surface_keyboard.c -@@ -0,0 +1,333 @@ +@@ -0,0 +1,601 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Surface System Aggregator Module (SSAM) legacy HID input device driver. @@ -7491,6 +7506,7 @@ index 000000000000..3d1269229d92 + * Copyright (C) 2019-2020 Maximilian Luz + */ + ++#include +#include +#include +#include @@ -7498,178 +7514,461 @@ index 000000000000..3d1269229d92 +#include +#include +#include ++#include + -+#include ++#include ++#include ++ ++#define SHID_RETRY 3 ++#define shid_retry(fn, args...) ssam_retry(fn, SHID_RETRY, args) + + -+#define USB_VENDOR_ID_MICROSOFT 0x045e -+#define USB_DEVICE_ID_MS_VHF 0xf001 ++enum surface_hid_descriptor_entry { ++ SURFACE_HID_DESC_HID = 0, ++ SURFACE_HID_DESC_REPORT = 1, ++ SURFACE_HID_DESC_ATTRS = 2, ++}; + -+#define VHF_INPUT_NAME "Microsoft Virtual HID Framework Device" ++struct surface_hid_descriptor { ++ __u8 desc_len; // = 9 ++ __u8 desc_type; // = HID_DT_HID ++ __le16 hid_version; ++ __u8 country_code; ++ __u8 num_descriptors; // = 1 + ++ __u8 report_desc_type; // = HID_DT_REPORT ++ __le16 report_desc_len; ++} __packed; + -+struct vhf_drvdata { -+ struct platform_device *dev; ++static_assert(sizeof(struct surface_hid_descriptor) == 9); ++ ++struct surface_hid_attributes { ++ __le32 length; ++ __le16 vendor; ++ __le16 product; ++ __le16 version; ++ __u8 _unknown[22]; ++} __packed; ++ ++static_assert(sizeof(struct surface_hid_attributes) == 32); ++ ++#define KBD_FEATURE_REPORT_SIZE 7 // 6 + report ID ++ ++enum surface_kbd_cid { ++ SURFACE_KBD_CID_GET_DESCRIPTOR = 0x00, ++ SURFACE_KBD_CID_SET_CAPSLOCK_LED = 0x01, ++ SURFACE_KBD_CID_EVT_INPUT_GENERIC = 0x03, ++ SURFACE_KBD_CID_EVT_INPUT_HOTKEYS = 0x04, ++ SURFACE_KBD_CID_GET_FEATURE_REPORT = 0x0b, ++}; ++ ++struct surface_hid_device { ++ struct device *dev; + struct ssam_controller *ctrl; ++ struct ssam_device_uid uid; ++ ++ struct surface_hid_descriptor hid_desc; ++ struct surface_hid_attributes attrs; + + struct ssam_event_notifier notif; -+ + struct hid_device *hid; +}; + + -+/* -+ * These report descriptors have been extracted from a Surface Book 2. -+ * They seems to be similar enough to be usable on the Surface Laptop. -+ */ -+static const u8 vhf_hid_desc[] = { -+ // keyboard descriptor (event command ID 0x03) -+ 0x05, 0x01, /* Usage Page (Desktop), */ -+ 0x09, 0x06, /* Usage (Keyboard), */ -+ 0xA1, 0x01, /* Collection (Application), */ -+ 0x85, 0x01, /* Report ID (1), */ -+ 0x15, 0x00, /* Logical Minimum (0), */ -+ 0x25, 0x01, /* Logical Maximum (1), */ -+ 0x75, 0x01, /* Report Size (1), */ -+ 0x95, 0x08, /* Report Count (8), */ -+ 0x05, 0x07, /* Usage Page (Keyboard), */ -+ 0x19, 0xE0, /* Usage Minimum (KB Leftcontrol), */ -+ 0x29, 0xE7, /* Usage Maximum (KB Right GUI), */ -+ 0x81, 0x02, /* Input (Variable), */ -+ 0x75, 0x08, /* Report Size (8), */ -+ 0x95, 0x0A, /* Report Count (10), */ -+ 0x19, 0x00, /* Usage Minimum (None), */ -+ 0x29, 0x91, /* Usage Maximum (KB LANG2), */ -+ 0x26, 0xFF, 0x00, /* Logical Maximum (255), */ -+ 0x81, 0x00, /* Input, */ -+ 0x05, 0x0C, /* Usage Page (Consumer), */ -+ 0x0A, 0xC0, 0x02, /* Usage (02C0h), */ -+ 0xA1, 0x02, /* Collection (Logical), */ -+ 0x1A, 0xC1, 0x02, /* Usage Minimum (02C1h), */ -+ 0x2A, 0xC6, 0x02, /* Usage Maximum (02C6h), */ -+ 0x95, 0x06, /* Report Count (6), */ -+ 0xB1, 0x03, /* Feature (Constant, Variable), */ -+ 0xC0, /* End Collection, */ -+ 0x05, 0x08, /* Usage Page (LED), */ -+ 0x19, 0x01, /* Usage Minimum (01h), */ -+ 0x29, 0x03, /* Usage Maximum (03h), */ -+ 0x75, 0x01, /* Report Size (1), */ -+ 0x95, 0x03, /* Report Count (3), */ -+ 0x25, 0x01, /* Logical Maximum (1), */ -+ 0x91, 0x02, /* Output (Variable), */ -+ 0x95, 0x05, /* Report Count (5), */ -+ 0x91, 0x01, /* Output (Constant), */ -+ 0xC0, /* End Collection, */ ++/* -- SAM interface. -------------------------------------------------------- */ + -+ // media key descriptor (event command ID 0x04) -+ 0x05, 0x0C, /* Usage Page (Consumer), */ -+ 0x09, 0x01, /* Usage (Consumer Control), */ -+ 0xA1, 0x01, /* Collection (Application), */ -+ 0x85, 0x03, /* Report ID (3), */ -+ 0x75, 0x10, /* Report Size (16), */ -+ 0x15, 0x00, /* Logical Minimum (0), */ -+ 0x26, 0xFF, 0x03, /* Logical Maximum (1023), */ -+ 0x19, 0x00, /* Usage Minimum (00h), */ -+ 0x2A, 0xFF, 0x03, /* Usage Maximum (03FFh), */ -+ 0x81, 0x00, /* Input, */ -+ 0xC0, /* End Collection, */ -+}; -+ -+ -+static int vhf_hid_start(struct hid_device *hid) ++static int ssam_kbd_get_descriptor(struct surface_hid_device *shid, u8 entry, ++ u8 *buf, size_t len) +{ -+ hid_dbg(hid, "%s\n", __func__); -+ return 0; -+} -+ -+static void vhf_hid_stop(struct hid_device *hid) -+{ -+ hid_dbg(hid, "%s\n", __func__); -+} -+ -+static int vhf_hid_open(struct hid_device *hid) -+{ -+ hid_dbg(hid, "%s\n", __func__); -+ return 0; -+} -+ -+static void vhf_hid_close(struct hid_device *hid) -+{ -+ hid_dbg(hid, "%s\n", __func__); -+} -+ -+static int vhf_hid_parse(struct hid_device *hid) -+{ -+ return hid_parse_report(hid, (u8 *)vhf_hid_desc, ARRAY_SIZE(vhf_hid_desc)); -+} -+ -+static int vhf_hid_raw_request(struct hid_device *hid, unsigned char reportnum, -+ u8 *buf, size_t len, unsigned char rtype, -+ int reqtype) -+{ -+ hid_dbg(hid, "%s\n", __func__); -+ return 0; -+} -+ -+static int vhf_hid_output_report(struct hid_device *hid, u8 *buf, size_t len) -+{ -+ hid_dbg(hid, "%s\n", __func__); -+ print_hex_dump_debug("report:", DUMP_PREFIX_OFFSET, 16, 1, buf, len, false); -+ -+ return len; -+} -+ -+static struct hid_ll_driver vhf_hid_ll_driver = { -+ .start = vhf_hid_start, -+ .stop = vhf_hid_stop, -+ .open = vhf_hid_open, -+ .close = vhf_hid_close, -+ .parse = vhf_hid_parse, -+ .raw_request = vhf_hid_raw_request, -+ .output_report = vhf_hid_output_report, -+}; -+ -+ -+static struct hid_device *vhf_create_hid_device(struct platform_device *pdev) -+{ -+ struct hid_device *hid; -+ -+ hid = hid_allocate_device(); -+ if (IS_ERR(hid)) -+ return hid; -+ -+ hid->dev.parent = &pdev->dev; -+ -+ hid->bus = BUS_VIRTUAL; -+ hid->vendor = USB_VENDOR_ID_MICROSOFT; -+ hid->product = USB_DEVICE_ID_MS_VHF; -+ -+ hid->ll_driver = &vhf_hid_ll_driver; -+ -+ sprintf(hid->name, "%s", VHF_INPUT_NAME); -+ -+ return hid; -+} -+ -+static u32 vhf_event_handler(struct ssam_event_notifier *nf, const struct ssam_event *event) -+{ -+ struct vhf_drvdata *drvdata = container_of(nf, struct vhf_drvdata, notif); ++ struct ssam_request rqst; ++ struct ssam_response rsp; + int status; + -+ if (event->command_id == 0x03 || event->command_id == 0x04) { -+ status = hid_input_report(drvdata->hid, HID_INPUT_REPORT, (u8 *)&event->data[0], event->length, 1); -+ return ssam_notifier_from_errno(status) | SSAM_NOTIF_HANDLED; ++ rqst.target_category = shid->uid.category; ++ rqst.target_id = shid->uid.target; ++ rqst.command_id = SURFACE_KBD_CID_GET_DESCRIPTOR; ++ rqst.instance_id = shid->uid.instance; ++ rqst.flags = SSAM_REQUEST_HAS_RESPONSE; ++ rqst.length = sizeof(u8); ++ rqst.payload = &entry; ++ ++ rsp.capacity = len; ++ rsp.length = 0; ++ rsp.pointer = buf; ++ ++ status = shid_retry(ssam_request_sync, shid->ctrl, &rqst, &rsp); ++ if (status) ++ return status; ++ ++ if (rsp.length != len) { ++ dev_err(shid->dev, "invalid descriptor length: got %zu, " ++ "expected, %zu\n", rsp.length, len); ++ return -EPROTO; ++ } ++ ++ return 0; ++} ++ ++static int ssam_kbd_set_caps_led(struct surface_hid_device *shid, bool value) ++{ ++ struct ssam_request rqst; ++ u8 value_u8 = value; ++ ++ rqst.target_category = shid->uid.category; ++ rqst.target_id = shid->uid.target; ++ rqst.command_id = SURFACE_KBD_CID_SET_CAPSLOCK_LED; ++ rqst.instance_id = shid->uid.instance; ++ rqst.flags = 0; ++ rqst.length = sizeof(u8); ++ rqst.payload = &value_u8; ++ ++ return shid_retry(ssam_request_sync, shid->ctrl, &rqst, NULL); ++} ++ ++static int ssam_kbd_get_feature_report(struct surface_hid_device *shid, u8 *buf, ++ size_t len) ++{ ++ struct ssam_request rqst; ++ struct ssam_response rsp; ++ u8 payload = 0; ++ int status; ++ ++ rqst.target_category = shid->uid.category; ++ rqst.target_id = shid->uid.target; ++ rqst.command_id = SURFACE_KBD_CID_GET_FEATURE_REPORT; ++ rqst.instance_id = shid->uid.instance; ++ rqst.flags = SSAM_REQUEST_HAS_RESPONSE; ++ rqst.length = sizeof(u8); ++ rqst.payload = &payload; ++ ++ rsp.capacity = len; ++ rsp.length = 0; ++ rsp.pointer = buf; ++ ++ status = shid_retry(ssam_request_sync, shid->ctrl, &rqst, &rsp); ++ if (status) ++ return status; ++ ++ if (rsp.length != len) { ++ dev_err(shid->dev, "invalid feature report length: got %zu, " ++ "expected, %zu\n", rsp.length, len); ++ return -EPROTO; ++ } ++ ++ return 0; ++} ++ ++static bool ssam_kbd_is_input_event(const struct ssam_event *event) ++{ ++ if (event->command_id == SURFACE_KBD_CID_EVT_INPUT_GENERIC) ++ return true; ++ ++ if (event->command_id == SURFACE_KBD_CID_EVT_INPUT_HOTKEYS) ++ return true; ++ ++ return false; ++} ++ ++static u32 ssam_kbd_event_fn(struct ssam_event_notifier *nf, ++ const struct ssam_event *event) ++{ ++ struct surface_hid_device *shid; ++ int status; ++ ++ shid = container_of(nf, struct surface_hid_device, notif); ++ ++ /* ++ * Check against device UID manually, as registry and device target ++ * category doesn't line up. ++ */ ++ ++ if (shid->uid.category != event->target_category) ++ return 0; ++ ++ if (shid->uid.target != event->target_id) ++ return 0; ++ ++ if (shid->uid.instance != event->instance_id) ++ return 0; ++ ++ if (!ssam_kbd_is_input_event(event)) ++ return 0; ++ ++ status = hid_input_report(shid->hid, HID_INPUT_REPORT, ++ (u8 *)&event->data[0], event->length, 0); ++ ++ return ssam_notifier_from_errno(status) | SSAM_NOTIF_HANDLED; ++} ++ ++ ++/* -- Device descriptor access. --------------------------------------------- */ ++ ++static int surface_hid_load_hid_descriptor(struct surface_hid_device *shid) ++{ ++ int status; ++ ++ status = ssam_kbd_get_descriptor(shid, SURFACE_HID_DESC_HID, ++ (u8 *)&shid->hid_desc, sizeof(shid->hid_desc)); ++ if (status) ++ return status; ++ ++ if (shid->hid_desc.desc_len != sizeof(shid->hid_desc)) { ++ dev_err(shid->dev, "unexpected HID descriptor length: got %u, " ++ "expected %zu\n", shid->hid_desc.desc_len, ++ sizeof(shid->hid_desc)); ++ return -EPROTO; ++ } ++ ++ if (shid->hid_desc.desc_type != HID_DT_HID) { ++ dev_err(shid->dev, "unexpected HID descriptor type: got 0x%x, " ++ "expected 0x%x\n", shid->hid_desc.desc_type, ++ HID_DT_HID); ++ return -EPROTO; ++ } ++ ++ if (shid->hid_desc.num_descriptors != 1) { ++ dev_err(shid->dev, "unexpected number of descriptors: got %u, " ++ "expected 1\n", shid->hid_desc.num_descriptors); ++ return -EPROTO; ++ } ++ ++ if (shid->hid_desc.report_desc_type != HID_DT_REPORT) { ++ dev_err(shid->dev, "unexpected report descriptor type: got 0x%x, " ++ "expected 0x%x\n", shid->hid_desc.report_desc_type, ++ HID_DT_REPORT); ++ return -EPROTO; ++ } ++ ++ return 0; ++} ++ ++static int surface_hid_load_device_attributes(struct surface_hid_device *shid) ++{ ++ int status; ++ ++ status = ssam_kbd_get_descriptor(shid, SURFACE_HID_DESC_ATTRS, ++ (u8 *)&shid->attrs, sizeof(shid->attrs)); ++ if (status) ++ return status; ++ ++ if (get_unaligned_le32(&shid->attrs.length) != sizeof(shid->attrs)) { ++ dev_err(shid->dev, "unexpected attribute length: got %u, " ++ "expected %zu\n", get_unaligned_le32(&shid->attrs.length), ++ sizeof(shid->attrs)); ++ return -EPROTO; + } + + return 0; +} + + ++/* -- Transport driver. ----------------------------------------------------- */ ++ ++static int skbd_get_caps_led_value(struct hid_device *hid, u8 report_id, ++ u8 *data, size_t len) ++{ ++ struct hid_field *field; ++ unsigned int offset, size; ++ int i; ++ ++ // get led field ++ field = hidinput_get_led_field(hid); ++ if (!field) ++ return -ENOENT; ++ ++ // check if we got the correct report ++ if (len != hid_report_len(field->report)) ++ return -ENOENT; ++ ++ if (report_id != field->report->id) ++ return -ENOENT; ++ ++ // get caps lock led index ++ for (i = 0; i < field->report_count; i++) ++ if ((field->usage[i].hid & 0xffff) == 0x02) ++ break; ++ ++ if (i == field->report_count) ++ return -ENOENT; ++ ++ // extract value ++ size = field->report_size; ++ offset = field->report_offset + i * size; ++ return !!hid_field_extract(hid, data + 1, size, offset); ++} ++ ++static int skbd_output_report(struct surface_hid_device *shid, u8 report_id, ++ u8 *data, size_t len) ++{ ++ int caps_led; ++ int status; ++ ++ caps_led = skbd_get_caps_led_value(shid->hid, report_id, data, len); ++ if (caps_led < 0) ++ return -EIO; // only caps output reports are supported ++ ++ status = ssam_kbd_set_caps_led(shid, caps_led); ++ if (status < 0) ++ return status; ++ ++ return len; ++} ++ ++static int skbd_get_feature_report(struct surface_hid_device *shid, ++ u8 report_id, u8 *data, size_t len) ++{ ++ u8 report[KBD_FEATURE_REPORT_SIZE]; ++ int status; ++ ++ /* ++ * The keyboard only has a single hard-coded read-only feature report ++ * of size KBD_FEATURE_REPORT_SIZE. Try to load it and compare its ++ * report ID against the requested one. ++ */ ++ ++ if (len < ARRAY_SIZE(report)) ++ return -ENOSPC; ++ ++ status = ssam_kbd_get_feature_report(shid, report, ARRAY_SIZE(report)); ++ if (status < 0) ++ return status; ++ ++ if (report_id != report[0]) ++ return -ENOENT; ++ ++ memcpy(data, report, ARRAY_SIZE(report)); ++ return len; ++} ++ ++ ++static int surface_hid_start(struct hid_device *hid) ++{ ++ struct surface_hid_device *shid = hid->driver_data; ++ ++ return ssam_notifier_register(shid->ctrl, &shid->notif); ++} ++ ++static void surface_hid_stop(struct hid_device *hid) ++{ ++ struct surface_hid_device *shid = hid->driver_data; ++ ++ // Note: This call will log errors for us, so ignore them here. ++ ssam_notifier_unregister(shid->ctrl, &shid->notif); ++} ++ ++static int surface_hid_open(struct hid_device *hid) ++{ ++ return 0; ++} ++ ++static void surface_hid_close(struct hid_device *hid) ++{ ++} ++ ++static int surface_hid_parse(struct hid_device *hid) ++{ ++ struct surface_hid_device *shid = hid->driver_data; ++ size_t len = get_unaligned_le16(&shid->hid_desc.report_desc_len); ++ u8 *buf; ++ int status; ++ ++ buf = kzalloc(len, GFP_KERNEL); ++ if (!buf) ++ return -ENOMEM; ++ ++ status = ssam_kbd_get_descriptor(shid, SURFACE_HID_DESC_REPORT, buf, len); ++ if (!status) ++ status = hid_parse_report(hid, buf, len); ++ ++ kfree(buf); ++ return status; ++} ++ ++static int surface_hid_raw_request(struct hid_device *hid, ++ unsigned char reportnum, u8 *buf, size_t len, ++ unsigned char rtype, int reqtype) ++{ ++ struct surface_hid_device *shid = hid->driver_data; ++ ++ hid_info(hid, "%s: reportnum=%d, rtype=%d, reqtype=%d\n", ++ __func__, reportnum, rtype, reqtype); ++ ++ print_hex_dump(KERN_INFO, "report: ", DUMP_PREFIX_OFFSET, 16, 1, ++ buf, len, false); ++ ++ if (rtype == HID_OUTPUT_REPORT && reqtype == HID_REQ_SET_REPORT) ++ return skbd_output_report(shid, reportnum, buf, len); ++ ++ else if (rtype == HID_FEATURE_REPORT && reqtype == HID_REQ_GET_REPORT) ++ return skbd_get_feature_report(shid, reportnum, buf, len); ++ ++ return -EIO; ++} ++ ++static struct hid_ll_driver surface_hid_ll_driver = { ++ .start = surface_hid_start, ++ .stop = surface_hid_stop, ++ .open = surface_hid_open, ++ .close = surface_hid_close, ++ .parse = surface_hid_parse, ++ .raw_request = surface_hid_raw_request, ++}; ++ ++ ++/* -- Common device setup. -------------------------------------------------- */ ++ ++static int surface_hid_device_add(struct surface_hid_device *shid) ++{ ++ int status; ++ ++ status = surface_hid_load_hid_descriptor(shid); ++ if (status) ++ return status; ++ ++ status = surface_hid_load_device_attributes(shid); ++ if (status) ++ return status; ++ ++ shid->hid = hid_allocate_device(); ++ if (IS_ERR(shid->hid)) ++ return PTR_ERR(shid->hid); ++ ++ shid->hid->dev.parent = shid->dev; ++ shid->hid->bus = BUS_VIRTUAL; // TODO: BUS_SURFACE ++ shid->hid->vendor = cpu_to_le16(shid->attrs.vendor); ++ shid->hid->product = cpu_to_le16(shid->attrs.product); ++ shid->hid->version = cpu_to_le16(shid->hid_desc.hid_version); ++ shid->hid->country = shid->hid_desc.country_code; ++ ++ snprintf(shid->hid->name, sizeof(shid->hid->name), ++ "Microsoft Surface %04X:%04X", ++ shid->hid->vendor, shid->hid->product); ++ ++ strlcpy(shid->hid->phys, dev_name(shid->dev), sizeof(shid->hid->phys)); ++ ++ shid->hid->driver_data = shid; ++ shid->hid->ll_driver = &surface_hid_ll_driver; ++ ++ status = hid_add_device(shid->hid); ++ if (status) ++ hid_destroy_device(shid->hid); ++ ++ return status; ++} ++ ++static void surface_hid_device_destroy(struct surface_hid_device *shid) ++{ ++ hid_destroy_device(shid->hid); ++} ++ ++ ++/* -- PM ops. --------------------------------------------------------------- */ ++ +#ifdef CONFIG_PM + -+static int surface_sam_vhf_suspend(struct device *dev) ++static int surface_hid_suspend(struct device *dev) +{ -+ struct vhf_drvdata *d = dev_get_drvdata(dev); ++ struct surface_hid_device *d = dev_get_drvdata(dev); + + if (d->hid->driver && d->hid->driver->suspend) + return d->hid->driver->suspend(d->hid, PMSG_SUSPEND); @@ -7677,9 +7976,9 @@ index 000000000000..3d1269229d92 + return 0; +} + -+static int surface_sam_vhf_resume(struct device *dev) ++static int surface_hid_resume(struct device *dev) +{ -+ struct vhf_drvdata *d = dev_get_drvdata(dev); ++ struct surface_hid_device *d = dev_get_drvdata(dev); + + if (d->hid->driver && d->hid->driver->resume) + return d->hid->driver->resume(d->hid); @@ -7687,9 +7986,9 @@ index 000000000000..3d1269229d92 + return 0; +} + -+static int surface_sam_vhf_freeze(struct device *dev) ++static int surface_hid_freeze(struct device *dev) +{ -+ struct vhf_drvdata *d = dev_get_drvdata(dev); ++ struct surface_hid_device *d = dev_get_drvdata(dev); + + if (d->hid->driver && d->hid->driver->suspend) + return d->hid->driver->suspend(d->hid, PMSG_FREEZE); @@ -7697,9 +7996,9 @@ index 000000000000..3d1269229d92 + return 0; +} + -+static int surface_sam_vhf_poweroff(struct device *dev) ++static int surface_hid_poweroff(struct device *dev) +{ -+ struct vhf_drvdata *d = dev_get_drvdata(dev); ++ struct surface_hid_device *d = dev_get_drvdata(dev); + + if (d->hid->driver && d->hid->driver->suspend) + return d->hid->driver->suspend(d->hid, PMSG_HIBERNATE); @@ -7707,9 +8006,9 @@ index 000000000000..3d1269229d92 + return 0; +} + -+static int surface_sam_vhf_restore(struct device *dev) ++static int surface_hid_restore(struct device *dev) +{ -+ struct vhf_drvdata *d = dev_get_drvdata(dev); ++ struct surface_hid_device *d = dev_get_drvdata(dev); + + if (d->hid->driver && d->hid->driver->reset_resume) + return d->hid->driver->reset_resume(d->hid); @@ -7717,27 +8016,28 @@ index 000000000000..3d1269229d92 + return 0; +} + -+struct dev_pm_ops surface_sam_vhf_pm_ops = { -+ .freeze = surface_sam_vhf_freeze, -+ .thaw = surface_sam_vhf_resume, -+ .suspend = surface_sam_vhf_suspend, -+ .resume = surface_sam_vhf_resume, -+ .poweroff = surface_sam_vhf_poweroff, -+ .restore = surface_sam_vhf_restore, ++const struct dev_pm_ops surface_hid_pm_ops = { ++ .freeze = surface_hid_freeze, ++ .thaw = surface_hid_resume, ++ .suspend = surface_hid_suspend, ++ .resume = surface_hid_resume, ++ .poweroff = surface_hid_poweroff, ++ .restore = surface_hid_restore, +}; + +#else /* CONFIG_PM */ + -+struct dev_pm_ops surface_sam_vhf_pm_ops = { }; ++const struct dev_pm_ops surface_hid_pm_ops = { }; + +#endif /* CONFIG_PM */ + + -+static int surface_sam_vhf_probe(struct platform_device *pdev) ++/* -- Driver setup. --------------------------------------------------------- */ ++ ++static int surface_kbd_probe(struct platform_device *pdev) +{ + struct ssam_controller *ctrl; -+ struct vhf_drvdata *drvdata; -+ struct hid_device *hid; ++ struct surface_hid_device *shid; + int status; + + // add device link to EC @@ -7745,71 +8045,54 @@ index 000000000000..3d1269229d92 + if (status) + return status == -ENXIO ? -EPROBE_DEFER : status; + -+ drvdata = devm_kzalloc(&pdev->dev, sizeof(*drvdata), GFP_KERNEL); -+ if (!drvdata) ++ shid = devm_kzalloc(&pdev->dev, sizeof(*shid), GFP_KERNEL); ++ if (!shid) + return -ENOMEM; + -+ hid = vhf_create_hid_device(pdev); -+ if (IS_ERR(hid)) -+ return PTR_ERR(hid); ++ shid->dev = &pdev->dev; ++ shid->ctrl = ctrl; + -+ status = hid_add_device(hid); -+ if (status) -+ goto err_add_hid; ++ shid->uid.domain = SSAM_DOMAIN_SERIALHUB; ++ shid->uid.category = SSAM_SSH_TC_KBD; ++ shid->uid.target = 2; ++ shid->uid.instance = 0; ++ shid->uid.function = 0; + -+ drvdata->dev = pdev; -+ drvdata->ctrl = ctrl; -+ drvdata->hid = hid; ++ shid->notif.base.priority = 1; ++ shid->notif.base.fn = ssam_kbd_event_fn; ++ shid->notif.event.reg = SSAM_EVENT_REGISTRY_SAM; ++ shid->notif.event.id.target_category = shid->uid.category; ++ shid->notif.event.id.instance = shid->uid.instance; ++ shid->notif.event.mask = SSAM_EVENT_MASK_NONE; ++ shid->notif.event.flags = 0; + -+ drvdata->notif.base.priority = 1; -+ drvdata->notif.base.fn = vhf_event_handler; -+ drvdata->notif.event.reg = SSAM_EVENT_REGISTRY_SAM; -+ drvdata->notif.event.id.target_category = SSAM_SSH_TC_KBD; -+ drvdata->notif.event.id.instance = 0; -+ drvdata->notif.event.mask = SSAM_EVENT_MASK_NONE; -+ drvdata->notif.event.flags = 0; -+ -+ platform_set_drvdata(pdev, drvdata); -+ -+ status = ssam_notifier_register(ctrl, &drvdata->notif); -+ if (status) -+ goto err_add_hid; -+ -+ return 0; -+ -+err_add_hid: -+ hid_destroy_device(hid); -+ return status; ++ platform_set_drvdata(pdev, shid); ++ return surface_hid_device_add(shid); +} + -+static int surface_sam_vhf_remove(struct platform_device *pdev) ++static int surface_kbd_remove(struct platform_device *pdev) +{ -+ struct vhf_drvdata *drvdata = platform_get_drvdata(pdev); -+ -+ ssam_notifier_unregister(drvdata->ctrl, &drvdata->notif); -+ hid_destroy_device(drvdata->hid); -+ ++ surface_hid_device_destroy(platform_get_drvdata(pdev)); + return 0; +} + -+ -+static const struct acpi_device_id surface_sam_vhf_match[] = { ++static const struct acpi_device_id surface_kbd_match[] = { + { "MSHW0096" }, + { }, +}; -+MODULE_DEVICE_TABLE(acpi, surface_sam_vhf_match); ++MODULE_DEVICE_TABLE(acpi, surface_kbd_match); + -+static struct platform_driver surface_sam_vhf = { -+ .probe = surface_sam_vhf_probe, -+ .remove = surface_sam_vhf_remove, ++static struct platform_driver surface_kbd_driver = { ++ .probe = surface_kbd_probe, ++ .remove = surface_kbd_remove, + .driver = { + .name = "surface_keyboard", -+ .acpi_match_table = surface_sam_vhf_match, -+ .pm = &surface_sam_vhf_pm_ops, ++ .acpi_match_table = surface_kbd_match, ++ .pm = &surface_hid_pm_ops, + .probe_type = PROBE_PREFER_ASYNCHRONOUS, + }, +}; -+module_platform_driver(surface_sam_vhf); ++module_platform_driver(surface_kbd_driver); + +MODULE_AUTHOR("Maximilian Luz "); +MODULE_DESCRIPTION("Legacy HID keyboard driver for Surface System Aggregator Module"); diff --git a/patches/5.8/0005-surface-sam-over-hid.patch b/patches/5.8/0005-surface-sam-over-hid.patch index 31e9bf0b3..ddaed28e6 100644 --- a/patches/5.8/0005-surface-sam-over-hid.patch +++ b/patches/5.8/0005-surface-sam-over-hid.patch @@ -1,4 +1,4 @@ -From 2d0655adddf7d426ddeb3a1b85134674293e7aca Mon Sep 17 00:00:00 2001 +From 0c676d0a9104f3bae99ce722c0daca6b536869e4 Mon Sep 17 00:00:00 2001 From: Maximilian Luz Date: Sat, 25 Jul 2020 17:19:53 +0200 Subject: [PATCH 5/6] surface-sam-over-hid diff --git a/patches/5.8/0006-surface-gpe.patch b/patches/5.8/0006-surface-gpe.patch index 6be491e36..e0f68d022 100644 --- a/patches/5.8/0006-surface-gpe.patch +++ b/patches/5.8/0006-surface-gpe.patch @@ -1,4 +1,4 @@ -From 980f9293ff6b0c2abb2b8b91b3df63f62f832cad Mon Sep 17 00:00:00 2001 +From 068ef6fccf870d379a749378f71d0e21a7d4f38c Mon Sep 17 00:00:00 2001 From: Maximilian Luz Date: Sun, 16 Aug 2020 23:39:56 +0200 Subject: [PATCH 6/6] surface-gpe diff --git a/pkg/arch/kernel/PKGBUILD b/pkg/arch/kernel/PKGBUILD index 6a86e3c7c..54060d824 100644 --- a/pkg/arch/kernel/PKGBUILD +++ b/pkg/arch/kernel/PKGBUILD @@ -41,12 +41,12 @@ sha256sums=('aa509b88cd57f909d5aaaf6d337d9553b828bfc58eea79a1e660f5fe09bcb4c5' '181330a9cf4517abbbe29b93165bc859ad8ca14a43582f4e1d69aae2b5ecc2c9' 'ba10bbf15bdc395691d14be9ce62d9f68d02eb9f59530743b6db34987953c9a8' '8cd2b019aac6d3807a5cdcbbbe0aad81e63193ff3e8dffd7a79d4a1421b858f6' - 'cc6a195f5145d6d99f7f4758bc31009f4227247f2af43de9e9f72af5b0cdd7a7' - '53fcd223d74941ed10a8f7ee0a8c3fa01bd8a2515ff5f629a680c3fbcf7cd1e1' - '7e20487e8aa4814a6442823802931e6e2d4f784cd903f922f7d78c4241519e58' - '2f4aeec03ca00933e893a56e1e9236e2dd208886f9957c0f6b9bbec2d80785d8' - 'b7437281fce14010fbbeafdfbcc06c0210507ff56d7df1e689bba8b79bd09ccd' - 'ae0d4045afcd32e5d7a61d03738c8f47b679676291e144fe353fb1e8e1187dff') + '95577055962cd42cca9db7c69169e6c78e6e5e98cdeac4cb52805fffabaa7369' + '62098e2effe680bd04445f902d3f181287c8c55f557215e5e5bb98cf38e64b1f' + '26d630e3fa500d02c6f9214033cadb3fb5403fd8528823a277968f728da7e07e' + 'cc793c63156b5dab5d396f22d78695f275aed171b17509f4fb944cdf3df6eb94' + '4db474c1805cbb07ba862f7aecd6217eacdf69a21afe74608dad93f3fd30d7bd' + '98ad82c271930e44aab77334a05d255858fef9f9d7bc103a6b96bcdfb626fd8a') export KBUILD_BUILD_HOST=archlinux