From b42ff37372a7ee04647e17a4fd98246ef5c64eed Mon Sep 17 00:00:00 2001 From: Maximilian Luz Date: Fri, 16 Oct 2020 19:33:14 +0200 Subject: [PATCH] Update v4.19 patches Changes: - SAM: - Merge keyboard and HID driver into single module - Restructure DTX user-space interface - Add DTX support for Surface Book 3 - Continued cleanup and misc. fixes - other: - Add patch to fix boot problems on the Surface Go series Links: - SAM: https://github.com/linux-surface/surface-aggregator-module/commit/bfef4ac6f8f83bbf48ee23f364f875d96b057e88 - kernel: https://github.com/linux-surface/kernel/commit/d1e77c5126eaef4ad66616dd29ec7859c6ca1105 --- configs/surface-4.19.config | 1 - patches/4.19/0001-surface3-power.patch | 8 +- .../0002-surface3-touchscreen-dma-fix.patch | 4 +- patches/4.19/0003-surface3-oemb.patch | 4 +- patches/4.19/0004-surface-buttons.patch | 4 +- patches/4.19/0005-suspend.patch | 8 +- patches/4.19/0006-ipts.patch | 4 +- patches/4.19/0007-wifi.patch | 4 +- patches/4.19/0008-surface-sam.patch | 2521 ++++++++++------- patches/4.19/0009-surface-sam-over-hid.patch | 8 +- patches/4.19/0010-surface-gpe.patch | 8 +- ...-acpi_walk_dep_device_list-getting-c.patch | 70 + ...-acpi_walk_dep_device_list-getting-c.patch | 1 + pkg/arch/kernel-lts/PKGBUILD | 24 +- 14 files changed, 1558 insertions(+), 1111 deletions(-) create mode 100644 patches/4.19/0011-i2c-core-Restore-acpi_walk_dep_device_list-getting-c.patch create mode 120000 pkg/arch/kernel-lts/0011-i2c-core-Restore-acpi_walk_dep_device_list-getting-c.patch diff --git a/configs/surface-4.19.config b/configs/surface-4.19.config index c340189ed..5a07ea74a 100644 --- a/configs/surface-4.19.config +++ b/configs/surface-4.19.config @@ -18,7 +18,6 @@ CONFIG_SURFACE_BATTERY=m CONFIG_SURFACE_DTX=m CONFIG_SURFACE_HID=m CONFIG_SURFACE_HOTPLUG=m -CONFIG_SURFACE_KEYBOARD=m CONFIG_SURFACE_PERFMODE=m # diff --git a/patches/4.19/0001-surface3-power.patch b/patches/4.19/0001-surface3-power.patch index b652c7eb1..dc7b8c3fb 100644 --- a/patches/4.19/0001-surface3-power.patch +++ b/patches/4.19/0001-surface3-power.patch @@ -1,7 +1,7 @@ -From a475caaa58e185535481400f70ef0bd0c012b13b Mon Sep 17 00:00:00 2001 +From 68915ae484c9d8881ed344ee3c639b1d11d8f29c Mon Sep 17 00:00:00 2001 From: Maximilian Luz Date: Sat, 28 Sep 2019 18:00:43 +0200 -Subject: [PATCH 01/10] surface3-power +Subject: [PATCH 01/11] surface3-power --- drivers/platform/x86/Kconfig | 7 + @@ -11,10 +11,10 @@ Subject: [PATCH 01/10] surface3-power create mode 100644 drivers/platform/x86/surface3_power.c diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig -index 1e2524de6a63..2ad19dc64a4a 100644 +index a13bb4ddd0cf..0d20ffdb5a67 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig -@@ -1160,6 +1160,13 @@ config SURFACE_3_BUTTON +@@ -1161,6 +1161,13 @@ config SURFACE_3_BUTTON ---help--- This driver handles the power/home/volume buttons on the Microsoft Surface 3 tablet. diff --git a/patches/4.19/0002-surface3-touchscreen-dma-fix.patch b/patches/4.19/0002-surface3-touchscreen-dma-fix.patch index b1bc1bf2f..d701cd5f5 100644 --- a/patches/4.19/0002-surface3-touchscreen-dma-fix.patch +++ b/patches/4.19/0002-surface3-touchscreen-dma-fix.patch @@ -1,7 +1,7 @@ -From 55e76b758aa3046850e630d6a4f7faaa16c5322c Mon Sep 17 00:00:00 2001 +From 26e5e3d5780b48ef1f2a7115ef7a1ef1f4bf1e67 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Sun, 5 Jul 2020 14:56:20 +0300 -Subject: [PATCH 02/10] surface3-touchscreen-dma-fix +Subject: [PATCH 02/11] surface3-touchscreen-dma-fix --- drivers/dma/dw/core.c | 12 ------------ diff --git a/patches/4.19/0003-surface3-oemb.patch b/patches/4.19/0003-surface3-oemb.patch index dfa4cedc4..3038639c1 100644 --- a/patches/4.19/0003-surface3-oemb.patch +++ b/patches/4.19/0003-surface3-oemb.patch @@ -1,7 +1,7 @@ -From 985bdbb89a9a3cc3ff14db61c0cd2d966831d8dc Mon Sep 17 00:00:00 2001 +From 118ae75f050cf4934c6b97d11f088492c2a628aa Mon Sep 17 00:00:00 2001 From: Chih-Wei Huang Date: Tue, 18 Sep 2018 11:01:37 +0800 -Subject: [PATCH 03/10] surface3-oemb +Subject: [PATCH 03/11] surface3-oemb --- drivers/platform/x86/surface3-wmi.c | 7 +++++++ diff --git a/patches/4.19/0004-surface-buttons.patch b/patches/4.19/0004-surface-buttons.patch index 4888f112b..ad967b6fa 100644 --- a/patches/4.19/0004-surface-buttons.patch +++ b/patches/4.19/0004-surface-buttons.patch @@ -1,7 +1,7 @@ -From 8b92bff78f88634721917f3c203dbdaa131edf4d Mon Sep 17 00:00:00 2001 +From b9404424ac5fb0521a36308323493abd8a1d0df7 Mon Sep 17 00:00:00 2001 From: Maximilian Luz Date: Sat, 27 Jul 2019 17:51:37 +0200 -Subject: [PATCH 04/10] surface-buttons +Subject: [PATCH 04/11] surface-buttons --- drivers/input/misc/Kconfig | 6 +- diff --git a/patches/4.19/0005-suspend.patch b/patches/4.19/0005-suspend.patch index 3a7e71463..710875f1d 100644 --- a/patches/4.19/0005-suspend.patch +++ b/patches/4.19/0005-suspend.patch @@ -1,7 +1,7 @@ -From 9db7f91549a7e2c160ec01e3589f5f7c0d6416a0 Mon Sep 17 00:00:00 2001 +From fb6b7e1a29e622cf19c57c17f397661ec9dae1da Mon Sep 17 00:00:00 2001 From: kitakar5525 <34676735+kitakar5525@users.noreply.github.com> Date: Sat, 28 Sep 2019 17:48:21 +0200 -Subject: [PATCH 05/10] suspend +Subject: [PATCH 05/11] suspend --- drivers/nvme/host/core.c | 36 ++++++++++++-- @@ -12,7 +12,7 @@ Subject: [PATCH 05/10] suspend 5 files changed, 162 insertions(+), 6 deletions(-) diff --git a/drivers/nvme/host/core.c b/drivers/nvme/host/core.c -index 9ea3d8e61100..1bc63549707a 100644 +index b633ea40430e..4993f04a8a58 100644 --- a/drivers/nvme/host/core.c +++ b/drivers/nvme/host/core.c @@ -1065,15 +1065,15 @@ static struct nvme_id_ns *nvme_identify_ns(struct nvme_ctrl *ctrl, @@ -59,7 +59,7 @@ index 9ea3d8e61100..1bc63549707a 100644 int nvme_set_queue_count(struct nvme_ctrl *ctrl, int *count) { u32 q_count = (*count - 1) | ((*count - 1) << 16); -@@ -3630,6 +3648,18 @@ static void nvme_free_ctrl(struct device *dev) +@@ -3632,6 +3650,18 @@ static void nvme_free_ctrl(struct device *dev) nvme_put_subsystem(subsys); } diff --git a/patches/4.19/0006-ipts.patch b/patches/4.19/0006-ipts.patch index c64e58d4f..b15d4a172 100644 --- a/patches/4.19/0006-ipts.patch +++ b/patches/4.19/0006-ipts.patch @@ -1,7 +1,7 @@ -From b9f55c5b557f1d88a2cc2570bec9f80aa439d5d1 Mon Sep 17 00:00:00 2001 +From f4a9111f3d630f2ad9a1746caa0d5c0bc319cb13 Mon Sep 17 00:00:00 2001 From: Maximilian Luz Date: Sat, 28 Sep 2019 17:58:17 +0200 -Subject: [PATCH 06/10] ipts +Subject: [PATCH 06/11] ipts --- drivers/gpu/drm/i915/Makefile | 3 + diff --git a/patches/4.19/0007-wifi.patch b/patches/4.19/0007-wifi.patch index a408d9876..440f0d204 100644 --- a/patches/4.19/0007-wifi.patch +++ b/patches/4.19/0007-wifi.patch @@ -1,7 +1,7 @@ -From 429bae577341648e2a157dc21ae9eef4edb93db8 Mon Sep 17 00:00:00 2001 +From d172c88f96b9e9187ea7d00231b7c124040526ae 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 07/10] wifi +Subject: [PATCH 07/11] wifi --- drivers/net/wireless/marvell/mwifiex/Makefile | 1 + diff --git a/patches/4.19/0008-surface-sam.patch b/patches/4.19/0008-surface-sam.patch index cbfa71992..fad471c3c 100644 --- a/patches/4.19/0008-surface-sam.patch +++ b/patches/4.19/0008-surface-sam.patch @@ -1,7 +1,7 @@ -From 9c2effd0f5723177f56ba516162463615ebf4d2d Mon Sep 17 00:00:00 2001 +From 3cf914699985f3179eb643ca2763a85fc854a1ad Mon Sep 17 00:00:00 2001 From: qzed Date: Mon, 26 Aug 2019 01:15:40 +0200 -Subject: [PATCH 08/10] surface-sam +Subject: [PATCH 08/11] surface-sam --- Documentation/driver-api/index.rst | 1 + @@ -23,18 +23,17 @@ Subject: [PATCH 08/10] surface-sam drivers/misc/surface_aggregator/Makefile | 18 + drivers/misc/surface_aggregator/bus.c | 424 +++ drivers/misc/surface_aggregator/bus.h | 27 + - .../misc/surface_aggregator/clients/Kconfig | 155 + - .../misc/surface_aggregator/clients/Makefile | 12 + + .../misc/surface_aggregator/clients/Kconfig | 151 + + .../misc/surface_aggregator/clients/Makefile | 11 + .../clients/surface_acpi_notify.c | 884 ++++++ - .../clients/surface_aggregator_cdev.c | 228 ++ - .../clients/surface_aggregator_registry.c | 602 ++++ + .../clients/surface_aggregator_cdev.c | 299 ++ + .../clients/surface_aggregator_registry.c | 605 ++++ .../clients/surface_battery.c | 1192 ++++++++ - .../surface_aggregator/clients/surface_dtx.c | 591 ++++ - .../surface_aggregator/clients/surface_hid.c | 548 ++++ + .../surface_aggregator/clients/surface_dtx.c | 1129 ++++++++ + .../surface_aggregator/clients/surface_hid.c | 919 ++++++ .../clients/surface_hotplug.c | 1285 +++++++++ - .../clients/surface_keyboard.c | 597 ++++ .../clients/surface_perfmode.c | 122 + - drivers/misc/surface_aggregator/controller.c | 2552 +++++++++++++++++ + drivers/misc/surface_aggregator/controller.c | 2553 +++++++++++++++++ drivers/misc/surface_aggregator/controller.h | 288 ++ drivers/misc/surface_aggregator/core.c | 842 ++++++ drivers/misc/surface_aggregator/ssh_msgb.h | 201 ++ @@ -48,13 +47,13 @@ Subject: [PATCH 08/10] surface-sam drivers/tty/serdev/core.c | 111 +- include/linux/mod_devicetable.h | 18 + include/linux/surface_acpi_notify.h | 39 + - include/linux/surface_aggregator/controller.h | 814 ++++++ + include/linux/surface_aggregator/controller.h | 815 ++++++ include/linux/surface_aggregator/device.h | 430 +++ include/linux/surface_aggregator/serial_hub.h | 655 +++++ include/uapi/linux/surface_aggregator/cdev.h | 58 + scripts/mod/devicetable-offsets.c | 8 + scripts/mod/file2alias.c | 23 + - 50 files changed, 18523 insertions(+), 29 deletions(-) + 49 files changed, 18906 insertions(+), 29 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/cdev.rst @@ -78,7 +77,6 @@ Subject: [PATCH 08/10] surface-sam 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 @@ -1955,10 +1953,10 @@ index 000000000000..7712baaed6a5 +#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..c18403c13df5 +index 000000000000..3c438cb3f0ca --- /dev/null +++ b/drivers/misc/surface_aggregator/clients/Kconfig -@@ -0,0 +1,155 @@ +@@ -0,0 +1,151 @@ +# SPDX-License-Identifier: GPL-2.0+ +# Copyright (C) 2019-2020 Maximilian Luz + @@ -2053,6 +2051,10 @@ index 000000000000..c18403c13df5 + behavior of this process, which includes the option to abort it in + case the base is still in use or speed it up in case it is not. + ++ Note that this module can be built without support for the Surface ++ Aggregator Bus (i.e. CONFIG_SURFACE_AGGREGATOR_BUS=n). In that case, ++ some devices, specifically the Surface Book 3, will not be supported. ++ +config SURFACE_HID + tristate "Surface HID Transport Driver" + depends on SURFACE_AGGREGATOR_BUS @@ -2067,6 +2069,11 @@ index 000000000000..c18403c13df5 + Surface Laptop 1 and 2, as well as keyboard and touchpad input on the + Surface Laptop 3 and Surface Book 3. + ++ Note that this module can be built without support for the Surface ++ Aggregator Bus (i.e. CONFIG_SURFACE_AGGREGATOR_BUS=n). In that case, ++ some devices, specifically the Surface Book 3 and Surface Laptop 3, ++ will not be supported. ++ +config SURFACE_HOTPLUG + tristate "Surface Hot-Plug System Driver" + depends on SURFACE_AGGREGATOR @@ -2084,19 +2091,6 @@ index 000000000000..c18403c13df5 + 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 @@ -2116,10 +2110,10 @@ index 000000000000..c18403c13df5 + 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..6d1707bc5705 +index 000000000000..7320922ba755 --- /dev/null +++ b/drivers/misc/surface_aggregator/clients/Makefile -@@ -0,0 +1,12 @@ +@@ -0,0 +1,11 @@ +# SPDX-License-Identifier: GPL-2.0+ +# Copyright (C) 2019-2020 Maximilian Luz + @@ -2130,11 +2124,10 @@ index 000000000000..6d1707bc5705 +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..25b8866ed81f +index 000000000000..9010f3aafd28 --- /dev/null +++ b/drivers/misc/surface_aggregator/clients/surface_acpi_notify.c @@ -0,0 +1,884 @@ @@ -2179,7 +2172,7 @@ index 000000000000..25b8866ed81f + container_of(ptr, struct san_data, member) + + -+/* -- dGPU Notifier Interface. ---------------------------------------------- */ ++/* -- dGPU notifier interface. ---------------------------------------------- */ + +struct san_rqsg_if { + struct rw_semaphore lock; @@ -2544,7 +2537,7 @@ index 000000000000..25b8866ed81f +} + + -+/* -- ACPI GSB OperationRegion Handler -------------------------------------- */ ++/* -- ACPI GSB OperationRegion handler -------------------------------------- */ + +struct gsb_data_in { + u8 cv; @@ -3024,10 +3017,10 @@ index 000000000000..25b8866ed81f +MODULE_LICENSE("GPL"); diff --git a/drivers/misc/surface_aggregator/clients/surface_aggregator_cdev.c b/drivers/misc/surface_aggregator/clients/surface_aggregator_cdev.c new file mode 100644 -index 000000000000..65f75ac7e864 +index 000000000000..7ebd0977fb37 --- /dev/null +++ b/drivers/misc/surface_aggregator/clients/surface_aggregator_cdev.c -@@ -0,0 +1,228 @@ +@@ -0,0 +1,299 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Provides user-space access to the SSAM EC via the /dev/surface/aggregator @@ -3038,9 +3031,11 @@ index 000000000000..65f75ac7e864 + +#include +#include ++#include +#include +#include +#include ++#include +#include +#include + @@ -3050,21 +3045,48 @@ index 000000000000..65f75ac7e864 +#define SSAM_CDEV_DEVICE_NAME "surface_aggregator_cdev" + +struct ssam_cdev { ++ struct kref kref; ++ struct rw_semaphore lock; + struct ssam_controller *ctrl; + struct miscdevice mdev; +}; + ++static void __ssam_cdev_release(struct kref *kref) ++{ ++ kfree(container_of(kref, struct ssam_cdev, kref)); ++} ++ ++static struct ssam_cdev *ssam_cdev_get(struct ssam_cdev *cdev) ++{ ++ if (cdev) ++ kref_get(&cdev->kref); ++ ++ return cdev; ++} ++ ++static void ssam_cdev_put(struct ssam_cdev *cdev) ++{ ++ if (cdev) ++ kref_put(&cdev->kref, __ssam_cdev_release); ++} ++ +static int ssam_cdev_device_open(struct inode *inode, struct file *filp) +{ + struct miscdevice *mdev = filp->private_data; ++ struct ssam_cdev *cdev = container_of(mdev, struct ssam_cdev, mdev); + -+ filp->private_data = container_of(mdev, struct ssam_cdev, mdev); -+ return nonseekable_open(inode, filp); ++ filp->private_data = ssam_cdev_get(cdev); ++ return stream_open(inode, filp); +} + -+static long ssam_cdev_request(struct file *file, unsigned long arg) ++static int ssam_cdev_device_release(struct inode *inode, struct file *filp) ++{ ++ ssam_cdev_put(filp->private_data); ++ return 0; ++} ++ ++static long ssam_cdev_request(struct ssam_cdev *cdev, unsigned long arg) +{ -+ struct ssam_cdev *cdev = file->private_data; + struct ssam_cdev_request __user *r; + struct ssam_cdev_request rqst; + struct ssam_request spec; @@ -3155,21 +3177,43 @@ index 000000000000..65f75ac7e864 + return ret; +} + -+static long ssam_cdev_device_ioctl(struct file *file, unsigned int cmd, -+ unsigned long arg) ++static long __ssam_cdev_device_ioctl(struct ssam_cdev *cdev, unsigned int cmd, ++ unsigned long arg) +{ + switch (cmd) { + case SSAM_CDEV_REQUEST: -+ return ssam_cdev_request(file, arg); ++ return ssam_cdev_request(cdev, arg); + + default: + return -ENOTTY; + } +} + ++static long ssam_cdev_device_ioctl(struct file *file, unsigned int cmd, ++ unsigned long arg) ++{ ++ struct ssam_cdev *cdev = file->private_data; ++ long status; ++ ++ // ensure that controller is valid for as long as we need it ++ if (down_read_killable(&cdev->lock)) ++ return -ERESTARTSYS; ++ ++ if (!cdev->ctrl) { ++ up_read(&cdev->lock); ++ return -ENODEV; ++ } ++ ++ status = __ssam_cdev_device_ioctl(cdev, cmd, arg); ++ ++ up_read(&cdev->lock); ++ return status; ++} ++ +static const struct file_operations ssam_controller_fops = { + .owner = THIS_MODULE, + .open = ssam_cdev_device_open, ++ .release = ssam_cdev_device_release, + .unlocked_ioctl = ssam_cdev_device_ioctl, + .compat_ioctl = ssam_cdev_device_ioctl, + .llseek = noop_llseek, @@ -3185,19 +3229,28 @@ index 000000000000..65f75ac7e864 + if (status) + return status == -ENXIO ? -EPROBE_DEFER : status; + -+ cdev = devm_kzalloc(&pdev->dev, sizeof(*cdev), GFP_KERNEL); ++ cdev = kzalloc(sizeof(*cdev), GFP_KERNEL); + if (!cdev) + return -ENOMEM; + ++ kref_init(&cdev->kref); ++ init_rwsem(&cdev->lock); + cdev->ctrl = ctrl; ++ + cdev->mdev.parent = &pdev->dev; + cdev->mdev.minor = MISC_DYNAMIC_MINOR; + cdev->mdev.name = "surface_aggregator"; + cdev->mdev.nodename = "surface/aggregator"; + cdev->mdev.fops = &ssam_controller_fops; + ++ status = misc_register(&cdev->mdev); ++ if (status) { ++ kfree(cdev); ++ return status; ++ } ++ + platform_set_drvdata(pdev, cdev); -+ return misc_register(&cdev->mdev); ++ return 0; +} + +static int ssam_dbg_device_remove(struct platform_device *pdev) @@ -3205,6 +3258,17 @@ index 000000000000..65f75ac7e864 + struct ssam_cdev *cdev = platform_get_drvdata(pdev); + + misc_deregister(&cdev->mdev); ++ ++ /* ++ * The controller is only guaranteed to be valid for as long as the ++ * driver is bound. Remove controller so that any lingering open files ++ * cannot access it any more after we're gone. ++ */ ++ down_write(&cdev->lock); ++ cdev->ctrl = NULL; ++ up_write(&cdev->lock); ++ ++ ssam_cdev_put(cdev); + return 0; +} + @@ -3258,10 +3322,10 @@ index 000000000000..65f75ac7e864 +MODULE_LICENSE("GPL"); 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..abebc4c446b0 +index 000000000000..c7e84316b88b --- /dev/null +++ b/drivers/misc/surface_aggregator/clients/surface_aggregator_registry.c -@@ -0,0 +1,602 @@ +@@ -0,0 +1,605 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Surface System Aggregator Module (SSAM) client device registry. @@ -3354,6 +3418,8 @@ index 000000000000..abebc4c446b0 + +#define SSAM_DUID_TMP_PERF SSAM_DUID(TMP, 0x01, 0x00, 0x01) + ++#define SSAM_DUID_BAS_DTX SSAM_DUID(BAS, 0x01, 0x00, 0x00) ++ +#define SSAM_DUID_HID_KEYBOARD SSAM_DUID(HID, 0x02, 0x01, 0x00) +#define SSAM_DUID_HID_TOUCHPAD SSAM_DUID(HID, 0x02, 0x03, 0x00) +#define SSAM_DUID_HID_IID5 SSAM_DUID(HID, 0x02, 0x05, 0x00) @@ -3368,6 +3434,7 @@ index 000000000000..abebc4c446b0 + { SSAM_DUID_TMP_PERF }, + { SSAM_DUID_BAT_AC }, + { SSAM_DUID_BAT_MAIN }, ++ { SSAM_DUID_BAS_DTX }, +}; + +static const struct ssam_hub_cell ssam_devices_sb3_base[] = { @@ -3866,7 +3933,7 @@ index 000000000000..abebc4c446b0 +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..b04af850183f +index 000000000000..d43797590cd9 --- /dev/null +++ b/drivers/misc/surface_aggregator/clients/surface_battery.c @@ -0,0 +1,1192 @@ @@ -4030,7 +4097,7 @@ index 000000000000..b04af850183f +}); + + -+/* -- Common Power-Subsystem Interface. ------------------------------------- */ ++/* -- Common power-subsystem interface. ------------------------------------- */ + +struct spwr_psy_properties { + const char *name; @@ -4889,7 +4956,7 @@ index 000000000000..b04af850183f +} + + -+/* -- Power Management. ----------------------------------------------------- */ ++/* -- Power management. ----------------------------------------------------- */ + +#ifdef CONFIG_PM_SLEEP + @@ -4914,7 +4981,7 @@ index 000000000000..b04af850183f +SIMPLE_DEV_PM_OPS(surface_ac_pm_ops, NULL, surface_ac_resume); + + -+/* -- Battery Driver. ------------------------------------------------------- */ ++/* -- Battery driver. ------------------------------------------------------- */ + +static int surface_battery_probe(struct ssam_device *sdev) +{ @@ -4977,7 +5044,7 @@ index 000000000000..b04af850183f +}; + + -+/* -- AC Driver. ------------------------------------------------------------ */ ++/* -- AC driver. ------------------------------------------------------------ */ + +static int surface_ac_probe(struct ssam_device *sdev) +{ @@ -5034,7 +5101,7 @@ index 000000000000..b04af850183f +}; + + -+/* -- Module Setup. --------------------------------------------------------- */ ++/* -- Module setup. --------------------------------------------------------- */ + +static int __init surface_battery_init(void) +{ @@ -5064,10 +5131,10 @@ index 000000000000..b04af850183f +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..d667f605b74d +index 000000000000..3b4cfe639509 --- /dev/null +++ b/drivers/misc/surface_aggregator/clients/surface_dtx.c -@@ -0,0 +1,591 @@ +@@ -0,0 +1,1129 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Surface Book (gen. 2 and later) detachment system (DTX) driver. @@ -5087,87 +5154,150 @@ index 000000000000..d667f605b74d +#include +#include +#include ++#include ++#include +#include +#include +#include +#include -+#include ++#include +#include +#include + +#include ++#include + + -+#define USB_VENDOR_ID_MICROSOFT 0x045e -+#define USB_DEVICE_ID_MS_SURFACE_BASE_2_INTEGRATION 0x0922 ++/* -- Public interface. ----------------------------------------------------- */ + -+// name copied from MS device manager -+#define DTX_INPUT_NAME "Microsoft Surface Base 2 Integration Device" ++/* Status/error categories */ ++#define SDTX_CATEGORY_STATUS 0x0000 ++#define SDTX_CATEGORY_RUNTIME_ERROR 0x1000 ++#define SDTX_CATEGORY_HARDWARE_ERROR 0x2000 ++#define SDTX_CATEGORY_UNKNOWN 0xf000 ++ ++#define SDTX_CATEGORY_MASK 0xf000 ++#define SDTX_CATEGORY(value) ((value) & SDTX_CATEGORY_MASK) ++ ++#define SDTX_STATUS(code) ((code) | SDTX_CATEGORY_STATUS) ++#define SDTX_ERR_RT(code) ((code) | SDTX_CATEGORY_RUNTIME_ERROR) ++#define SDTX_ERR_HW(code) ((code) | SDTX_CATEGORY_HARDWARE_ERROR) ++#define SDTX_UNKNOWN(code) ((code) | SDTX_CATEGORY_UNKNOWN) ++ ++#define SDTX_SUCCESS(value) (SDTX_CATEGORY(value) == SDTX_CATEGORY_STATUS) ++ ++/* Latch status values */ ++#define SDTX_LATCH_CLOSED SDTX_STATUS(0x00) ++#define SDTX_LATCH_OPENED SDTX_STATUS(0x01) ++ ++/* Base state values */ ++#define SDTX_BASE_DETACHED SDTX_STATUS(0x00) ++#define SDTX_BASE_ATTACHED SDTX_STATUS(0x01) ++ ++/* Runtime errors (non-critical) */ ++#define SDTX_DETACH_NOT_FEASIBLE SDTX_ERR_RT(0x01) ++#define SDTX_DETACH_TIMEDOUT SDTX_ERR_RT(0x02) ++ ++/* Hardware errors (critical) */ ++#define SDTX_ERR_FAILED_TO_OPEN SDTX_ERR_HW(0x01) ++#define SDTX_ERR_FAILED_TO_REMAIN_OPEN SDTX_ERR_HW(0x02) ++#define SDTX_ERR_FAILED_TO_CLOSE SDTX_ERR_HW(0x03) + + -+#define DTX_CMD_LATCH_LOCK _IO(0x11, 0x01) -+#define DTX_CMD_LATCH_UNLOCK _IO(0x11, 0x02) -+#define DTX_CMD_LATCH_REQUEST _IO(0x11, 0x03) -+#define DTX_CMD_LATCH_OPEN _IO(0x11, 0x04) -+#define DTX_CMD_GET_OPMODE _IOR(0x11, 0x05, int) ++/* Base types */ ++#define SDTX_DEVICE_TYPE_HID 0x0100 ++#define SDTX_DEVICE_TYPE_SSH 0x0200 + -+#define SAM_EVENT_DTX_CID_CONNECTION 0x0c -+#define SAM_EVENT_DTX_CID_BUTTON 0x0e -+#define SAM_EVENT_DTX_CID_ERROR 0x0f -+#define SAM_EVENT_DTX_CID_LATCH_STATUS 0x11 ++#define SDTX_DEVICE_TYPE_MASK 0x0f00 ++#define SDTX_DEVICE_TYPE(value) ((value) & SDTX_DEVICE_TYPE_MASK) + -+#define DTX_OPMODE_TABLET 0x00 -+#define DTX_OPMODE_LAPTOP 0x01 -+#define DTX_OPMODE_STUDIO 0x02 -+ -+#define DTX_LATCH_CLOSED 0x00 -+#define DTX_LATCH_OPENED 0x01 ++#define SDTX_BASE_TYPE_HID(id) ((id) | SDTX_DEVICE_TYPE_HID) ++#define SDTX_BASE_TYPE_SSH(id) ((id) | SDTX_DEVICE_TYPE_SSH) + + -+// Warning: This must always be a power of 2! -+#define DTX_CLIENT_BUF_SIZE 16 -+ -+#define DTX_CONNECT_OPMODE_DELAY 1000 -+ -+#define DTX_ERR KERN_ERR "surface_dtx: " -+#define DTX_WARN KERN_WARNING "surface_dtx: " ++/* Device mode */ ++enum sdtx_device_mode { ++ SDTX_DEVICE_MODE_TABLET = 0x00, ++ SDTX_DEVICE_MODE_LAPTOP = 0x01, ++ SDTX_DEVICE_MODE_STUDIO = 0x02, ++}; + + -+struct surface_dtx_event { -+ u8 type; -+ u8 code; -+ u8 arg0; -+ u8 arg1; ++/* Event provided by reading from the device */ ++struct sdtx_event { ++ __u16 length; ++ __u16 code; ++ __u8 data[]; +} __packed; + -+struct surface_dtx_dev { -+ struct ssam_controller *ctrl; -+ -+ struct ssam_event_notifier notif; -+ struct delayed_work opmode_work; -+ wait_queue_head_t waitq; -+ struct miscdevice mdev; -+ spinlock_t client_lock; -+ struct list_head client_list; -+ struct mutex mutex; -+ bool active; -+ spinlock_t input_lock; -+ struct input_dev *input_dev; -+}; -+ -+struct surface_dtx_client { -+ struct list_head node; -+ struct surface_dtx_dev *ddev; -+ struct fasync_struct *fasync; -+ spinlock_t buffer_lock; -+ unsigned int buffer_head; -+ unsigned int buffer_tail; -+ struct surface_dtx_event buffer[DTX_CLIENT_BUF_SIZE]; ++enum sdtx_event_code { ++ SDTX_EVENT_REQUEST = 1, ++ SDTX_EVENT_CANCEL = 2, ++ SDTX_EVENT_BASE_CONNECTION = 3, ++ SDTX_EVENT_LATCH_STATUS = 4, ++ SDTX_EVENT_DEVICE_MODE = 5, +}; + + -+static struct surface_dtx_dev surface_dtx_dev; ++/* IOCTL interface */ ++struct sdtx_base_info { ++ __u16 state; ++ __u16 base_id; ++} __packed; + ++#define SDTX_IOCTL_EVENTS_ENABLE _IO(0xa5, 0x21) ++#define SDTX_IOCTL_EVENTS_DISABLE _IO(0xa5, 0x22) ++ ++#define SDTX_IOCTL_LATCH_LOCK _IO(0xa5, 0x23) ++#define SDTX_IOCTL_LATCH_UNLOCK _IO(0xa5, 0x24) ++#define SDTX_IOCTL_LATCH_REQUEST _IO(0xa5, 0x25) ++#define SDTX_IOCTL_LATCH_CONFIRM _IO(0xa5, 0x26) ++#define SDTX_IOCTL_LATCH_HEARTBEAT _IO(0xa5, 0x27) ++#define SDTX_IOCTL_LATCH_CANCEL _IO(0xa5, 0x28) ++ ++#define SDTX_IOCTL_GET_BASE_INFO _IOR(0xa5, 0x29, struct sdtx_base_info) ++#define SDTX_IOCTL_GET_DEVICE_MODE _IOR(0xa5, 0x2a, u16) ++#define SDTX_IOCTL_GET_LATCH_STATUS _IOR(0xa5, 0x2b, u16) ++ ++ ++/* -- SSAM interface. ------------------------------------------------------- */ ++ ++enum sam_event_cid_bas { ++ SAM_EVENT_CID_DTX_CONNECTION = 0x0c, ++ SAM_EVENT_CID_DTX_REQUEST = 0x0e, ++ SAM_EVENT_CID_DTX_CANCEL = 0x0f, ++ SAM_EVENT_CID_DTX_LATCH_STATUS = 0x11, ++}; ++ ++enum dtx_base_state { ++ SDTX_BASE_STATE_DETACH_SUCCESS = 0x00, ++ SDTX_BASE_STATE_ATTACHED = 0x01, ++ SDTX_BASE_STATE_NOT_FEASIBLE = 0x02, ++}; ++ ++enum dtx_latch_status { ++ SDTX_LATCH_STATUS_CLOSED = 0x00, ++ SDTX_LATCH_STATUS_OPENED = 0x01, ++ SDTX_LATCH_STATUS_FAILED_TO_OPEN = 0x02, ++ SDTX_LATCH_STATUS_FAILED_TO_REMAIN_OPEN = 0x03, ++ SDTX_LATCH_STATUS_FAILED_TO_CLOSE = 0x04, ++}; ++ ++enum dtx_cancel_reason { ++ SDTX_CANCEL_REASON_NOT_FEASIBLE = 0x00, // low battery ++ SDTX_CANCEL_REASON_TIMEOUT = 0x02, ++ SDTX_CANCEL_REASON_FAILED_TO_OPEN = 0x03, ++ SDTX_CANCEL_REASON_FAILED_TO_REMAIN_OPEN = 0x04, ++ SDTX_CANCEL_REASON_FAILED_TO_CLOSE = 0x05, ++}; ++ ++ ++struct ssam_dtx_base_info { ++ u8 state; ++ u8 base_id; ++} __packed; ++ ++static_assert(sizeof(struct ssam_dtx_base_info) == 2); + +static SSAM_DEFINE_SYNC_REQUEST_N(ssam_bas_latch_lock, { + .target_category = SSAM_SSH_TC_BAS, @@ -5190,202 +5320,449 @@ index 000000000000..d667f605b74d + .instance_id = 0x00, +}); + -+static SSAM_DEFINE_SYNC_REQUEST_N(ssam_bas_latch_open, { ++static SSAM_DEFINE_SYNC_REQUEST_N(ssam_bas_latch_confirm, { + .target_category = SSAM_SSH_TC_BAS, + .target_id = 0x01, + .command_id = 0x09, + .instance_id = 0x00, +}); + -+static SSAM_DEFINE_SYNC_REQUEST_R(ssam_bas_query_opmode, u8, { ++static SSAM_DEFINE_SYNC_REQUEST_N(ssam_bas_latch_heartbeat, { ++ .target_category = SSAM_SSH_TC_BAS, ++ .target_id = 0x01, ++ .command_id = 0x0a, ++ .instance_id = 0x00, ++}); ++ ++static SSAM_DEFINE_SYNC_REQUEST_N(ssam_bas_latch_cancel, { ++ .target_category = SSAM_SSH_TC_BAS, ++ .target_id = 0x01, ++ .command_id = 0x0b, ++ .instance_id = 0x00, ++}); ++ ++static SSAM_DEFINE_SYNC_REQUEST_R(ssam_bas_get_base, struct ssam_dtx_base_info, { ++ .target_category = SSAM_SSH_TC_BAS, ++ .target_id = 0x01, ++ .command_id = 0x0c, ++ .instance_id = 0x00, ++}); ++ ++static SSAM_DEFINE_SYNC_REQUEST_R(ssam_bas_get_device_mode, u8, { + .target_category = SSAM_SSH_TC_BAS, + .target_id = 0x01, + .command_id = 0x0d, + .instance_id = 0x00, +}); + ++static SSAM_DEFINE_SYNC_REQUEST_R(ssam_bas_get_latch_status, u8, { ++ .target_category = SSAM_SSH_TC_BAS, ++ .target_id = 0x01, ++ .command_id = 0x11, ++ .instance_id = 0x00, ++}); + -+static int dtx_bas_get_opmode(struct ssam_controller *ctrl, int __user *buf) ++ ++/* -- Main structures. ------------------------------------------------------ */ ++ ++enum sdtx_device_state { ++ SDTX_DEVICE_SHUTDOWN = BIT(0), ++}; ++ ++struct sdtx_device { ++ struct kref kref; ++ struct rw_semaphore lock; ++ ++ struct device *dev; ++ struct ssam_controller *ctrl; ++ unsigned long state; ++ ++ struct miscdevice mdev; ++ wait_queue_head_t waitq; ++ struct rw_semaphore client_lock; ++ struct list_head client_list; ++ ++ struct delayed_work mode_work; ++ struct input_dev *mode_switch; ++ ++ struct ssam_event_notifier notif; ++ ++ struct mutex mutex; ++ bool active; ++}; ++ ++enum sdtx_client_state { ++ SDTX_CLIENT_EVENTS_ENABLED = BIT(0), ++}; ++ ++struct sdtx_client { ++ struct sdtx_device *ddev; ++ struct list_head node; ++ unsigned long state; ++ ++ struct fasync_struct *fasync; ++ ++ struct mutex read_lock; ++ spinlock_t write_lock; ++ DECLARE_KFIFO(buffer, u8, 512); ++}; ++ ++static void __sdtx_device_release(struct kref *kref) +{ -+ u8 opmode; ++ kfree(container_of(kref, struct sdtx_device, kref)); ++} ++ ++static struct sdtx_device *sdtx_device_get(struct sdtx_device *ddev) ++{ ++ if (ddev) ++ kref_get(&ddev->kref); ++ ++ return ddev; ++} ++ ++static void sdtx_device_put(struct sdtx_device *ddev) ++{ ++ if (ddev) ++ kref_put(&ddev->kref, __sdtx_device_release); ++} ++ ++ ++/* -- Firmware value translations. ------------------------------------------ */ ++ ++static u16 sdtx_translate_base_state(struct sdtx_device *ddev, u8 state) ++{ ++ switch (state) { ++ case SDTX_BASE_STATE_ATTACHED: ++ return SDTX_BASE_ATTACHED; ++ ++ case SDTX_BASE_STATE_DETACH_SUCCESS: ++ return SDTX_BASE_DETACHED; ++ ++ case SDTX_BASE_STATE_NOT_FEASIBLE: ++ return SDTX_DETACH_NOT_FEASIBLE; ++ ++ default: ++ dev_err(ddev->dev, "unknown base state: 0x%02x\n", state); ++ return SDTX_UNKNOWN(state); ++ } ++} ++ ++static u16 sdtx_translate_latch_status(struct sdtx_device *ddev, u8 status) ++{ ++ switch (status) { ++ case SDTX_LATCH_STATUS_CLOSED: ++ return SDTX_LATCH_CLOSED; ++ ++ case SDTX_LATCH_STATUS_OPENED: ++ return SDTX_LATCH_OPENED; ++ ++ case SDTX_LATCH_STATUS_FAILED_TO_OPEN: ++ return SDTX_ERR_FAILED_TO_OPEN; ++ ++ case SDTX_LATCH_STATUS_FAILED_TO_REMAIN_OPEN: ++ return SDTX_ERR_FAILED_TO_REMAIN_OPEN; ++ ++ case SDTX_LATCH_STATUS_FAILED_TO_CLOSE: ++ return SDTX_ERR_FAILED_TO_CLOSE; ++ ++ default: ++ dev_err(ddev->dev, "unknown latch status: 0x%02x\n", status); ++ return SDTX_UNKNOWN(status); ++ } ++} ++ ++static u16 sdtx_translate_cancel_reason(struct sdtx_device *ddev, u8 reason) ++{ ++ switch (reason) { ++ case SDTX_CANCEL_REASON_NOT_FEASIBLE: ++ return SDTX_DETACH_NOT_FEASIBLE; ++ ++ case SDTX_CANCEL_REASON_TIMEOUT: ++ return SDTX_DETACH_TIMEDOUT; ++ ++ case SDTX_CANCEL_REASON_FAILED_TO_OPEN: ++ return SDTX_ERR_FAILED_TO_OPEN; ++ ++ case SDTX_CANCEL_REASON_FAILED_TO_REMAIN_OPEN: ++ return SDTX_ERR_FAILED_TO_REMAIN_OPEN; ++ ++ case SDTX_CANCEL_REASON_FAILED_TO_CLOSE: ++ return SDTX_ERR_FAILED_TO_CLOSE; ++ ++ default: ++ dev_err(ddev->dev, "unknown cancel reason: 0x%02x\n", reason); ++ return SDTX_UNKNOWN(reason); ++ } ++} ++ ++ ++/* -- IOCTLs. --------------------------------------------------------------- */ ++ ++static int sdtx_ioctl_get_base_info(struct sdtx_device *ddev, ++ struct sdtx_base_info __user *buf) ++{ ++ struct ssam_dtx_base_info raw; ++ struct sdtx_base_info info; + int status; + -+ status = ssam_bas_query_opmode(ctrl, &opmode); ++ status = ssam_bas_get_base(ddev->ctrl, &raw); + if (status < 0) + return status; + -+ if (put_user(opmode, buf)) -+ return -EACCES; ++ info.state = sdtx_translate_base_state(ddev, raw.state); ++ info.base_id = SDTX_BASE_TYPE_SSH(raw.base_id); ++ ++ if (copy_to_user(buf, &info, sizeof(info))) ++ return -EFAULT; + + return 0; +} + ++static int sdtx_ioctl_get_device_mode(struct sdtx_device *ddev, u16 __user *buf) ++{ ++ u8 mode; ++ int status; ++ ++ status = ssam_bas_get_device_mode(ddev->ctrl, &mode); ++ if (status < 0) ++ return status; ++ ++ return put_user(mode, buf); ++} ++ ++static int sdtx_ioctl_get_latch_status(struct sdtx_device *ddev, u16 __user *buf) ++{ ++ u8 latch; ++ int status; ++ ++ status = ssam_bas_get_latch_status(ddev->ctrl, &latch); ++ if (status < 0) ++ return status; ++ ++ return put_user(sdtx_translate_latch_status(ddev, latch), buf); ++} ++ ++static long __surface_dtx_ioctl(struct sdtx_client *client, unsigned int cmd, ++ unsigned long arg) ++{ ++ struct sdtx_device *ddev = client->ddev; ++ ++ switch (cmd) { ++ case SDTX_IOCTL_EVENTS_ENABLE: ++ set_bit(SDTX_CLIENT_EVENTS_ENABLED, &client->state); ++ return 0; ++ ++ case SDTX_IOCTL_EVENTS_DISABLE: ++ clear_bit(SDTX_CLIENT_EVENTS_ENABLED, &client->state); ++ return 0; ++ ++ case SDTX_IOCTL_LATCH_LOCK: ++ return ssam_bas_latch_lock(ddev->ctrl); ++ ++ case SDTX_IOCTL_LATCH_UNLOCK: ++ return ssam_bas_latch_unlock(ddev->ctrl); ++ ++ case SDTX_IOCTL_LATCH_REQUEST: ++ return ssam_bas_latch_request(ddev->ctrl); ++ ++ case SDTX_IOCTL_LATCH_CONFIRM: ++ return ssam_bas_latch_confirm(ddev->ctrl); ++ ++ case SDTX_IOCTL_LATCH_HEARTBEAT: ++ return ssam_bas_latch_heartbeat(ddev->ctrl); ++ ++ case SDTX_IOCTL_LATCH_CANCEL: ++ return ssam_bas_latch_cancel(ddev->ctrl); ++ ++ case SDTX_IOCTL_GET_BASE_INFO: ++ return sdtx_ioctl_get_base_info(ddev, ++ (struct sdtx_base_info __user *)arg); ++ ++ case SDTX_IOCTL_GET_DEVICE_MODE: ++ return sdtx_ioctl_get_device_mode(ddev, (u16 __user *)arg); ++ ++ case SDTX_IOCTL_GET_LATCH_STATUS: ++ return sdtx_ioctl_get_latch_status(ddev, (u16 __user *)arg); ++ ++ default: ++ return -EINVAL; ++ } ++} ++ ++static long surface_dtx_ioctl(struct file *file, unsigned int cmd, ++ unsigned long arg) ++{ ++ struct sdtx_client *client = file->private_data; ++ long status; ++ ++ if (down_read_killable(&client->ddev->lock)) ++ return -ERESTARTSYS; ++ ++ if (test_bit(SDTX_DEVICE_SHUTDOWN, &client->ddev->state)) { ++ up_read(&client->ddev->lock); ++ return -ENODEV; ++ } ++ ++ status = __surface_dtx_ioctl(client, cmd, arg); ++ ++ up_read(&client->ddev->lock); ++ return status; ++} ++ ++ ++/* -- File operations. ------------------------------------------------------ */ + +static int surface_dtx_open(struct inode *inode, struct file *file) +{ -+ struct surface_dtx_dev *ddev = container_of(file->private_data, struct surface_dtx_dev, mdev); -+ struct surface_dtx_client *client; ++ struct sdtx_device *ddev; ++ struct sdtx_client *client; ++ ++ ddev = container_of(file->private_data, struct sdtx_device, mdev); + + // initialize client + client = kzalloc(sizeof(*client), GFP_KERNEL); + if (!client) + return -ENOMEM; + -+ spin_lock_init(&client->buffer_lock); -+ client->buffer_head = 0; -+ client->buffer_tail = 0; -+ client->ddev = ddev; ++ client->ddev = sdtx_device_get(ddev); + -+ // attach client -+ spin_lock(&ddev->client_lock); -+ list_add_tail_rcu(&client->node, &ddev->client_list); -+ spin_unlock(&ddev->client_lock); ++ INIT_LIST_HEAD(&client->node); ++ ++ mutex_init(&client->read_lock); ++ spin_lock_init(&client->write_lock); ++ INIT_KFIFO(client->buffer); + + file->private_data = client; -+ nonseekable_open(inode, file); + ++ // attach client ++ down_write(&ddev->client_lock); ++ ++ // do not add a new client if the device has been shut down ++ if (test_bit(SDTX_DEVICE_SHUTDOWN, &ddev->state)) { ++ up_write(&ddev->client_lock); ++ sdtx_device_put(client->ddev); ++ kfree(client); ++ return -ENODEV; ++ } ++ ++ list_add_tail(&client->node, &ddev->client_list); ++ up_write(&ddev->client_lock); ++ ++ stream_open(inode, file); + return 0; +} + +static int surface_dtx_release(struct inode *inode, struct file *file) +{ -+ struct surface_dtx_client *client = file->private_data; ++ struct sdtx_client *client = file->private_data; + + // detach client -+ spin_lock(&client->ddev->client_lock); -+ list_del_rcu(&client->node); -+ spin_unlock(&client->ddev->client_lock); -+ synchronize_rcu(); ++ down_write(&client->ddev->client_lock); ++ list_del(&client->node); ++ up_write(&client->ddev->client_lock); + ++ sdtx_device_put(client->ddev); + kfree(client); -+ file->private_data = NULL; + + return 0; +} + -+static ssize_t surface_dtx_read(struct file *file, char __user *buf, size_t count, loff_t *offs) ++static ssize_t surface_dtx_read(struct file *file, char __user *buf, ++ size_t count, loff_t *offs) +{ -+ struct surface_dtx_client *client = file->private_data; -+ struct surface_dtx_dev *ddev = client->ddev; -+ struct surface_dtx_event event; -+ size_t read = 0; ++ struct sdtx_client *client = file->private_data; ++ struct sdtx_device *ddev = client->ddev; ++ unsigned int copied; + int status = 0; + -+ if (count != 0 && count < sizeof(struct surface_dtx_event)) -+ return -EINVAL; ++ if (down_read_killable(&ddev->lock)) ++ return -ERESTARTSYS; + -+ if (!ddev->active) ++ // make sure we're not shut down ++ if (test_bit(SDTX_DEVICE_SHUTDOWN, &ddev->state)) { ++ up_read(&ddev->lock); + return -ENODEV; -+ -+ // check availability -+ if (client->buffer_head == client->buffer_tail) { -+ if (file->f_flags & O_NONBLOCK) -+ return -EAGAIN; -+ -+ status = wait_event_interruptible(ddev->waitq, -+ client->buffer_head != client->buffer_tail || -+ !ddev->active); -+ if (status) -+ return status; -+ -+ if (!ddev->active) -+ return -ENODEV; + } + -+ // copy events one by one -+ while (read + sizeof(struct surface_dtx_event) <= count) { -+ spin_lock_irq(&client->buffer_lock); ++ do { ++ // check availability, wait if necessary ++ if (kfifo_is_empty(&client->buffer)) { ++ up_read(&ddev->lock); + -+ if (client->buffer_head == client->buffer_tail) { -+ spin_unlock_irq(&client->buffer_lock); -+ break; ++ if (file->f_flags & O_NONBLOCK) ++ return -EAGAIN; ++ ++ status = wait_event_interruptible(ddev->waitq, ++ !kfifo_is_empty(&client->buffer) ++ || test_bit(SDTX_DEVICE_SHUTDOWN, ++ &ddev->state)); ++ if (status < 0) ++ return status; ++ ++ if (down_read_killable(&client->ddev->lock)) ++ return -ERESTARTSYS; ++ ++ // need to check that we're not shut down again ++ if (test_bit(SDTX_DEVICE_SHUTDOWN, &ddev->state)) { ++ up_read(&ddev->lock); ++ return -ENODEV; ++ } + } + -+ // get one event -+ event = client->buffer[client->buffer_tail]; -+ client->buffer_tail = (client->buffer_tail + 1) & (DTX_CLIENT_BUF_SIZE - 1); -+ spin_unlock_irq(&client->buffer_lock); ++ // try to read from fifo ++ if (mutex_lock_interruptible(&client->read_lock)) { ++ up_read(&ddev->lock); ++ return -ERESTARTSYS; ++ } + -+ // copy to userspace -+ if (copy_to_user(buf, &event, sizeof(struct surface_dtx_event))) -+ return -EFAULT; ++ status = kfifo_to_user(&client->buffer, buf, count, &copied); ++ mutex_unlock(&client->read_lock); + -+ read += sizeof(struct surface_dtx_event); -+ } ++ if (status < 0) { ++ up_read(&ddev->lock); ++ return status; ++ } + -+ return read; ++ // we might not have gotten anything, check this here ++ if (copied == 0 && (file->f_flags & O_NONBLOCK)) { ++ up_read(&ddev->lock); ++ return -EAGAIN; ++ } ++ } while (copied == 0); ++ ++ up_read(&ddev->lock); ++ return copied; +} + +static __poll_t surface_dtx_poll(struct file *file, struct poll_table_struct *pt) +{ -+ struct surface_dtx_client *client = file->private_data; -+ int mask; ++ struct sdtx_client *client = file->private_data; ++ __poll_t events = 0; ++ ++ if (down_read_killable(&client->ddev->lock)) ++ return -ERESTARTSYS; ++ ++ if (test_bit(SDTX_DEVICE_SHUTDOWN, &client->ddev->state)) { ++ up_read(&client->ddev->lock); ++ return EPOLLHUP | EPOLLERR; ++ } + + poll_wait(file, &client->ddev->waitq, pt); + -+ if (client->ddev->active) -+ mask = EPOLLOUT | EPOLLWRNORM; -+ else -+ mask = EPOLLHUP | EPOLLERR; ++ if (!kfifo_is_empty(&client->buffer)) ++ events |= EPOLLIN | EPOLLRDNORM; + -+ if (client->buffer_head != client->buffer_tail) -+ mask |= EPOLLIN | EPOLLRDNORM; -+ -+ return mask; ++ up_read(&client->ddev->lock); ++ return events; +} + +static int surface_dtx_fasync(int fd, struct file *file, int on) +{ -+ struct surface_dtx_client *client = file->private_data; ++ struct sdtx_client *client = file->private_data; + + return fasync_helper(fd, file, on, &client->fasync); +} + -+static long surface_dtx_ioctl(struct file *file, unsigned int cmd, unsigned long arg) -+{ -+ struct surface_dtx_client *client = file->private_data; -+ struct surface_dtx_dev *ddev = client->ddev; -+ int status; -+ -+ status = mutex_lock_interruptible(&ddev->mutex); -+ if (status) -+ return status; -+ -+ if (!ddev->active) { -+ mutex_unlock(&ddev->mutex); -+ return -ENODEV; -+ } -+ -+ switch (cmd) { -+ case DTX_CMD_LATCH_LOCK: -+ status = ssam_bas_latch_lock(ddev->ctrl); -+ break; -+ -+ case DTX_CMD_LATCH_UNLOCK: -+ status = ssam_bas_latch_unlock(ddev->ctrl); -+ break; -+ -+ case DTX_CMD_LATCH_REQUEST: -+ status = ssam_bas_latch_request(ddev->ctrl); -+ break; -+ -+ case DTX_CMD_LATCH_OPEN: -+ status = ssam_bas_latch_open(ddev->ctrl); -+ break; -+ -+ case DTX_CMD_GET_OPMODE: -+ status = dtx_bas_get_opmode(ddev->ctrl, (int __user *)arg); -+ break; -+ -+ default: -+ status = -EINVAL; -+ break; -+ } -+ -+ mutex_unlock(&ddev->mutex); -+ return status; -+} -+ +static const struct file_operations surface_dtx_fops = { + .owner = THIS_MODULE, + .open = surface_dtx_open, @@ -5394,277 +5771,505 @@ index 000000000000..d667f605b74d + .poll = surface_dtx_poll, + .fasync = surface_dtx_fasync, + .unlocked_ioctl = surface_dtx_ioctl, ++ .compat_ioctl = surface_dtx_ioctl, + .llseek = no_llseek, +}; + -+static struct surface_dtx_dev surface_dtx_dev = { -+ .mdev = { -+ .minor = MISC_DYNAMIC_MINOR, -+ .name = "surface_dtx", -+ .fops = &surface_dtx_fops, -+ }, -+ .client_lock = __SPIN_LOCK_UNLOCKED(), -+ .input_lock = __SPIN_LOCK_UNLOCKED(), -+ .mutex = __MUTEX_INITIALIZER(surface_dtx_dev.mutex), -+ .active = false, ++ ++/* -- Event handling/forwarding. -------------------------------------------- */ ++ ++/* ++ * The device operation mode is not immediately updated on the EC when the ++ * base has been connected, i.e. querying the device mode inside the ++ * connection event callback yields an outdated value. Thus, we can only ++ * determine the new tablet-mode switch and device mode values after some ++ * time. ++ * ++ * These delays have been chosen by experimenting. We first delay on connect ++ * events, then check and validate the device mode against the base state and ++ * if invalid delay again by the "recheck" delay. ++ */ ++#define SDTX_DEVICE_MODE_DELAY_CONNECT msecs_to_jiffies(100) ++#define SDTX_DEVICE_MODE_DELAY_RECHECK msecs_to_jiffies(100) ++ ++static void sdtx_device_mode_update(struct sdtx_device *ddev, unsigned long delay); ++ ++ ++struct sdtx_status_event { ++ struct sdtx_event e; ++ u16 v; ++} __packed; ++ ++struct sdtx_base_info_event { ++ struct sdtx_event e; ++ struct sdtx_base_info v; ++} __packed; ++ ++union sdtx_generic_event { ++ struct sdtx_event common; ++ struct sdtx_status_event status; ++ struct sdtx_base_info_event base; +}; + -+ -+static void surface_dtx_push_event(struct surface_dtx_dev *ddev, struct surface_dtx_event *event) ++static void sdtx_push_event(struct sdtx_device *ddev, struct sdtx_event *evt) +{ -+ struct surface_dtx_client *client; ++ const size_t len = sizeof(struct sdtx_event) + evt->length; ++ struct sdtx_client *client; + -+ rcu_read_lock(); -+ list_for_each_entry_rcu(client, &ddev->client_list, node) { -+ spin_lock(&client->buffer_lock); ++ down_read(&ddev->client_lock); ++ list_for_each_entry(client, &ddev->client_list, node) { ++ if (!test_bit(SDTX_CLIENT_EVENTS_ENABLED, &client->state)) ++ continue; + -+ client->buffer[client->buffer_head++] = *event; -+ client->buffer_head &= DTX_CLIENT_BUF_SIZE - 1; ++ spin_lock(&client->write_lock); + -+ if (unlikely(client->buffer_head == client->buffer_tail)) { -+ printk(DTX_WARN "event buffer overrun\n"); -+ client->buffer_tail = (client->buffer_tail + 1) & (DTX_CLIENT_BUF_SIZE - 1); ++ if (likely(kfifo_avail(&client->buffer) >= len)) { ++ kfifo_in(&client->buffer, (const u8 *)evt, len); ++ spin_unlock(&client->write_lock); ++ } else { ++ spin_unlock(&client->write_lock); ++ dev_warn(ddev->dev, "event buffer overrun\n"); + } + -+ spin_unlock(&client->buffer_lock); -+ + kill_fasync(&client->fasync, SIGIO, POLL_IN); + } -+ rcu_read_unlock(); ++ up_read(&ddev->client_lock); + + wake_up_interruptible(&ddev->waitq); +} + -+ -+static void surface_dtx_update_opmpde(struct surface_dtx_dev *ddev) ++static u32 sdtx_notifier(struct ssam_event_notifier *nf, ++ const struct ssam_event *in) +{ -+ struct surface_dtx_event event; -+ u8 opmode; -+ int status; ++ struct sdtx_device *ddev = container_of(nf, struct sdtx_device, notif); ++ union sdtx_generic_event event; ++ size_t len; + -+ // get operation mode -+ status = ssam_bas_query_opmode(ddev->ctrl, &opmode); -+ if (status < 0) { -+ printk(DTX_ERR "EC request failed with error %d\n", status); -+ return; -+ } ++ // validate event payload length ++ switch (in->command_id) { ++ case SAM_EVENT_CID_DTX_CONNECTION: ++ len = 2; ++ break; + -+ // send DTX event -+ event.type = 0x11; -+ event.code = 0x0D; -+ event.arg0 = opmode; -+ event.arg1 = 0x00; ++ case SAM_EVENT_CID_DTX_REQUEST: ++ len = 0; ++ break; + -+ surface_dtx_push_event(ddev, &event); ++ case SAM_EVENT_CID_DTX_CANCEL: ++ len = 1; ++ break; + -+ // send SW_TABLET_MODE event -+ spin_lock(&ddev->input_lock); -+ input_report_switch(ddev->input_dev, SW_TABLET_MODE, opmode != DTX_OPMODE_LAPTOP); -+ input_sync(ddev->input_dev); -+ spin_unlock(&ddev->input_lock); -+} -+ -+static void surface_dtx_opmode_workfn(struct work_struct *work) -+{ -+ struct surface_dtx_dev *ddev = container_of(work, struct surface_dtx_dev, opmode_work.work); -+ -+ surface_dtx_update_opmpde(ddev); -+} -+ -+static u32 surface_dtx_notification(struct ssam_event_notifier *nf, const struct ssam_event *in_event) -+{ -+ struct surface_dtx_dev *ddev = container_of(nf, struct surface_dtx_dev, notif); -+ struct surface_dtx_event event; -+ unsigned long delay; -+ -+ switch (in_event->command_id) { -+ case SAM_EVENT_DTX_CID_CONNECTION: -+ case SAM_EVENT_DTX_CID_BUTTON: -+ case SAM_EVENT_DTX_CID_ERROR: -+ case SAM_EVENT_DTX_CID_LATCH_STATUS: -+ if (in_event->length > 2) { -+ printk(DTX_ERR "unexpected payload size (cid: %x, len: %u)\n", -+ in_event->command_id, in_event->length); -+ return SSAM_NOTIF_HANDLED; -+ } -+ -+ event.type = in_event->target_category; -+ event.code = in_event->command_id; -+ event.arg0 = in_event->length >= 1 ? in_event->data[0] : 0x00; -+ event.arg1 = in_event->length >= 2 ? in_event->data[1] : 0x00; -+ surface_dtx_push_event(ddev, &event); ++ case SAM_EVENT_CID_DTX_LATCH_STATUS: ++ len = 1; + break; + + default: + return 0; ++ }; ++ ++ if (in->length != len) { ++ dev_err(ddev->dev, "unexpected payload size for event 0x%02x: " ++ "got %u, expected %zu", in->command_id, in->length, len); ++ return 0; + } + -+ // update device mode -+ if (in_event->command_id == SAM_EVENT_DTX_CID_CONNECTION) { -+ delay = event.arg0 ? DTX_CONNECT_OPMODE_DELAY : 0; -+ schedule_delayed_work(&ddev->opmode_work, delay); ++ // translate event ++ switch (in->command_id) { ++ case SAM_EVENT_CID_DTX_CONNECTION: ++ event.base.e.code = SDTX_EVENT_BASE_CONNECTION; ++ event.base.e.length = sizeof(struct sdtx_base_info); ++ event.base.v.state = sdtx_translate_base_state(ddev, in->data[0]); ++ event.base.v.base_id = SDTX_BASE_TYPE_SSH(in->data[1]); ++ break; ++ ++ case SAM_EVENT_CID_DTX_REQUEST: ++ event.common.code = SDTX_EVENT_REQUEST; ++ event.common.length = 0; ++ break; ++ ++ case SAM_EVENT_CID_DTX_CANCEL: ++ event.status.e.code = SDTX_EVENT_CANCEL; ++ event.status.e.length = sizeof(u16); ++ event.status.v = sdtx_translate_cancel_reason(ddev, in->data[0]); ++ break; ++ ++ case SAM_EVENT_CID_DTX_LATCH_STATUS: ++ event.status.e.code = SDTX_EVENT_LATCH_STATUS; ++ event.status.e.length = sizeof(u16); ++ event.status.v = sdtx_translate_latch_status(ddev, in->data[0]); ++ break; ++ } ++ ++ sdtx_push_event(ddev, &event.common); ++ ++ // update device mode on base connection change ++ if (in->command_id == SAM_EVENT_CID_DTX_CONNECTION) { ++ unsigned long delay; ++ ++ delay = in->data[0] ? SDTX_DEVICE_MODE_DELAY_CONNECT : 0; ++ sdtx_device_mode_update(ddev, delay); + } + + return SSAM_NOTIF_HANDLED; +} + + -+static struct input_dev *surface_dtx_register_inputdev( -+ struct platform_device *pdev, struct ssam_controller *ctrl) ++/* -- Tablet-mode switch. --------------------------------------------------- */ ++ ++static void sdtx_device_mode_update(struct sdtx_device *ddev, unsigned long delay) +{ -+ struct input_dev *input_dev; -+ u8 opmode; -+ int status; ++ schedule_delayed_work(&ddev->mode_work, delay); ++} + -+ input_dev = input_allocate_device(); -+ if (!input_dev) -+ return ERR_PTR(-ENOMEM); ++static void sdtx_device_mode_workfn(struct work_struct *work) ++{ ++ struct sdtx_device *ddev; ++ struct sdtx_status_event event; ++ struct ssam_dtx_base_info base; ++ int status, tablet; ++ bool invalid; ++ u8 mode; + -+ input_dev->name = DTX_INPUT_NAME; -+ input_dev->dev.parent = &pdev->dev; -+ input_dev->id.bustype = BUS_VIRTUAL; -+ input_dev->id.vendor = USB_VENDOR_ID_MICROSOFT; -+ input_dev->id.product = USB_DEVICE_ID_MS_SURFACE_BASE_2_INTEGRATION; ++ ddev = container_of(work, struct sdtx_device, mode_work.work); + -+ input_set_capability(input_dev, EV_SW, SW_TABLET_MODE); -+ -+ status = ssam_bas_query_opmode(ctrl, &opmode); -+ if (status < 0) { -+ input_free_device(input_dev); -+ return ERR_PTR(status); -+ } -+ -+ input_report_switch(input_dev, SW_TABLET_MODE, opmode != DTX_OPMODE_LAPTOP); -+ -+ status = input_register_device(input_dev); ++ // get operation mode ++ status = ssam_bas_get_device_mode(ddev->ctrl, &mode); + if (status) { -+ input_unregister_device(input_dev); -+ return ERR_PTR(status); ++ dev_err(ddev->dev, "failed to get device mode: %d\n", status); ++ return; + } + -+ return input_dev; ++ // get base info ++ status = ssam_bas_get_base(ddev->ctrl, &base); ++ if (status) { ++ dev_err(ddev->dev, "failed to get base info: %d\n", status); ++ return; ++ } ++ ++ /* ++ * In some cases (specifically when attaching the base), the device ++ * mode isn't updated right away. Thus we check if the device mode ++ * makes sense for the given base state and try again later if it ++ * doesn't. ++ */ ++ invalid = ((base.state == SDTX_BASE_STATE_ATTACHED) ++ && (mode == SDTX_DEVICE_MODE_TABLET)) ++ || ((base.state == SDTX_BASE_STATE_DETACH_SUCCESS) ++ && (mode != SDTX_DEVICE_MODE_TABLET)); ++ ++ if (invalid) { ++ dev_dbg(ddev->dev, "device mode is invalid, trying again\n"); ++ sdtx_device_mode_update(ddev, SDTX_DEVICE_MODE_DELAY_RECHECK); ++ return; ++ } ++ ++ event.e.code = SDTX_EVENT_DEVICE_MODE; ++ event.e.length = sizeof(u16); ++ event.v = mode; ++ ++ sdtx_push_event(ddev, &event.e); ++ ++ // send SW_TABLET_MODE event ++ tablet = mode != SDTX_DEVICE_MODE_LAPTOP; ++ input_report_switch(ddev->mode_switch, SW_TABLET_MODE, tablet); ++ input_sync(ddev->mode_switch); +} + + -+static int surface_sam_dtx_probe(struct platform_device *pdev) ++/* -- Common device initialization. ----------------------------------------- */ ++ ++static int sdtx_device_init(struct sdtx_device *ddev, struct device *dev, ++ struct ssam_controller *ctrl) +{ -+ struct surface_dtx_dev *ddev = &surface_dtx_dev; -+ struct ssam_controller *ctrl; -+ struct input_dev *input_dev; + int status; ++ u8 mode; + -+ // link to ec -+ status = ssam_client_bind(&pdev->dev, &ctrl); -+ if (status) -+ return status == -ENXIO ? -EPROBE_DEFER : status; -+ -+ input_dev = surface_dtx_register_inputdev(pdev, ctrl); -+ if (IS_ERR(input_dev)) -+ return PTR_ERR(input_dev); -+ -+ // initialize device -+ mutex_lock(&ddev->mutex); -+ if (ddev->active) { -+ mutex_unlock(&ddev->mutex); -+ status = -ENODEV; -+ goto err_register; -+ } -+ ++ // basic initialization ++ kref_init(&ddev->kref); ++ ddev->dev = dev; + ddev->ctrl = ctrl; -+ INIT_DELAYED_WORK(&ddev->opmode_work, surface_dtx_opmode_workfn); -+ INIT_LIST_HEAD(&ddev->client_list); -+ init_waitqueue_head(&ddev->waitq); -+ ddev->active = true; -+ ddev->input_dev = input_dev; -+ mutex_unlock(&ddev->mutex); + -+ status = misc_register(&ddev->mdev); -+ if (status) -+ goto err_register; ++ ddev->mdev.minor = MISC_DYNAMIC_MINOR; ++ ddev->mdev.name = "surface_dtx"; ++ ddev->mdev.nodename = "surface/dtx"; ++ ddev->mdev.fops = &surface_dtx_fops; + -+ // set up events + ddev->notif.base.priority = 1; -+ ddev->notif.base.fn = surface_dtx_notification; ++ ddev->notif.base.fn = sdtx_notifier; + ddev->notif.event.reg = SSAM_EVENT_REGISTRY_SAM; + ddev->notif.event.id.target_category = SSAM_SSH_TC_BAS; + ddev->notif.event.id.instance = 0; + ddev->notif.event.mask = SSAM_EVENT_MASK_NONE; + ddev->notif.event.flags = SSAM_EVENT_SEQUENCED; + -+ status = ssam_notifier_register(ctrl, &ddev->notif); ++ init_waitqueue_head(&ddev->waitq); ++ init_rwsem(&ddev->client_lock); ++ INIT_LIST_HEAD(&ddev->client_list); ++ ++ INIT_DELAYED_WORK(&ddev->mode_work, sdtx_device_mode_workfn); ++ ++ // get current device mode ++ status = ssam_bas_get_device_mode(ddev->ctrl, &mode); + if (status) -+ goto err_events_setup; ++ return status; ++ ++ mode = (mode != SDTX_DEVICE_MODE_LAPTOP); ++ ++ // set up tablet mode switch ++ ddev->mode_switch = input_allocate_device(); ++ if (!ddev->mode_switch) ++ return -ENOMEM; ++ ++ ddev->mode_switch->name = "Microsoft Surface DTX Device Mode Switch"; ++ ddev->mode_switch->phys = "ssam/01:11:01:00:00/input0"; ++ ddev->mode_switch->id.bustype = BUS_HOST; ++ ddev->mode_switch->dev.parent = ddev->dev; ++ ++ input_set_capability(ddev->mode_switch, EV_SW, SW_TABLET_MODE); ++ input_report_switch(ddev->mode_switch, SW_TABLET_MODE, mode); ++ ++ status = input_register_device(ddev->mode_switch); ++ if (status) { ++ input_free_device(ddev->mode_switch); ++ return status; ++ } ++ ++ // set up event notifier ++ status = ssam_notifier_register(ddev->ctrl, &ddev->notif); ++ if (status) ++ goto err_notif; ++ ++ // register miscdevice ++ status = misc_register(&ddev->mdev); ++ if (status) ++ goto err_mdev; ++ ++ /* ++ * Update device mode in case it has changed between getting the ++ * initial mode and registring the event notifier. Note that this is ++ * safe with regards to concurrent connection change events as the ++ * update work will actually check for consistency with base info. ++ */ ++ sdtx_device_mode_update(ddev, 0); + + return 0; + -+err_events_setup: -+ misc_deregister(&ddev->mdev); -+err_register: -+ input_unregister_device(ddev->input_dev); ++err_notif: ++ ssam_notifier_unregister(ddev->ctrl, &ddev->notif); ++ cancel_delayed_work_sync(&ddev->mode_work); ++err_mdev: ++ input_unregister_device(ddev->mode_switch); + return status; +} + -+static int surface_sam_dtx_remove(struct platform_device *pdev) ++static struct sdtx_device *sdtx_device_setup(struct device *dev, ++ struct ssam_controller *ctrl) +{ -+ struct surface_dtx_dev *ddev = &surface_dtx_dev; -+ struct surface_dtx_client *client; ++ struct sdtx_device *ddev; ++ int status; + -+ mutex_lock(&ddev->mutex); -+ if (!ddev->active) { -+ mutex_unlock(&ddev->mutex); -+ return 0; ++ ddev = kzalloc(sizeof(*ddev), GFP_KERNEL); ++ if (!ddev) ++ return ERR_PTR(-ENOMEM); ++ ++ status = sdtx_device_init(ddev, dev, ctrl); ++ if (status) { ++ kfree(ddev); ++ return ERR_PTR(status); + } + -+ // mark as inactive -+ ddev->active = false; -+ mutex_unlock(&ddev->mutex); ++ return ddev; ++} + -+ // After this call we're guaranteed that no more input events will arive ++static void sdtx_device_destroy(struct sdtx_device *ddev) ++{ ++ struct sdtx_client *client; ++ ++ // disable notifiers, prevent new events from arriving + ssam_notifier_unregister(ddev->ctrl, &ddev->notif); + -+ // wake up clients -+ spin_lock(&ddev->client_lock); ++ // stop mode_work, prevent access to mode_switch ++ cancel_delayed_work_sync(&ddev->mode_work); ++ ++ // with mode_work canceled, we can unregister the mode_switch ++ input_unregister_device(ddev->mode_switch); ++ ++ /* ++ * Mark device as shut-down. Prevent new clients from being added and ++ * new operations from being executed. ++ */ ++ set_bit(SDTX_DEVICE_SHUTDOWN, &ddev->state); ++ ++ // wake up async clients ++ down_write(&ddev->client_lock); + list_for_each_entry(client, &ddev->client_list, node) { + kill_fasync(&client->fasync, SIGIO, POLL_HUP); + } -+ spin_unlock(&ddev->client_lock); ++ up_write(&ddev->client_lock); + ++ // wake up blocking clients + wake_up_interruptible(&ddev->waitq); + -+ // unregister user-space devices -+ input_unregister_device(ddev->input_dev); ++ /* ++ * Wait for clients to finish their current operation. After this, the ++ * controller and device references are guaranteed to be no longer in ++ * use. ++ */ ++ down_write(&ddev->lock); ++ ddev->dev = NULL; ++ ddev->ctrl = NULL; ++ up_write(&ddev->lock); ++ ++ // finally remove the misc-device + misc_deregister(&ddev->mdev); + -+ return 0; ++ /* ++ * We're now guaranteed that sdtx_device_open() won't be called any ++ * more, so we can now drop out reference. ++ */ ++ sdtx_device_put(ddev); +} + + -+static const struct acpi_device_id surface_sam_dtx_match[] = { ++/* -- Platform driver. ------------------------------------------------------ */ ++ ++static int surface_dtx_platform_probe(struct platform_device *pdev) ++{ ++ struct ssam_controller *ctrl; ++ struct sdtx_device *ddev; ++ int status; ++ ++ // link to EC ++ status = ssam_client_bind(&pdev->dev, &ctrl); ++ if (status) ++ return status == -ENXIO ? -EPROBE_DEFER : status; ++ ++ ddev = sdtx_device_setup(&pdev->dev, ctrl); ++ if (IS_ERR(ddev)) ++ return PTR_ERR(ddev); ++ ++ platform_set_drvdata(pdev, ddev); ++ return 0; ++} ++ ++static int surface_dtx_platform_remove(struct platform_device *pdev) ++{ ++ sdtx_device_destroy(platform_get_drvdata(pdev)); ++ return 0; ++} ++ ++static const struct acpi_device_id surface_dtx_acpi_match[] = { + { "MSHW0133", 0 }, + { }, +}; -+MODULE_DEVICE_TABLE(acpi, surface_sam_dtx_match); ++MODULE_DEVICE_TABLE(acpi, surface_dtx_acpi_match); + -+static struct platform_driver surface_sam_dtx = { -+ .probe = surface_sam_dtx_probe, -+ .remove = surface_sam_dtx_remove, ++static struct platform_driver surface_dtx_platform_driver = { ++ .probe = surface_dtx_platform_probe, ++ .remove = surface_dtx_platform_remove, + .driver = { -+ .name = "surface_dtx", -+ .acpi_match_table = surface_sam_dtx_match, ++ .name = "surface_dtx_pltf", ++ .acpi_match_table = surface_dtx_acpi_match, + .probe_type = PROBE_PREFER_ASYNCHRONOUS, + }, +}; -+module_platform_driver(surface_sam_dtx); ++ ++ ++/* -- SSAM device driver. --------------------------------------------------- */ ++ ++#ifdef CONFIG_SURFACE_AGGREGATOR_BUS ++ ++static int surface_dtx_ssam_probe(struct ssam_device *sdev) ++{ ++ struct sdtx_device *ddev; ++ ++ ddev = sdtx_device_setup(&sdev->dev, sdev->ctrl); ++ if (IS_ERR(ddev)) ++ return PTR_ERR(ddev); ++ ++ ssam_device_set_drvdata(sdev, ddev); ++ return 0; ++} ++ ++static void surface_dtx_ssam_remove(struct ssam_device *sdev) ++{ ++ sdtx_device_destroy(ssam_device_get_drvdata(sdev)); ++} ++ ++static const struct ssam_device_id surface_dtx_ssam_match[] = { ++ { SSAM_SDEV(BAS, 0x01, 0x00, 0x00) }, ++ { }, ++}; ++MODULE_DEVICE_TABLE(ssam, surface_dtx_ssam_match); ++ ++static struct ssam_device_driver surface_dtx_ssam_driver = { ++ .probe = surface_dtx_ssam_probe, ++ .remove = surface_dtx_ssam_remove, ++ .match_table = surface_dtx_ssam_match, ++ .driver = { ++ .name = "surface_dtx", ++ .probe_type = PROBE_PREFER_ASYNCHRONOUS, ++ }, ++}; ++ ++static int ssam_dtx_driver_register(void) ++{ ++ return ssam_device_driver_register(&surface_dtx_ssam_driver); ++} ++ ++static void ssam_dtx_driver_unregister(void) ++{ ++ ssam_device_driver_unregister(&surface_dtx_ssam_driver); ++} ++ ++#else /* CONFIG_SURFACE_AGGREGATOR_BUS */ ++ ++static int ssam_dtx_driver_register(void) ++{ ++ return 0; ++} ++ ++static void ssam_dtx_driver_unregister(void) ++{ ++} ++ ++#endif /* CONFIG_SURFACE_AGGREGATOR_BUS */ ++ ++ ++/* -- Module setup. --------------------------------------------------------- */ ++ ++static int __init surface_dtx_init(void) ++{ ++ int status; ++ ++ status = ssam_dtx_driver_register(); ++ if (status) ++ return status; ++ ++ status = platform_driver_register(&surface_dtx_platform_driver); ++ if (status) ++ ssam_dtx_driver_unregister(); ++ ++ return status; ++} ++module_init(surface_dtx_init); ++ ++static void __exit surface_dtx_exit(void) ++{ ++ platform_driver_unregister(&surface_dtx_platform_driver); ++ ssam_dtx_driver_unregister(); ++} ++module_exit(surface_dtx_exit); + +MODULE_AUTHOR("Maximilian Luz "); +MODULE_DESCRIPTION("Detachment-system driver for Surface System Aggregator Module"); +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..b219da91f606 +index 000000000000..1cfdb5ee616d --- /dev/null +++ b/drivers/misc/surface_aggregator/clients/surface_hid.c -@@ -0,0 +1,548 @@ +@@ -0,0 +1,919 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Surface System Aggregator Module (SSAM) HID device driver. @@ -5677,10 +6282,12 @@ index 000000000000..b219da91f606 + */ + +#include ++#include +#include +#include +#include +#include ++#include +#include +#include + @@ -5716,6 +6323,38 @@ index 000000000000..b219da91f606 + __u8 _unknown[22]; +} __packed; + ++struct surface_hid_device; ++ ++struct surface_hid_device_ops { ++ int (*get_descriptor)(struct surface_hid_device *shid, u8 entry, ++ u8 *buf, size_t len); ++ int (*output_report)(struct surface_hid_device *shid, u8 report_id, ++ u8 *data, size_t len); ++ int (*get_feature_report)(struct surface_hid_device *shid, u8 report_id, ++ u8 *data, size_t len); ++ int (*set_feature_report)(struct surface_hid_device *shid, u8 report_id, ++ u8 *data, size_t len); ++}; ++ ++struct surface_hid_device { ++ struct device *dev; ++ struct ssam_controller *ctrl; ++ struct ssam_device_uid uid; ++ ++ struct surface_hid_descriptor hid_desc; ++ struct surface_hid_attributes attrs; ++ ++ struct ssam_event_notifier notif; ++ struct hid_device *hid; ++ ++ struct surface_hid_device_ops ops; ++}; ++ ++ ++/* -- SAM interface (HID). -------------------------------------------------- */ ++ ++#ifdef CONFIG_SURFACE_AGGREGATOR_BUS ++ +struct surface_hid_buffer_slice { + __u8 entry; + __le32 offset; @@ -5731,21 +6370,6 @@ index 000000000000..b219da91f606 + 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; -+}; -+ -+ -+/* -- SAM interface. -------------------------------------------------------- */ -+ +static int ssam_hid_get_descriptor(struct surface_hid_device *shid, u8 entry, + u8 *buf, size_t len) +{ @@ -5788,7 +6412,8 @@ index 000000000000..b219da91f606 + + rsp.length = 0; + -+ status = shid_retry(ssam_request_sync, shid->ctrl, &rqst, &rsp); ++ status = shid_retry(ssam_request_sync_onstack, shid->ctrl, ++ &rqst, &rsp, sizeof(*slice)); + if (status) + return status; + @@ -5853,14 +6478,15 @@ index 000000000000..b219da91f606 + rqst.instance_id = shid->uid.instance; + rqst.command_id = SURFACE_HID_CID_GET_FEATURE_REPORT; + rqst.flags = 0; -+ rqst.length = sizeof(u8); ++ rqst.length = sizeof(report_id); + rqst.payload = &report_id; + + rsp.capacity = len; + rsp.length = 0; + rsp.pointer = buf; + -+ return shid_retry(ssam_request_sync, shid->ctrl, &rqst, &rsp); ++ return shid_retry(ssam_request_sync_onstack, shid->ctrl, &rqst, &rsp, ++ sizeof(report_id)); +} + +static u32 ssam_hid_event_fn(struct ssam_event_notifier *nf, @@ -5881,13 +6507,269 @@ index 000000000000..b219da91f606 +} + + ++/* -- Transport driver (HID). ----------------------------------------------- */ ++ ++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; ++} ++ ++#endif /* CONFIG_SURFACE_AGGREGATOR_BUS */ ++ ++ ++/* -- SAM interface (KBD). -------------------------------------------------- */ ++ ++#define KBD_FEATURE_REPORT_SIZE 7 // 6 + report ID ++ ++enum surface_kbd_cid { ++ SURFACE_KBD_CID_GET_DESCRIPTOR = 0x00, ++ SURFACE_KBD_CID_SET_CAPSLOCK_LED = 0x01, ++ SURFACE_KBD_CID_EVT_INPUT_GENERIC = 0x03, ++ SURFACE_KBD_CID_EVT_INPUT_HOTKEYS = 0x04, ++ SURFACE_KBD_CID_GET_FEATURE_REPORT = 0x0b, ++}; ++ ++static int ssam_kbd_get_descriptor(struct surface_hid_device *shid, u8 entry, ++ u8 *buf, size_t len) ++{ ++ struct ssam_request rqst; ++ struct ssam_response rsp; ++ int status; ++ ++ rqst.target_category = shid->uid.category; ++ rqst.target_id = shid->uid.target; ++ rqst.command_id = SURFACE_KBD_CID_GET_DESCRIPTOR; ++ rqst.instance_id = shid->uid.instance; ++ rqst.flags = SSAM_REQUEST_HAS_RESPONSE; ++ rqst.length = sizeof(entry); ++ rqst.payload = &entry; ++ ++ rsp.capacity = len; ++ rsp.length = 0; ++ rsp.pointer = buf; ++ ++ status = shid_retry(ssam_request_sync_onstack, shid->ctrl, &rqst, &rsp, ++ sizeof(entry)); ++ if (status) ++ return status; ++ ++ if (rsp.length != len) { ++ dev_err(shid->dev, "invalid descriptor length: got %zu, " ++ "expected, %zu\n", rsp.length, len); ++ return -EPROTO; ++ } ++ ++ return 0; ++} ++ ++static int ssam_kbd_set_caps_led(struct surface_hid_device *shid, bool value) ++{ ++ struct ssam_request rqst; ++ u8 value_u8 = value; ++ ++ rqst.target_category = shid->uid.category; ++ rqst.target_id = shid->uid.target; ++ rqst.command_id = SURFACE_KBD_CID_SET_CAPSLOCK_LED; ++ rqst.instance_id = shid->uid.instance; ++ rqst.flags = 0; ++ rqst.length = sizeof(value_u8); ++ rqst.payload = &value_u8; ++ ++ return shid_retry(ssam_request_sync_onstack, shid->ctrl, &rqst, NULL, ++ sizeof(value_u8)); ++} ++ ++static int ssam_kbd_get_feature_report(struct surface_hid_device *shid, u8 *buf, ++ size_t len) ++{ ++ struct ssam_request rqst; ++ struct ssam_response rsp; ++ u8 payload = 0; ++ int status; ++ ++ rqst.target_category = shid->uid.category; ++ rqst.target_id = shid->uid.target; ++ rqst.command_id = SURFACE_KBD_CID_GET_FEATURE_REPORT; ++ rqst.instance_id = shid->uid.instance; ++ rqst.flags = SSAM_REQUEST_HAS_RESPONSE; ++ rqst.length = sizeof(payload); ++ rqst.payload = &payload; ++ ++ rsp.capacity = len; ++ rsp.length = 0; ++ rsp.pointer = buf; ++ ++ status = shid_retry(ssam_request_sync_onstack, shid->ctrl, &rqst, &rsp, ++ sizeof(payload)); ++ if (status) ++ return status; ++ ++ if (rsp.length != len) { ++ dev_err(shid->dev, "invalid feature report length: got %zu, " ++ "expected, %zu\n", rsp.length, len); ++ return -EPROTO; ++ } ++ ++ return 0; ++} ++ ++static bool ssam_kbd_is_input_event(const struct ssam_event *event) ++{ ++ if (event->command_id == SURFACE_KBD_CID_EVT_INPUT_GENERIC) ++ return true; ++ ++ if (event->command_id == SURFACE_KBD_CID_EVT_INPUT_HOTKEYS) ++ return true; ++ ++ return false; ++} ++ ++static u32 ssam_kbd_event_fn(struct ssam_event_notifier *nf, ++ const struct ssam_event *event) ++{ ++ struct surface_hid_device *shid; ++ 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; ++} ++ ++ ++/* -- Transport driver (KBD). ----------------------------------------------- */ ++ ++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 skbd_set_feature_report(struct surface_hid_device *shid, ++ u8 report_id, u8 *data, size_t len) ++{ ++ return -EIO; ++} ++ ++ +/* -- 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, ++ status = shid->ops.get_descriptor(shid, SURFACE_HID_DESC_HID, + (u8 *)&shid->hid_desc, sizeof(shid->hid_desc)); + if (status) + return status; @@ -5926,7 +6808,7 @@ index 000000000000..b219da91f606 +{ + int status; + -+ status = ssam_hid_get_descriptor(shid, SURFACE_HID_DESC_ATTRS, ++ status = shid->ops.get_descriptor(shid, SURFACE_HID_DESC_ATTRS, + (u8 *)&shid->attrs, sizeof(shid->attrs)); + if (status) + return status; @@ -5942,35 +6824,7 @@ index 000000000000..b219da91f606 +} + + -+/* -- 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; -+} -+ ++/* -- Transport driver (common). -------------------------------------------- */ + +static int surface_hid_start(struct hid_device *hid) +{ @@ -6007,7 +6861,7 @@ index 000000000000..b219da91f606 + if (!buf) + return -ENOMEM; + -+ status = ssam_hid_get_descriptor(shid, SURFACE_HID_DESC_REPORT, buf, len); ++ status = shid->ops.get_descriptor(shid, SURFACE_HID_DESC_REPORT, buf, len); + if (!status) + status = hid_parse_report(hid, buf, len); + @@ -6022,13 +6876,13 @@ index 000000000000..b219da91f606 + 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); ++ return shid->ops.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); ++ return shid->ops.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 shid->ops.set_feature_report(shid, reportnum, buf, len); + + return -EIO; +} @@ -6062,7 +6916,7 @@ index 000000000000..b219da91f606 + return PTR_ERR(shid->hid); + + shid->hid->dev.parent = shid->dev; -+ shid->hid->bus = BUS_VIRTUAL; // TODO: BUS_SURFACE ++ shid->hid->bus = BUS_HOST; // 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); @@ -6160,7 +7014,9 @@ index 000000000000..b219da91f606 +#endif /* CONFIG_PM */ + + -+/* -- Driver setup. --------------------------------------------------------- */ ++/* -- Driver setup (HID). --------------------------------------------------- */ ++ ++#ifdef CONFIG_SURFACE_AGGREGATOR_BUS + +static int surface_hid_probe(struct ssam_device *sdev) +{ @@ -6182,6 +7038,11 @@ index 000000000000..b219da91f606 + shid->notif.event.mask = SSAM_EVENT_MASK_STRICT; + shid->notif.event.flags = 0; + ++ shid->ops.get_descriptor = ssam_hid_get_descriptor; ++ shid->ops.output_report = shid_output_report; ++ shid->ops.get_feature_report = shid_get_feature_report; ++ shid->ops.set_feature_report = shid_set_feature_report; ++ + ssam_device_set_drvdata(sdev, shid); + return surface_hid_device_add(shid); +} @@ -6207,7 +7068,122 @@ index 000000000000..b219da91f606 + .probe_type = PROBE_PREFER_ASYNCHRONOUS, + }, +}; -+module_ssam_device_driver(surface_hid_driver); ++ ++static int surface_hid_driver_register(void) ++{ ++ return ssam_device_driver_register(&surface_hid_driver); ++} ++ ++static void surface_hid_driver_unregister(void) ++{ ++ ssam_device_driver_unregister(&surface_hid_driver); ++} ++ ++#else /* CONFIG_SURFACE_AGGREGATOR_BUS */ ++ ++static int surface_hid_driver_register(void) ++{ ++ return 0; ++} ++ ++static void surface_hid_driver_unregister(void) ++{ ++} ++ ++#endif /* CONFIG_SURFACE_AGGREGATOR_BUS */ ++ ++ ++/* -- Driver setup (KBD). --------------------------------------------------- */ ++ ++static int surface_kbd_probe(struct platform_device *pdev) ++{ ++ struct ssam_controller *ctrl; ++ struct surface_hid_device *shid; ++ int status; ++ ++ // add device link to EC ++ status = ssam_client_bind(&pdev->dev, &ctrl); ++ if (status) ++ return status == -ENXIO ? -EPROBE_DEFER : status; ++ ++ shid = devm_kzalloc(&pdev->dev, sizeof(*shid), GFP_KERNEL); ++ if (!shid) ++ return -ENOMEM; ++ ++ shid->dev = &pdev->dev; ++ shid->ctrl = ctrl; ++ ++ shid->uid.domain = SSAM_DOMAIN_SERIALHUB; ++ shid->uid.category = SSAM_SSH_TC_KBD; ++ shid->uid.target = 2; ++ shid->uid.instance = 0; ++ shid->uid.function = 0; ++ ++ shid->notif.base.priority = 1; ++ shid->notif.base.fn = ssam_kbd_event_fn; ++ shid->notif.event.reg = SSAM_EVENT_REGISTRY_SAM; ++ shid->notif.event.id.target_category = shid->uid.category; ++ shid->notif.event.id.instance = shid->uid.instance; ++ shid->notif.event.mask = SSAM_EVENT_MASK_NONE; ++ shid->notif.event.flags = 0; ++ ++ shid->ops.get_descriptor = ssam_kbd_get_descriptor; ++ shid->ops.output_report = skbd_output_report; ++ shid->ops.get_feature_report = skbd_get_feature_report; ++ shid->ops.set_feature_report = skbd_set_feature_report; ++ ++ platform_set_drvdata(pdev, shid); ++ return surface_hid_device_add(shid); ++} ++ ++static int surface_kbd_remove(struct platform_device *pdev) ++{ ++ surface_hid_device_destroy(platform_get_drvdata(pdev)); ++ return 0; ++} ++ ++static const struct acpi_device_id surface_kbd_match[] = { ++ { "MSHW0096" }, ++ { }, ++}; ++MODULE_DEVICE_TABLE(acpi, surface_kbd_match); ++ ++static struct platform_driver surface_kbd_driver = { ++ .probe = surface_kbd_probe, ++ .remove = surface_kbd_remove, ++ .driver = { ++ .name = "surface_keyboard", ++ .acpi_match_table = surface_kbd_match, ++ .pm = &surface_hid_pm_ops, ++ .probe_type = PROBE_PREFER_ASYNCHRONOUS, ++ }, ++}; ++ ++ ++/* -- Module setup. --------------------------------------------------------- */ ++ ++static int __init surface_hid_init(void) ++{ ++ int status; ++ ++ status = surface_hid_driver_register(); ++ if (status) ++ return status; ++ ++ status = platform_driver_register(&surface_kbd_driver); ++ if (status) ++ surface_hid_driver_unregister(); ++ ++ return status; ++} ++module_init(surface_hid_init); ++ ++static void __exit surface_hid_exit(void) ++{ ++ platform_driver_unregister(&surface_kbd_driver); ++ surface_hid_driver_unregister(); ++} ++module_exit(surface_hid_exit); + +MODULE_AUTHOR("Blaž Hrastnik "); +MODULE_AUTHOR("Maximilian Luz "); @@ -7504,609 +8480,6 @@ index 000000000000..f18cc17d019d +MODULE_AUTHOR("Maximilian Luz "); +MODULE_DESCRIPTION("DGPU hot-plug system driver for Surface System Aggregator Module"); +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..4d1e21c3742e ---- /dev/null -+++ b/drivers/misc/surface_aggregator/clients/surface_keyboard.c -@@ -0,0 +1,597 @@ -+// SPDX-License-Identifier: GPL-2.0+ -+/* -+ * Surface System Aggregator Module (SSAM) legacy HID input device driver. -+ * -+ * Provides support for the legacy HID keyboard device found on the Surface -+ * Laptop 1 and 2. -+ * -+ * Copyright (C) 2019-2020 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) -+ -+ -+enum surface_hid_descriptor_entry { -+ SURFACE_HID_DESC_HID = 0, -+ SURFACE_HID_DESC_REPORT = 1, -+ SURFACE_HID_DESC_ATTRS = 2, -+}; -+ -+struct surface_hid_descriptor { -+ __u8 desc_len; // = 9 -+ __u8 desc_type; // = HID_DT_HID -+ __le16 hid_version; -+ __u8 country_code; -+ __u8 num_descriptors; // = 1 -+ -+ __u8 report_desc_type; // = HID_DT_REPORT -+ __le16 report_desc_len; -+} __packed; -+ -+struct surface_hid_attributes { -+ __le32 length; -+ __le16 vendor; -+ __le16 product; -+ __le16 version; -+ __u8 _unknown[22]; -+} __packed; -+ -+#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; -+}; -+ -+ -+/* -- SAM interface. -------------------------------------------------------- */ -+ -+static int ssam_kbd_get_descriptor(struct surface_hid_device *shid, u8 entry, -+ u8 *buf, size_t len) -+{ -+ struct ssam_request rqst; -+ struct ssam_response rsp; -+ int status; -+ -+ rqst.target_category = shid->uid.category; -+ rqst.target_id = shid->uid.target; -+ rqst.command_id = SURFACE_KBD_CID_GET_DESCRIPTOR; -+ rqst.instance_id = shid->uid.instance; -+ rqst.flags = SSAM_REQUEST_HAS_RESPONSE; -+ rqst.length = sizeof(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_hid_suspend(struct device *dev) -+{ -+ struct surface_hid_device *d = dev_get_drvdata(dev); -+ -+ if (d->hid->driver && d->hid->driver->suspend) -+ return d->hid->driver->suspend(d->hid, PMSG_SUSPEND); -+ -+ return 0; -+} -+ -+static int surface_hid_resume(struct device *dev) -+{ -+ struct surface_hid_device *d = dev_get_drvdata(dev); -+ -+ if (d->hid->driver && d->hid->driver->resume) -+ return d->hid->driver->resume(d->hid); -+ -+ return 0; -+} -+ -+static int surface_hid_freeze(struct device *dev) -+{ -+ struct surface_hid_device *d = dev_get_drvdata(dev); -+ -+ if (d->hid->driver && d->hid->driver->suspend) -+ return d->hid->driver->suspend(d->hid, PMSG_FREEZE); -+ -+ return 0; -+} -+ -+static int surface_hid_poweroff(struct device *dev) -+{ -+ struct surface_hid_device *d = dev_get_drvdata(dev); -+ -+ if (d->hid->driver && d->hid->driver->suspend) -+ return d->hid->driver->suspend(d->hid, PMSG_HIBERNATE); -+ -+ return 0; -+} -+ -+static int surface_hid_restore(struct device *dev) -+{ -+ struct surface_hid_device *d = dev_get_drvdata(dev); -+ -+ if (d->hid->driver && d->hid->driver->reset_resume) -+ return d->hid->driver->reset_resume(d->hid); -+ -+ return 0; -+} -+ -+const struct dev_pm_ops surface_hid_pm_ops = { -+ .freeze = surface_hid_freeze, -+ .thaw = surface_hid_resume, -+ .suspend = surface_hid_suspend, -+ .resume = surface_hid_resume, -+ .poweroff = surface_hid_poweroff, -+ .restore = surface_hid_restore, -+}; -+ -+#else /* CONFIG_PM */ -+ -+const struct dev_pm_ops surface_hid_pm_ops = { }; -+ -+#endif /* CONFIG_PM */ -+ -+ -+/* -- Driver setup. --------------------------------------------------------- */ -+ -+static int surface_kbd_probe(struct platform_device *pdev) -+{ -+ struct ssam_controller *ctrl; -+ struct surface_hid_device *shid; -+ int status; -+ -+ // add device link to EC -+ status = ssam_client_bind(&pdev->dev, &ctrl); -+ if (status) -+ return status == -ENXIO ? -EPROBE_DEFER : status; -+ -+ shid = devm_kzalloc(&pdev->dev, sizeof(*shid), GFP_KERNEL); -+ if (!shid) -+ return -ENOMEM; -+ -+ shid->dev = &pdev->dev; -+ shid->ctrl = ctrl; -+ -+ shid->uid.domain = SSAM_DOMAIN_SERIALHUB; -+ shid->uid.category = SSAM_SSH_TC_KBD; -+ shid->uid.target = 2; -+ shid->uid.instance = 0; -+ shid->uid.function = 0; -+ -+ shid->notif.base.priority = 1; -+ shid->notif.base.fn = ssam_kbd_event_fn; -+ shid->notif.event.reg = SSAM_EVENT_REGISTRY_SAM; -+ shid->notif.event.id.target_category = shid->uid.category; -+ shid->notif.event.id.instance = shid->uid.instance; -+ shid->notif.event.mask = SSAM_EVENT_MASK_NONE; -+ shid->notif.event.flags = 0; -+ -+ platform_set_drvdata(pdev, shid); -+ return surface_hid_device_add(shid); -+} -+ -+static int surface_kbd_remove(struct platform_device *pdev) -+{ -+ surface_hid_device_destroy(platform_get_drvdata(pdev)); -+ return 0; -+} -+ -+static const struct acpi_device_id surface_kbd_match[] = { -+ { "MSHW0096" }, -+ { }, -+}; -+MODULE_DEVICE_TABLE(acpi, surface_kbd_match); -+ -+static struct platform_driver surface_kbd_driver = { -+ .probe = surface_kbd_probe, -+ .remove = surface_kbd_remove, -+ .driver = { -+ .name = "surface_keyboard", -+ .acpi_match_table = surface_kbd_match, -+ .pm = &surface_hid_pm_ops, -+ .probe_type = PROBE_PREFER_ASYNCHRONOUS, -+ }, -+}; -+module_platform_driver(surface_kbd_driver); -+ -+MODULE_AUTHOR("Maximilian Luz "); -+MODULE_DESCRIPTION("Legacy HID keyboard driver for Surface System Aggregator Module"); -+MODULE_LICENSE("GPL"); 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..006601b3bea6 @@ -8237,10 +8610,10 @@ index 000000000000..006601b3bea6 +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..d20efc9a341a +index 000000000000..27332aed13a5 --- /dev/null +++ b/drivers/misc/surface_aggregator/controller.c -@@ -0,0 +1,2552 @@ +@@ -0,0 +1,2553 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Main SSAM/SSH controller structure and functionality. @@ -9663,7 +10036,7 @@ index 000000000000..d20efc9a341a + */ +ssize_t ssam_request_write_data(struct ssam_span *buf, + struct ssam_controller *ctrl, -+ struct ssam_request *spec) ++ const struct ssam_request *spec) +{ + struct msgbuf msgb; + u16 rqid; @@ -9874,7 +10247,8 @@ index 000000000000..d20efc9a341a + * + * Return: Returns the status of the request or any failure during setup. + */ -+int ssam_request_sync(struct ssam_controller *ctrl, struct ssam_request *spec, ++int ssam_request_sync(struct ssam_controller *ctrl, ++ const struct ssam_request *spec, + struct ssam_response *rsp) +{ + struct ssam_request_sync *rqst; @@ -9928,7 +10302,7 @@ index 000000000000..d20efc9a341a + * Return: Returns the status of the request or any failure during setup. + */ +int ssam_request_sync_with_buffer(struct ssam_controller *ctrl, -+ struct ssam_request *spec, ++ const struct ssam_request *spec, + struct ssam_response *rsp, + struct ssam_span *buf) +{ @@ -17004,10 +17378,10 @@ index 000000000000..8e3e86c7d78c +#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..d91348d54457 +index 000000000000..447cda590409 --- /dev/null +++ b/include/linux/surface_aggregator/controller.h -@@ -0,0 +1,814 @@ +@@ -0,0 +1,815 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Surface System Aggregator Module (SSAM) controller interface. @@ -17130,7 +17504,7 @@ index 000000000000..d91348d54457 + +ssize_t ssam_request_write_data(struct ssam_span *buf, + struct ssam_controller *ctrl, -+ struct ssam_request *spec); ++ const struct ssam_request *spec); + + +/* -- Synchronous request interface. ---------------------------------------- */ @@ -17218,11 +17592,12 @@ index 000000000000..d91348d54457 + return rqst->status; +} + -+int ssam_request_sync(struct ssam_controller *ctrl, struct ssam_request *spec, ++int ssam_request_sync(struct ssam_controller *ctrl, ++ const struct ssam_request *spec, + struct ssam_response *rsp); + +int ssam_request_sync_with_buffer(struct ssam_controller *ctrl, -+ struct ssam_request *spec, ++ const struct ssam_request *spec, + struct ssam_response *rsp, + struct ssam_span *buf); + @@ -17824,7 +18199,7 @@ index 000000000000..d91348d54457 +#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..41d06f665135 +index 000000000000..64b1299d7bd8 --- /dev/null +++ b/include/linux/surface_aggregator/device.h @@ -0,0 +1,430 @@ @@ -17850,7 +18225,7 @@ index 000000000000..41d06f665135 +#include + + -+/* -- Surface System Aggregator Module Bus. --------------------------------- */ ++/* -- Surface System Aggregator Module bus. --------------------------------- */ + +/** + * enum ssam_device_domain - SAM device domain. diff --git a/patches/4.19/0009-surface-sam-over-hid.patch b/patches/4.19/0009-surface-sam-over-hid.patch index aba7461e5..11daf0d4a 100644 --- a/patches/4.19/0009-surface-sam-over-hid.patch +++ b/patches/4.19/0009-surface-sam-over-hid.patch @@ -1,7 +1,7 @@ -From ea7396cdf81dde8a89404ae962a1f3c0fa4ceb39 Mon Sep 17 00:00:00 2001 +From 1a31644e36615c46e3a3f748a9ca7734d088fbf2 Mon Sep 17 00:00:00 2001 From: Maximilian Luz Date: Sat, 25 Jul 2020 17:19:53 +0200 -Subject: [PATCH 09/10] surface-sam-over-hid +Subject: [PATCH 09/11] surface-sam-over-hid --- drivers/i2c/i2c-core-acpi.c | 35 +++++++ @@ -65,10 +65,10 @@ index eb0569359387..c2b5a2aca731 100644 dev_warn(&adapter->dev, "protocol 0x%02x not supported for client 0x%02x\n", accessor_type, client->addr); diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig -index 2ad19dc64a4a..57131ca5eb1e 100644 +index 0d20ffdb5a67..1e1ec7e562e8 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig -@@ -1167,6 +1167,13 @@ config SURFACE_3_POWER_OPREGION +@@ -1168,6 +1168,13 @@ config SURFACE_3_POWER_OPREGION Select this option to enable support for ACPI operation region of the Surface 3 battery platform driver. diff --git a/patches/4.19/0010-surface-gpe.patch b/patches/4.19/0010-surface-gpe.patch index e8c13eefc..701f3f0f6 100644 --- a/patches/4.19/0010-surface-gpe.patch +++ b/patches/4.19/0010-surface-gpe.patch @@ -1,7 +1,7 @@ -From 0461bfb515ea6b0e393d06c918090f4534fe793e Mon Sep 17 00:00:00 2001 +From fef8fda488d7c56fad5cf2fe238441f165b12e1d Mon Sep 17 00:00:00 2001 From: Maximilian Luz Date: Sun, 16 Aug 2020 23:39:56 +0200 -Subject: [PATCH 10/10] surface-gpe +Subject: [PATCH 10/11] surface-gpe --- drivers/platform/x86/Kconfig | 9 + @@ -11,10 +11,10 @@ Subject: [PATCH 10/10] surface-gpe create mode 100644 drivers/platform/x86/surface_gpe.c diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig -index 57131ca5eb1e..e2c05d95f8b2 100644 +index 1e1ec7e562e8..0b50f1e4c437 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig -@@ -1174,6 +1174,15 @@ config SURFACE_BOOK1_DGPU_SWITCH +@@ -1175,6 +1175,15 @@ config SURFACE_BOOK1_DGPU_SWITCH This driver provides a sysfs switch to set the power-state of the discrete GPU found on the Microsoft Surface Book 1. diff --git a/patches/4.19/0011-i2c-core-Restore-acpi_walk_dep_device_list-getting-c.patch b/patches/4.19/0011-i2c-core-Restore-acpi_walk_dep_device_list-getting-c.patch new file mode 100644 index 000000000..fc2e3b4e6 --- /dev/null +++ b/patches/4.19/0011-i2c-core-Restore-acpi_walk_dep_device_list-getting-c.patch @@ -0,0 +1,70 @@ +From d6a1bcfe65b9c45fedcf19fdcc7d02245a8e09b4 Mon Sep 17 00:00:00 2001 +From: Hans de Goede +Date: Wed, 14 Oct 2020 16:41:58 +0200 +Subject: [PATCH 11/11] i2c: core: Restore acpi_walk_dep_device_list() getting + called after registering the ACPI i2c devs + +Commit 21653a4181ff ("i2c: core: Call i2c_acpi_install_space_handler() +before i2c_acpi_register_devices()")'s intention was to only move the +acpi_install_address_space_handler() call to the point before where +the ACPI declared i2c-children of the adapter where instantiated by +i2c_acpi_register_devices(). + +But i2c_acpi_install_space_handler() had a call to +acpi_walk_dep_device_list() hidden (that is I missed it) at the end +of it, so as an unwanted side-effect now acpi_walk_dep_device_list() +was also being called before i2c_acpi_register_devices(). + +Move the acpi_walk_dep_device_list() call to the end of +i2c_acpi_register_devices(), so that it is once again called *after* +the i2c_client-s hanging of the adapter have been created. + +This fixes the Microsoft Surface Go 2 hanging at boot. + +Fixes: 21653a4181ff ("i2c: core: Call i2c_acpi_install_space_handler() before i2c_acpi_register_devices()") +Suggested-by: Maximilian Luz +Reported-and-tested-by: Kieran Bingham +Signed-off-by: Hans de Goede +--- + drivers/i2c/i2c-core-acpi.c | 11 ++++++++++- + 1 file changed, 10 insertions(+), 1 deletion(-) + +diff --git a/drivers/i2c/i2c-core-acpi.c b/drivers/i2c/i2c-core-acpi.c +index c2b5a2aca731..f9a24b56fec0 100644 +--- a/drivers/i2c/i2c-core-acpi.c ++++ b/drivers/i2c/i2c-core-acpi.c +@@ -219,6 +219,7 @@ static acpi_status i2c_acpi_add_device(acpi_handle handle, u32 level, + void i2c_acpi_register_devices(struct i2c_adapter *adap) + { + acpi_status status; ++ acpi_handle handle; + + if (!has_acpi_companion(&adap->dev)) + return; +@@ -229,6 +230,15 @@ void i2c_acpi_register_devices(struct i2c_adapter *adap) + adap, NULL); + if (ACPI_FAILURE(status)) + dev_warn(&adap->dev, "failed to enumerate I2C slaves\n"); ++ ++ if (!adap->dev.parent) ++ return; ++ ++ handle = ACPI_HANDLE(adap->dev.parent); ++ if (!handle) ++ return; ++ ++ acpi_walk_dep_device_list(handle); + } + + const struct acpi_device_id * +@@ -728,7 +738,6 @@ int i2c_acpi_install_space_handler(struct i2c_adapter *adapter) + return -ENOMEM; + } + +- acpi_walk_dep_device_list(handle); + return 0; + } + +-- +2.28.0 + diff --git a/pkg/arch/kernel-lts/0011-i2c-core-Restore-acpi_walk_dep_device_list-getting-c.patch b/pkg/arch/kernel-lts/0011-i2c-core-Restore-acpi_walk_dep_device_list-getting-c.patch new file mode 120000 index 000000000..9894f12f6 --- /dev/null +++ b/pkg/arch/kernel-lts/0011-i2c-core-Restore-acpi_walk_dep_device_list-getting-c.patch @@ -0,0 +1 @@ +../../../patches/4.19/0011-i2c-core-Restore-acpi_walk_dep_device_list-getting-c.patch \ No newline at end of file diff --git a/pkg/arch/kernel-lts/PKGBUILD b/pkg/arch/kernel-lts/PKGBUILD index 7454f9575..f0650c62e 100644 --- a/pkg/arch/kernel-lts/PKGBUILD +++ b/pkg/arch/kernel-lts/PKGBUILD @@ -30,6 +30,7 @@ source=( 0008-surface-sam.patch 0009-surface-sam-over-hid.patch 0010-surface-gpe.patch + 0011-i2c-core-Restore-acpi_walk_dep_device_list-getting-c.patch ) validpgpkeys=( 'ABAF11C65A2970B130ABE3C479BE3E4300411886' # Linus Torvalds @@ -40,17 +41,18 @@ sha256sums=('f2f709ef086a4d8cb3c15a857daa44dfecf1b88d7d7c53c980fb180f6dccbace' 'SKIP' '4e68572e7cc4c5368f0236e0792660ae8498373988625dca46e509399a7eaea6' 'a13581d3c6dc595206e4fe7fcf6b542e7a1bdbe96101f0f010fc5be49f99baf2' - 'e34d7cd57431f26d6da83f7b71f66116c81089fd3d5fc149d1020be54bb6ec72' - 'cab05db8d6b4c7cec18acb4feefa9be871146a066afcab75901c5aeef0cbd723' - 'c5064dbda8c1da8edd394f178e4bb0eff9fea624fd2d187649749a9dc8b7d194' - 'e823d4ff94b6d715923772d8ee44c9949e6f79e9258977a89e709ed1fe62a1af' - 'eed4b20c1c41c78314a3ada3323622d04809c93adab178e68eae04680aa3dc91' - '516929e55a36d94fb803b9ad8f5760bace3dec138fcdba62cfea82af0f57320b' - '6d2d74dcc481be1d9db80fbe2a09fc358fe8d03d28ecbc5fc3e92c4c4d99e35d' - '9627632dad1e8ec117118f9c6bfa2c9eda829bf96bf7a2a5e301ff8e7958973e' - '3d90363a51b87544425f41e002c02d094820c33b8e64eb610b12d09eefcea870' - 'b8b63934eb256b4ace372a145595e98b548af8bf58f9eb1e1930bec98b1dc6cc' - '6017221395b343c90d575344f76eb27de8b0917d420c811e72e11e70340c8814') + '1c4963e4a911e74ed56f1fe0065c31201edca9a5e3f89eb30b7063f9981ebdd8' + '3892c3974f53e87e2efa059326359c3108bba1a411eecd5a2b614174384e8755' + 'e73d7567a23d10babda2124a65337c8f57ff61fbb1c65a629afdfcc7a3d542e2' + '33c4264b920e2e4466de8369e3f2fcf3383c2e0eb68dacff82e107ae9f8d2354' + '20c72eea089af4ca105bec93bd7c7e91c2e8fbe5d24c88188e4ca2c77699a7ab' + '776ce945b51e59c543e19cc9e006b5047b22ec06255c2cce1163152aa3c65894' + '6185565a16689190140e6fe19df4d51fac4eeb1ac4a927855ec555f2274d7d0c' + 'd205cf4756307c6c0909e26ca4b2ca45354a82df29255cd5809caf4b6aed41e1' + '8d7e5e77eac47a9750530813894067348985f2ece285d995b721c368d512fe14' + '058ac04d25069f51f0d8899e78c3f089155e0c2aeb47ee4c6befc9db1a8f959b' + '842832fd4356fc24f191674b96aab9ee3d140f00ca3d32fd453a159f596d822e' + '2739e3f2d0e70ce40c52b3d550aa4ac7073c2d6bbe5fd684fb981f02f32d373e') export KBUILD_BUILD_HOST=archlinux export KBUILD_BUILD_USER=$pkgbase