From 0f203948230720e849ad50d158adac1cd32c282f Mon Sep 17 00:00:00 2001 From: Christophe JAILLET Date: Sat, 15 Jan 2022 16:24:50 +0100 Subject: [PATCH 001/137] HID: amd_sfh: Remove useless DMA-32 fallback configuration As stated in [1], dma_set_mask() with a 64-bit mask never fails if dev->dma_mask is non-NULL. So, if it fails, the 32 bits case will also fail for the same reason. Simplify code and remove some dead code accordingly. [1]: https://lore.kernel.org/linux-kernel/YL3vSPK5DXTNvgdx@infradead.org/#t Signed-off-by: Christophe JAILLET Acked-by: Basavaraj Natikar Signed-off-by: Jiri Kosina --- drivers/hid/amd-sfh-hid/amd_sfh_pcie.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/drivers/hid/amd-sfh-hid/amd_sfh_pcie.c b/drivers/hid/amd-sfh-hid/amd_sfh_pcie.c index 2503be0253d3..673536d1d9ba 100644 --- a/drivers/hid/amd-sfh-hid/amd_sfh_pcie.c +++ b/drivers/hid/amd-sfh-hid/amd_sfh_pcie.c @@ -248,11 +248,8 @@ static int amd_mp2_pci_probe(struct pci_dev *pdev, const struct pci_device_id *i pci_set_master(pdev); rc = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(64)); if (rc) { - rc = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32)); - if (rc) { - dev_err(&pdev->dev, "failed to set DMA mask\n"); - return rc; - } + dev_err(&pdev->dev, "failed to set DMA mask\n"); + return rc; } privdata->cl_data = devm_kzalloc(&pdev->dev, sizeof(struct amdtp_cl_data), GFP_KERNEL); From 06608bc2d9e6f5a24baf51951e9e2fff3ec78e54 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Sat, 25 Dec 2021 13:00:26 +0100 Subject: [PATCH 002/137] gpio: crystalcove: Set IRQ domain bus token to DOMAIN_BUS_WIRED For the CRC PMIC we end up with multiple irq-domains with the same fwnode. One for the irqchip which demultiplexes the actual PMIC interrupt into interrupts for the various cells (known as the level 1 interrupts); And 2 more for the irqchips which are part of the crystal_cove_gpio and crystal_cove_charger cells. This leads to the following error being printed when CONFIG_GENERIC_IRQ_DEBUGFS is enabled: debugfs: File '\_SB.I2C7.PMIC' in directory 'domains' already present! Set the bus token of the main IRQ domain to DOMAIN_BUS_WIRED to avoid this error, this also allows irq_find_matching_fwspec() to find the right domain if necessary. Note all 3 domain registering drivers need to set the IRQ domain bus token. This is necessary because the IRQ domain code defaults to creating the debugfs dir with just the fwnode name and then renames it when the bus token is set. So each one starts with the same default name and all 3 must be given a different name to avoid problems when one of the other drivers loads and starts with the same default name. Signed-off-by: Hans de Goede Acked-by: Andy Shevchenko Signed-off-by: Andy Shevchenko --- drivers/gpio/gpio-crystalcove.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/drivers/gpio/gpio-crystalcove.c b/drivers/gpio/gpio-crystalcove.c index 5a909f3c79e8..b55c74a5e064 100644 --- a/drivers/gpio/gpio-crystalcove.c +++ b/drivers/gpio/gpio-crystalcove.c @@ -370,7 +370,14 @@ static int crystalcove_gpio_probe(struct platform_device *pdev) return retval; } - return devm_gpiochip_add_data(&pdev->dev, &cg->chip, cg); + retval = devm_gpiochip_add_data(&pdev->dev, &cg->chip, cg); + if (retval) + return retval; + + /* Distuingish IRQ domain from others sharing (MFD) the same fwnode */ + irq_domain_update_bus_token(cg->chip.irq.domain, DOMAIN_BUS_WIRED); + + return 0; } static struct platform_driver crystalcove_gpio_driver = { From c84eab5850d11bea546491bb1798039448971141 Mon Sep 17 00:00:00 2001 From: Xiaoke Wang Date: Mon, 17 Jan 2022 15:06:06 +0800 Subject: [PATCH 003/137] gpio: merrifield: check the return value of devm_kstrdup() devm_kstrdup() returns pointer to allocated string on success, NULL on failure. So it is better to check the return value of it. Before, if devm_kstrdup() fails, pinctrl_dev_name will be NULL, then the retval below will be a negative error-code (inside gpiochip_add_pin_range(), pinctrl_find_and_add_gpio_range()->get_pinctrl_dev_from_devname() will finally check pinctrl_dev_name and return an error), so the failure of devm_kstrdup() can be only implicitly caught after a long call chain. While this patch is to explicitly catch the failure in time. Signed-off-by: Xiaoke Wang Signed-off-by: Andy Shevchenko --- drivers/gpio/gpio-merrifield.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/gpio/gpio-merrifield.c b/drivers/gpio/gpio-merrifield.c index 42c4d9d0cd50..f3d1baeacbe9 100644 --- a/drivers/gpio/gpio-merrifield.c +++ b/drivers/gpio/gpio-merrifield.c @@ -409,6 +409,9 @@ static int mrfld_gpio_add_pin_ranges(struct gpio_chip *chip) int retval; pinctrl_dev_name = mrfld_gpio_get_pinctrl_dev_name(priv); + if (!pinctrl_dev_name) + return -ENOMEM; + for (i = 0; i < ARRAY_SIZE(mrfld_gpio_ranges); i++) { range = &mrfld_gpio_ranges[i]; retval = gpiochip_add_pin_range(&priv->chip, pinctrl_dev_name, From f473bdccb8775e8935cc08ca9800cae5f700a9b5 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Thu, 23 Dec 2021 14:27:33 +0200 Subject: [PATCH 004/137] gpio: altera-a10sr: Switch to use fwnode instead of of_node GPIO library now accepts fwnode as a firmware node, so switch the driver to use it. Signed-off-by: Andy Shevchenko Reviewed-by: Linus Walleij --- drivers/gpio/gpio-altera-a10sr.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/gpio/gpio-altera-a10sr.c b/drivers/gpio/gpio-altera-a10sr.c index 6af51feda06f..be1ed7ee5225 100644 --- a/drivers/gpio/gpio-altera-a10sr.c +++ b/drivers/gpio/gpio-altera-a10sr.c @@ -10,6 +10,7 @@ #include #include #include +#include /** * struct altr_a10sr_gpio - Altera Max5 GPIO device private data structure @@ -88,7 +89,7 @@ static int altr_a10sr_gpio_probe(struct platform_device *pdev) gpio->gp = altr_a10sr_gc; gpio->gp.parent = pdev->dev.parent; - gpio->gp.of_node = pdev->dev.of_node; + gpio->gp.fwnode = dev_fwnode(&pdev->dev); return devm_gpiochip_add_data(&pdev->dev, &gpio->gp, gpio); } From b3376ed7d82f5937cecb17ff92d5d8dc0762e1c1 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Thu, 23 Dec 2021 14:26:39 +0200 Subject: [PATCH 005/137] gpio: tegra: Get rid of duplicate of_node assignment GPIO library does copy the of_node from the parent device of the GPIO chip, there is no need to repeat this in the individual drivers. Remove these assignment all at once. For the details one may look into the of_gpio_dev_init() implementation. Signed-off-by: Andy Shevchenko Reviewed-by: Thierry Reding Tested-by: Thierry Reding --- drivers/gpio/gpio-tegra.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/gpio/gpio-tegra.c b/drivers/gpio/gpio-tegra.c index 7f5bc10a6479..ff2d2a1f9c73 100644 --- a/drivers/gpio/gpio-tegra.c +++ b/drivers/gpio/gpio-tegra.c @@ -691,7 +691,6 @@ static int tegra_gpio_probe(struct platform_device *pdev) tgi->gc.base = 0; tgi->gc.ngpio = tgi->bank_count * 32; tgi->gc.parent = &pdev->dev; - tgi->gc.of_node = pdev->dev.of_node; tgi->ic.name = "GPIO"; tgi->ic.irq_ack = tegra_gpio_irq_ack; From a1ce76e89907a69713f729ff21db1efa00f3bb47 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Tue, 11 Jan 2022 11:56:32 +0100 Subject: [PATCH 006/137] gpio: tps68470: Allow building as module The gpio-tps68470 driver binds to a tps68470-gpio platform-device which itself gets instantiated by a special MFD driver from drivers/platform/x86/intel/int3472/tps68470.c This MFD driver itself can be built as a module, so it makes no sense to force the gpio-tps68470 driver to always be built-in. Reviewed-by: Andy Shevchenko Signed-off-by: Hans de Goede Signed-off-by: Andy Shevchenko --- drivers/gpio/Kconfig | 6 +----- drivers/gpio/gpio-tps68470.c | 5 ++++- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index 1c211b4c63be..04c48f315b05 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -1380,7 +1380,7 @@ config GPIO_TPS65912 This driver supports TPS65912 GPIO chip. config GPIO_TPS68470 - bool "TPS68470 GPIO" + tristate "TPS68470 GPIO" depends on INTEL_SKL_INT3472 help Select this option to enable GPIO driver for the TPS68470 @@ -1390,10 +1390,6 @@ config GPIO_TPS68470 input or output as appropriate, the sensor related GPIOs are "output only" GPIOs. - This driver config is bool, as the GPIO functionality - of the TPS68470 must be available before dependent - drivers are loaded. - config GPIO_TQMX86 tristate "TQ-Systems QTMX86 GPIO" depends on MFD_TQMX86 || COMPILE_TEST diff --git a/drivers/gpio/gpio-tps68470.c b/drivers/gpio/gpio-tps68470.c index 423b7bc30ae8..aaddcabe9b35 100644 --- a/drivers/gpio/gpio-tps68470.c +++ b/drivers/gpio/gpio-tps68470.c @@ -154,5 +154,8 @@ static struct platform_driver tps68470_gpio_driver = { }, .probe = tps68470_gpio_probe, }; +module_platform_driver(tps68470_gpio_driver); -builtin_platform_driver(tps68470_gpio_driver) +MODULE_ALIAS("platform:tps68470-gpio"); +MODULE_DESCRIPTION("GPIO driver for TPS68470 PMIC"); +MODULE_LICENSE("GPL v2"); From 8bdc849f76963c217b9e15467b2124fd011431f3 Mon Sep 17 00:00:00 2001 From: Marcel Ziswiler Date: Fri, 14 Jan 2022 15:14:58 +0100 Subject: [PATCH 007/137] dt-bindings: gpio: fix gpio-hog example Even if this is no yaml yet at least fix the example to be compliant to later schema as e.g. found in gpio-pca95xx.yaml, fairchild,74hc595.yaml and gpio/fsl-imx-gpio.yaml. Signed-off-by: Marcel Ziswiler Acked-by: Rob Herring Reviewed-by: Linus Walleij Signed-off-by: Bartosz Golaszewski --- Documentation/devicetree/bindings/gpio/gpio.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/gpio/gpio.txt b/Documentation/devicetree/bindings/gpio/gpio.txt index a8895d339bfe..5663e71b751f 100644 --- a/Documentation/devicetree/bindings/gpio/gpio.txt +++ b/Documentation/devicetree/bindings/gpio/gpio.txt @@ -213,7 +213,7 @@ Example of two SOC GPIO banks defined as gpio-controller nodes: gpio-controller; #gpio-cells = <2>; - line_b { + line_b-hog { gpio-hog; gpios = <6 0>; output-low; From 4737499ca3dfefb86d70a5a3a9d4cef2bb3bdabc Mon Sep 17 00:00:00 2001 From: John Crispin Date: Sun, 30 Jan 2022 15:51:14 +0100 Subject: [PATCH 008/137] dt-bindings: arm: airoha: Add binding for Airoha GPIO controller Airoha's GPIO controller on their ARM EN7523 SoCs consists of two banks of 32 GPIOs Reviewed-by: Linus Walleij Reviewed-by: Rob Herring Signed-off-by: John Crispin Signed-off-by: Felix Fietkau [Bartosz: removed stray newline at the end of the file] Signed-off-by: Bartosz Golaszewski --- .../bindings/gpio/airoha,en7523-gpio.yaml | 66 +++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 Documentation/devicetree/bindings/gpio/airoha,en7523-gpio.yaml diff --git a/Documentation/devicetree/bindings/gpio/airoha,en7523-gpio.yaml b/Documentation/devicetree/bindings/gpio/airoha,en7523-gpio.yaml new file mode 100644 index 000000000000..7c41d8e814cd --- /dev/null +++ b/Documentation/devicetree/bindings/gpio/airoha,en7523-gpio.yaml @@ -0,0 +1,66 @@ +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/gpio/airoha,en7523-gpio.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Airoha EN7523 GPIO controller + +maintainers: + - John Crispin + +description: | + Airoha's GPIO controller on their ARM EN7523 SoCs consists of two banks of 32 + GPIOs. + +properties: + $nodename: + pattern: "^gpio@[0-9a-f]+$" + + compatible: + items: + - const: airoha,en7523-gpio + + reg: + description: | + The first tuple points to the input register. + The second and third tuple point to the direction registers + The fourth tuple points to the output register + maxItems: 4 + + "#gpio-cells": + const: 2 + + gpio-controller: true + +required: + - compatible + - reg + - "#gpio-cells" + - gpio-controller + +additionalProperties: false + +examples: + - | + gpio0: gpio@1fbf0200 { + compatible = "airoha,en7523-gpio"; + reg = <0x1fbf0204 0x4>, + <0x1fbf0200 0x4>, + <0x1fbf0220 0x4>, + <0x1fbf0214 0x4>; + gpio-controller; + #gpio-cells = <2>; + }; + + gpio1: gpio@1fbf0270 { + compatible = "airoha,en7523-gpio"; + reg = <0x1fbf0270 0x4>, + <0x1fbf0260 0x4>, + <0x1fbf0264 0x4>, + <0x1fbf0278 0x4>; + gpio-controller; + #gpio-cells = <2>; + }; + +... From 0868ad385affa28cf15aebca3c38c5c51f79b286 Mon Sep 17 00:00:00 2001 From: John Crispin Date: Sun, 30 Jan 2022 15:51:15 +0100 Subject: [PATCH 009/137] gpio: Add support for Airoha EN7523 GPIO controller Airoha's GPIO controller on their ARM EN7523 SoCs consists of two banks of 32 GPIOs. Each instance in DT is for a single bank. Acked-by: Bartosz Golaszewski Reviewed-by: Linus Walleij Reviewed-by: Andy Shevchenko Signed-off-by: John Crispin Signed-off-by: Felix Fietkau Signed-off-by: Bartosz Golaszewski --- drivers/gpio/Kconfig | 10 +++ drivers/gpio/Makefile | 1 + drivers/gpio/gpio-en7523.c | 137 +++++++++++++++++++++++++++++++++++++ 3 files changed, 148 insertions(+) create mode 100644 drivers/gpio/gpio-en7523.c diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig index 1c211b4c63be..2031a727be30 100644 --- a/drivers/gpio/Kconfig +++ b/drivers/gpio/Kconfig @@ -247,6 +247,16 @@ config GPIO_EM help Say yes here to support GPIO on Renesas Emma Mobile SoCs. +config GPIO_EN7523 + tristate "Airoha GPIO support" + depends on ARCH_AIROHA + default ARCH_AIROHA + select GPIO_GENERIC + select GPIOLIB_IRQCHIP + help + Say Y or M here to support the GPIO controller block on the + Airoha EN7523 SoC. It supports two banks of 32 GPIOs. + config GPIO_EP93XX def_bool y depends on ARCH_EP93XX diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile index edbaa3cb343c..1a14e248bdbd 100644 --- a/drivers/gpio/Makefile +++ b/drivers/gpio/Makefile @@ -55,6 +55,7 @@ obj-$(CONFIG_GPIO_DLN2) += gpio-dln2.o obj-$(CONFIG_GPIO_DWAPB) += gpio-dwapb.o obj-$(CONFIG_GPIO_EIC_SPRD) += gpio-eic-sprd.o obj-$(CONFIG_GPIO_EM) += gpio-em.o +obj-$(CONFIG_GPIO_EN7523) += gpio-en7523.o obj-$(CONFIG_GPIO_EP93XX) += gpio-ep93xx.o obj-$(CONFIG_GPIO_EXAR) += gpio-exar.o obj-$(CONFIG_GPIO_F7188X) += gpio-f7188x.o diff --git a/drivers/gpio/gpio-en7523.c b/drivers/gpio/gpio-en7523.c new file mode 100644 index 000000000000..f836a8db4c1d --- /dev/null +++ b/drivers/gpio/gpio-en7523.c @@ -0,0 +1,137 @@ +// SPDX-License-Identifier: GPL-2.0-only + +#include +#include +#include +#include +#include +#include +#include +#include + +#define AIROHA_GPIO_MAX 32 + +/** + * airoha_gpio_ctrl - Airoha GPIO driver data + * @gc: Associated gpio_chip instance. + * @data: The data register. + * @dir0: The direction register for the lower 16 pins. + * @dir1: The direction register for the higher 16 pins. + * @output: The output enable register. + */ +struct airoha_gpio_ctrl { + struct gpio_chip gc; + void __iomem *data; + void __iomem *dir[2]; + void __iomem *output; +}; + +static struct airoha_gpio_ctrl *gc_to_ctrl(struct gpio_chip *gc) +{ + return container_of(gc, struct airoha_gpio_ctrl, gc); +} + +static int airoha_dir_set(struct gpio_chip *gc, unsigned int gpio, + int val, int out) +{ + struct airoha_gpio_ctrl *ctrl = gc_to_ctrl(gc); + u32 dir = ioread32(ctrl->dir[gpio / 16]); + u32 output = ioread32(ctrl->output); + u32 mask = BIT((gpio % 16) * 2); + + if (out) { + dir |= mask; + output |= BIT(gpio); + } else { + dir &= ~mask; + output &= ~BIT(gpio); + } + + iowrite32(dir, ctrl->dir[gpio / 16]); + + if (out) + gc->set(gc, gpio, val); + + iowrite32(output, ctrl->output); + + return 0; +} + +static int airoha_dir_out(struct gpio_chip *gc, unsigned int gpio, + int val) +{ + return airoha_dir_set(gc, gpio, val, 1); +} + +static int airoha_dir_in(struct gpio_chip *gc, unsigned int gpio) +{ + return airoha_dir_set(gc, gpio, 0, 0); +} + +static int airoha_get_dir(struct gpio_chip *gc, unsigned int gpio) +{ + struct airoha_gpio_ctrl *ctrl = gc_to_ctrl(gc); + u32 dir = ioread32(ctrl->dir[gpio / 16]); + u32 mask = BIT((gpio % 16) * 2); + + return (dir & mask) ? GPIO_LINE_DIRECTION_OUT : GPIO_LINE_DIRECTION_IN; +} + +static int airoha_gpio_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct airoha_gpio_ctrl *ctrl; + int err; + + ctrl = devm_kzalloc(dev, sizeof(*ctrl), GFP_KERNEL); + if (!ctrl) + return -ENOMEM; + + ctrl->data = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(ctrl->data)) + return PTR_ERR(ctrl->data); + + ctrl->dir[0] = devm_platform_ioremap_resource(pdev, 1); + if (IS_ERR(ctrl->dir[0])) + return PTR_ERR(ctrl->dir[0]); + + ctrl->dir[1] = devm_platform_ioremap_resource(pdev, 2); + if (IS_ERR(ctrl->dir[1])) + return PTR_ERR(ctrl->dir[1]); + + ctrl->output = devm_platform_ioremap_resource(pdev, 3); + if (IS_ERR(ctrl->output)) + return PTR_ERR(ctrl->output); + + err = bgpio_init(&ctrl->gc, dev, 4, ctrl->data, NULL, + NULL, NULL, NULL, 0); + if (err) + return dev_err_probe(dev, err, "unable to init generic GPIO"); + + ctrl->gc.ngpio = AIROHA_GPIO_MAX; + ctrl->gc.owner = THIS_MODULE; + ctrl->gc.direction_output = airoha_dir_out; + ctrl->gc.direction_input = airoha_dir_in; + ctrl->gc.get_direction = airoha_get_dir; + + return devm_gpiochip_add_data(dev, &ctrl->gc, ctrl); +} + +static const struct of_device_id airoha_gpio_of_match[] = { + { .compatible = "airoha,en7523-gpio" }, + { } +}; +MODULE_DEVICE_TABLE(of, airoha_gpio_of_match); + +static struct platform_driver airoha_gpio_driver = { + .driver = { + .name = "airoha-gpio", + .of_match_table = airoha_gpio_of_match, + }, + .probe = airoha_gpio_probe, +}; +module_platform_driver(airoha_gpio_driver); + +MODULE_DESCRIPTION("Airoha GPIO support"); +MODULE_AUTHOR("John Crispin "); +MODULE_LICENSE("GPL v2"); From 01f1269fbb0ead7e37f85213f77fb8e7efc4e099 Mon Sep 17 00:00:00 2001 From: "Gustavo A. R. Silva" Date: Tue, 25 Jan 2022 17:37:44 -0600 Subject: [PATCH 010/137] HID: google: Use struct_size() helper in kzalloc() Make use of the struct_size() helper instead of an open-coded version, in order to avoid any potential type mistakes or integer overflows that, in the worst scenario, could lead to heap overflows. Also, address the following sparse warnings: drivers/hid/hid-google-hammer.c:61:23: warning: using sizeof on a flexible structure Link: https://github.com/KSPP/linux/issues/174 Signed-off-by: Gustavo A. R. Silva Signed-off-by: Jiri Kosina --- drivers/hid/hid-google-hammer.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hid/hid-google-hammer.c b/drivers/hid/hid-google-hammer.c index 0403beb3104b..ddbe0de177e2 100644 --- a/drivers/hid/hid-google-hammer.c +++ b/drivers/hid/hid-google-hammer.c @@ -58,7 +58,7 @@ static int cbas_ec_query_base(struct cros_ec_device *ec_dev, bool get_state, struct cros_ec_command *msg; int ret; - msg = kzalloc(sizeof(*msg) + max(sizeof(u32), sizeof(*params)), + msg = kzalloc(struct_size(msg, data, max(sizeof(u32), sizeof(*params))), GFP_KERNEL); if (!msg) return -ENOMEM; From 976734041b6c958838a97642f82715576d09c23e Mon Sep 17 00:00:00 2001 From: Desmond Lim Date: Thu, 30 Dec 2021 23:27:56 +0800 Subject: [PATCH 011/137] HID: add SiGma Micro driver Fix for SiGma Micro-based keyboards where all the modifier keys mapped to Shift_L. Co-developed-by: Kinglong Mee Signed-off-by: Kinglong Mee Signed-off-by: Desmond Lim Signed-off-by: Jiri Kosina --- drivers/hid/Kconfig | 10 +++ drivers/hid/Makefile | 1 + drivers/hid/hid-ids.h | 1 + drivers/hid/hid-sigmamicro.c | 130 +++++++++++++++++++++++++++++++++++ 4 files changed, 142 insertions(+) create mode 100644 drivers/hid/hid-sigmamicro.c diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig index f5544157576c..c983b0afc635 100644 --- a/drivers/hid/Kconfig +++ b/drivers/hid/Kconfig @@ -984,6 +984,16 @@ config HID_SEMITEK - Woo-dy - X-Bows Nature/Knight +config HID_SIGMAMICRO + tristate "SiGma Micro-based keyboards" + depends on USB_HID + help + Support for keyboards that use the SiGma Micro (a.k.a SigmaChip) IC. + + Supported devices: + - Landslides KR-700 + - Rapoo V500 + config HID_SONY tristate "Sony PS2/3/4 accessories" depends on USB_HID diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile index 6d3e630e81af..b370b9a7b4df 100644 --- a/drivers/hid/Makefile +++ b/drivers/hid/Makefile @@ -109,6 +109,7 @@ obj-$(CONFIG_HID_RMI) += hid-rmi.o obj-$(CONFIG_HID_SAITEK) += hid-saitek.o obj-$(CONFIG_HID_SAMSUNG) += hid-samsung.o obj-$(CONFIG_HID_SEMITEK) += hid-semitek.o +obj-$(CONFIG_HID_SIGMAMICRO) += hid-sigmamicro.o obj-$(CONFIG_HID_SMARTJOYPLUS) += hid-sjoy.o obj-$(CONFIG_HID_SONY) += hid-sony.o obj-$(CONFIG_HID_SPEEDLINK) += hid-speedlink.o diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h index 85975031389b..8a77040aa618 100644 --- a/drivers/hid/hid-ids.h +++ b/drivers/hid/hid-ids.h @@ -1092,6 +1092,7 @@ #define USB_VENDOR_ID_SIGMA_MICRO 0x1c4f #define USB_DEVICE_ID_SIGMA_MICRO_KEYBOARD 0x0002 +#define USB_DEVICE_ID_SIGMA_MICRO_KEYBOARD2 0x0059 #define USB_VENDOR_ID_SIGMATEL 0x066F #define USB_DEVICE_ID_SIGMATEL_STMP3780 0x3780 diff --git a/drivers/hid/hid-sigmamicro.c b/drivers/hid/hid-sigmamicro.c new file mode 100644 index 000000000000..2e7058ac0e9d --- /dev/null +++ b/drivers/hid/hid-sigmamicro.c @@ -0,0 +1,130 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * HID driver for SiGma Micro-based keyboards + * + * Copyright (c) 2016 Kinglong Mee + * Copyright (c) 2021 Desmond Lim + */ + +#include +#include +#include + +#include "hid-ids.h" + +static const __u8 sm_0059_rdesc[] = { + 0x05, 0x0c, /* Usage Page (Consumer Devices) 0 */ + 0x09, 0x01, /* Usage (Consumer Control) 2 */ + 0xa1, 0x01, /* Collection (Application) 4 */ + 0x85, 0x01, /* Report ID (1) 6 */ + 0x19, 0x00, /* Usage Minimum (0) 8 */ + 0x2a, 0x3c, 0x02, /* Usage Maximum (572) 10 */ + 0x15, 0x00, /* Logical Minimum (0) 13 */ + 0x26, 0x3c, 0x02, /* Logical Maximum (572) 15 */ + 0x95, 0x01, /* Report Count (1) 18 */ + 0x75, 0x10, /* Report Size (16) 20 */ + 0x81, 0x00, /* Input (Data,Arr,Abs) 22 */ + 0xc0, /* End Collection 24 */ + 0x05, 0x01, /* Usage Page (Generic Desktop) 25 */ + 0x09, 0x80, /* Usage (System Control) 27 */ + 0xa1, 0x01, /* Collection (Application) 29 */ + 0x85, 0x02, /* Report ID (2) 31 */ + 0x19, 0x81, /* Usage Minimum (129) 33 */ + 0x29, 0x83, /* Usage Maximum (131) 35 */ + 0x25, 0x01, /* Logical Maximum (1) 37 */ + 0x75, 0x01, /* Report Size (1) 39 */ + 0x95, 0x03, /* Report Count (3) 41 */ + 0x81, 0x02, /* Input (Data,Var,Abs) 43 */ + 0x95, 0x05, /* Report Count (5) 45 */ + 0x81, 0x01, /* Input (Cnst,Arr,Abs) 47 */ + 0xc0, /* End Collection 49 */ + 0x06, 0x00, 0xff, /* Usage Page (Vendor Defined Page 1) 50 */ + 0x09, 0x01, /* Usage (Vendor Usage 1) 53 */ + 0xa1, 0x01, /* Collection (Application) 55 */ + 0x85, 0x03, /* Report ID (3) 57 */ + 0x1a, 0xf1, 0x00, /* Usage Minimum (241) 59 */ + 0x2a, 0xf8, 0x00, /* Usage Maximum (248) 62 */ + 0x15, 0x00, /* Logical Minimum (0) 65 */ + 0x25, 0x01, /* Logical Maximum (1) 67 */ + 0x75, 0x01, /* Report Size (1) 69 */ + 0x95, 0x08, /* Report Count (8) 71 */ + 0x81, 0x02, /* Input (Data,Var,Abs) 73 */ + 0xc0, /* End Collection 75 */ + 0x05, 0x01, /* Usage Page (Generic Desktop) 76 */ + 0x09, 0x06, /* Usage (Keyboard) 78 */ + 0xa1, 0x01, /* Collection (Application) 80 */ + 0x85, 0x04, /* Report ID (4) 82 */ + 0x05, 0x07, /* Usage Page (Keyboard) 84 */ + 0x19, 0xe0, /* Usage Minimum (224) 86 */ + 0x29, 0xe7, /* Usage Maximum (231) 88 */ + 0x15, 0x00, /* Logical Minimum (0) 90 */ + 0x25, 0x01, /* Logical Maximum (1) 92 */ + 0x75, 0x01, /* Report Size (1) 94 */ + 0x95, 0x08, /* Report Count (8) 96 */ + 0x81, 0x00, /* Input (Data,Arr,Abs) 98 */ + 0x95, 0x30, /* Report Count (48) 100 */ + 0x75, 0x01, /* Report Size (1) 102 */ + 0x15, 0x00, /* Logical Minimum (0) 104 */ + 0x25, 0x01, /* Logical Maximum (1) 106 */ + 0x05, 0x07, /* Usage Page (Keyboard) 108 */ + 0x19, 0x00, /* Usage Minimum (0) 110 */ + 0x29, 0x2f, /* Usage Maximum (47) 112 */ + 0x81, 0x02, /* Input (Data,Var,Abs) 114 */ + 0xc0, /* End Collection 116 */ + 0x05, 0x01, /* Usage Page (Generic Desktop) 117 */ + 0x09, 0x06, /* Usage (Keyboard) 119 */ + 0xa1, 0x01, /* Collection (Application) 121 */ + 0x85, 0x05, /* Report ID (5) 123 */ + 0x95, 0x38, /* Report Count (56) 125 */ + 0x75, 0x01, /* Report Size (1) 127 */ + 0x15, 0x00, /* Logical Minimum (0) 129 */ + 0x25, 0x01, /* Logical Maximum (1) 131 */ + 0x05, 0x07, /* Usage Page (Keyboard) 133 */ + 0x19, 0x30, /* Usage Minimum (48) 135 */ + 0x29, 0x67, /* Usage Maximum (103) 137 */ + 0x81, 0x02, /* Input (Data,Var,Abs) 139 */ + 0xc0, /* End Collection 141 */ + 0x05, 0x01, /* Usage Page (Generic Desktop) 142 */ + 0x09, 0x06, /* Usage (Keyboard) 144 */ + 0xa1, 0x01, /* Collection (Application) 146 */ + 0x85, 0x06, /* Report ID (6) 148 */ + 0x95, 0x38, /* Report Count (56) 150 */ + 0x75, 0x01, /* Report Size (1) 152 */ + 0x15, 0x00, /* Logical Minimum (0) 154 */ + 0x25, 0x01, /* Logical Maximum (1) 156 */ + 0x05, 0x07, /* Usage Page (Keyboard) 158 */ + 0x19, 0x68, /* Usage Minimum (104) 160 */ + 0x29, 0x9f, /* Usage Maximum (159) 162 */ + 0x81, 0x02, /* Input (Data,Var,Abs) 164 */ + 0xc0, /* End Collection 166 */ +}; + +static __u8 *sm_report_fixup(struct hid_device *hdev, __u8 *rdesc, + unsigned int *rsize) +{ + if (*rsize == sizeof(sm_0059_rdesc) && + !memcmp(sm_0059_rdesc, rdesc, *rsize)) { + hid_info(hdev, "Fixing up SiGma Micro report descriptor\n"); + rdesc[99] = 0x02; + } + return rdesc; +} + +static const struct hid_device_id sm_devices[] = { + { HID_USB_DEVICE(USB_VENDOR_ID_SIGMA_MICRO, + USB_DEVICE_ID_SIGMA_MICRO_KEYBOARD2) }, + { } +}; +MODULE_DEVICE_TABLE(hid, sm_devices); + +static struct hid_driver sm_driver = { + .name = "sigmamicro", + .id_table = sm_devices, + .report_fixup = sm_report_fixup, +}; +module_hid_driver(sm_driver); + +MODULE_AUTHOR("Kinglong Mee "); +MODULE_AUTHOR("Desmond Lim "); +MODULE_DESCRIPTION("SiGma Micro HID driver"); +MODULE_LICENSE("GPL"); From e9b0e120d02abb45fe03e10d8a54828ceb92514e Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Thu, 3 Feb 2022 11:33:02 +0100 Subject: [PATCH 012/137] platform/x86: thinkpad_acpi: Add dual-fan quirk for T15g (2nd gen) The ThinkPad T15g Gen 2 has 2 fan, add a TPACPI_FAN_2CTL quirk entry for it to the fan_quirk_table[] so that both fans can be controllerd. Reported-and-tested-by: David Dreschner Signed-off-by: Hans de Goede Link: https://lore.kernel.org/r/20220203103302.49401-1-hdegoede@redhat.com --- drivers/platform/x86/thinkpad_acpi.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/platform/x86/thinkpad_acpi.c b/drivers/platform/x86/thinkpad_acpi.c index bd045486b933..3424b080db77 100644 --- a/drivers/platform/x86/thinkpad_acpi.c +++ b/drivers/platform/x86/thinkpad_acpi.c @@ -8703,6 +8703,7 @@ static const struct tpacpi_quirk fan_quirk_table[] __initconst = { TPACPI_Q_LNV3('N', '4', '0', TPACPI_FAN_2CTL), /* P1 / X1 Extreme (4nd gen) */ TPACPI_Q_LNV3('N', '3', '0', TPACPI_FAN_2CTL), /* P15 (1st gen) / P15v (1st gen) */ TPACPI_Q_LNV3('N', '3', '2', TPACPI_FAN_2CTL), /* X1 Carbon (9th gen) */ + TPACPI_Q_LNV3('N', '3', '7', TPACPI_FAN_2CTL), /* T15g (2nd gen) */ TPACPI_Q_LNV3('N', '1', 'O', TPACPI_FAN_NOFAN), /* X1 Tablet (2nd gen) */ }; From f7e62c5890f08543e7b4d305c4b4501f89aafe61 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Wed, 26 Jan 2022 20:38:27 +0100 Subject: [PATCH 013/137] platform/x86: Replace acpi_bus_get_device() Replace acpi_bus_get_device() that is going to be dropped with acpi_fetch_acpi_dev(). No intentional functional impact. Signed-off-by: Rafael J. Wysocki Link: https://lore.kernel.org/r/2631712.mvXUDI8C0e@kreacher Reviewed-by: Hans de Goede Signed-off-by: Hans de Goede --- drivers/platform/x86/intel/hid.c | 7 ++----- drivers/platform/x86/intel/int3472/discrete.c | 5 ++--- drivers/platform/x86/intel/vbtn.c | 7 ++----- drivers/platform/x86/thinkpad_acpi.c | 17 ++++++++--------- drivers/platform/x86/x86-android-tablets.c | 2 +- 5 files changed, 15 insertions(+), 23 deletions(-) diff --git a/drivers/platform/x86/intel/hid.c b/drivers/platform/x86/intel/hid.c index 13f8cf70b9ae..2def562c6e1d 100644 --- a/drivers/platform/x86/intel/hid.c +++ b/drivers/platform/x86/intel/hid.c @@ -726,12 +726,9 @@ static acpi_status __init check_acpi_dev(acpi_handle handle, u32 lvl, void *context, void **rv) { const struct acpi_device_id *ids = context; - struct acpi_device *dev; + struct acpi_device *dev = acpi_fetch_acpi_dev(handle); - if (acpi_bus_get_device(handle, &dev) != 0) - return AE_OK; - - if (acpi_match_device_ids(dev, ids) == 0) + if (dev && acpi_match_device_ids(dev, ids) == 0) if (!IS_ERR_OR_NULL(acpi_create_platform_device(dev, NULL))) dev_info(&dev->dev, "intel-hid: created platform device\n"); diff --git a/drivers/platform/x86/intel/int3472/discrete.c b/drivers/platform/x86/intel/int3472/discrete.c index 5b514fa01a97..ed4c9d760757 100644 --- a/drivers/platform/x86/intel/int3472/discrete.c +++ b/drivers/platform/x86/intel/int3472/discrete.c @@ -112,7 +112,6 @@ static int skl_int3472_map_gpio_to_sensor(struct int3472_discrete_device *int347 struct acpi_device *adev; acpi_handle handle; acpi_status status; - int ret; if (int3472->n_sensor_gpios >= INT3472_MAX_SENSOR_GPIOS) { dev_warn(int3472->dev, "Too many GPIOs mapped\n"); @@ -139,8 +138,8 @@ static int skl_int3472_map_gpio_to_sensor(struct int3472_discrete_device *int347 if (ACPI_FAILURE(status)) return -EINVAL; - ret = acpi_bus_get_device(handle, &adev); - if (ret) + adev = acpi_fetch_acpi_dev(handle); + if (!adev) return -ENODEV; table_entry = &int3472->gpios.table[int3472->n_sensor_gpios]; diff --git a/drivers/platform/x86/intel/vbtn.c b/drivers/platform/x86/intel/vbtn.c index 15f013af9e62..c5e4e35c8d20 100644 --- a/drivers/platform/x86/intel/vbtn.c +++ b/drivers/platform/x86/intel/vbtn.c @@ -384,12 +384,9 @@ static acpi_status __init check_acpi_dev(acpi_handle handle, u32 lvl, void *context, void **rv) { const struct acpi_device_id *ids = context; - struct acpi_device *dev; + struct acpi_device *dev = acpi_fetch_acpi_dev(handle); - if (acpi_bus_get_device(handle, &dev) != 0) - return AE_OK; - - if (acpi_match_device_ids(dev, ids) == 0) + if (dev && acpi_match_device_ids(dev, ids) == 0) if (!IS_ERR_OR_NULL(acpi_create_platform_device(dev, NULL))) dev_info(&dev->dev, "intel-vbtn: created platform device\n"); diff --git a/drivers/platform/x86/thinkpad_acpi.c b/drivers/platform/x86/thinkpad_acpi.c index 3424b080db77..e9b1574729b9 100644 --- a/drivers/platform/x86/thinkpad_acpi.c +++ b/drivers/platform/x86/thinkpad_acpi.c @@ -728,11 +728,10 @@ static void __init drv_acpi_handle_init(const char *name, static acpi_status __init tpacpi_acpi_handle_locate_callback(acpi_handle handle, u32 level, void *context, void **return_value) { - struct acpi_device *dev; if (!strcmp(context, "video")) { - if (acpi_bus_get_device(handle, &dev)) - return AE_OK; - if (strcmp(ACPI_VIDEO_HID, acpi_device_hid(dev))) + struct acpi_device *dev = acpi_fetch_acpi_dev(handle); + + if (!dev || strcmp(ACPI_VIDEO_HID, acpi_device_hid(dev))) return AE_OK; } @@ -786,7 +785,6 @@ static void dispatch_acpi_notify(acpi_handle handle, u32 event, void *data) static int __init setup_acpi_notify(struct ibm_struct *ibm) { acpi_status status; - int rc; BUG_ON(!ibm->acpi); @@ -796,9 +794,9 @@ static int __init setup_acpi_notify(struct ibm_struct *ibm) vdbg_printk(TPACPI_DBG_INIT, "setting up ACPI notify for %s\n", ibm->name); - rc = acpi_bus_get_device(*ibm->acpi->handle, &ibm->acpi->device); - if (rc < 0) { - pr_err("acpi_bus_get_device(%s) failed: %d\n", ibm->name, rc); + ibm->acpi->device = acpi_fetch_acpi_dev(*ibm->acpi->handle); + if (!ibm->acpi->device) { + pr_err("acpi_fetch_acpi_dev(%s) failed\n", ibm->name); return -ENODEV; } @@ -6723,7 +6721,8 @@ static int __init tpacpi_query_bcl_levels(acpi_handle handle) struct acpi_device *device, *child; int rc; - if (acpi_bus_get_device(handle, &device)) + device = acpi_fetch_acpi_dev(handle); + if (!device) return 0; rc = 0; diff --git a/drivers/platform/x86/x86-android-tablets.c b/drivers/platform/x86/x86-android-tablets.c index 9360a8a92486..3120acf9837c 100644 --- a/drivers/platform/x86/x86-android-tablets.c +++ b/drivers/platform/x86/x86-android-tablets.c @@ -105,7 +105,7 @@ static int x86_acpi_irq_helper_get(const struct x86_acpi_irq_data *data) return -ENODEV; } - acpi_bus_get_device(handle, &adev); + adev = acpi_fetch_acpi_dev(handle); if (!adev) { pr_err("error could not get %s adev\n", data->chip); return -ENODEV; From 6768bddb70f028a03cb297f18402988599959f7e Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Wed, 26 Jan 2022 20:41:27 +0100 Subject: [PATCH 014/137] platform/surface: Replace acpi_bus_get_device() Replace acpi_bus_get_device() that is going to be dropped with acpi_fetch_acpi_dev(). No intentional functional impact. Signed-off-by: Rafael J. Wysocki Reviewed-by: Maximilian Luz Link: https://lore.kernel.org/r/5805278.lOV4Wx5bFT@kreacher Reviewed-by: Hans de Goede Signed-off-by: Hans de Goede --- drivers/platform/surface/surface3-wmi.c | 12 ++++-------- drivers/platform/surface/surface_acpi_notify.c | 3 ++- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/drivers/platform/surface/surface3-wmi.c b/drivers/platform/surface/surface3-wmi.c index 09ac9cfc40d8..089a8a48d920 100644 --- a/drivers/platform/surface/surface3-wmi.c +++ b/drivers/platform/surface/surface3-wmi.c @@ -116,15 +116,11 @@ static acpi_status s3_wmi_attach_spi_device(acpi_handle handle, void *data, void **return_value) { - struct acpi_device *adev, **ts_adev; + struct acpi_device *adev = acpi_fetch_acpi_dev(handle); + struct acpi_device **ts_adev = data; - if (acpi_bus_get_device(handle, &adev)) - return AE_OK; - - ts_adev = data; - - if (strncmp(acpi_device_bid(adev), SPI_TS_OBJ_NAME, - strlen(SPI_TS_OBJ_NAME))) + if (!adev || strncmp(acpi_device_bid(adev), SPI_TS_OBJ_NAME, + strlen(SPI_TS_OBJ_NAME))) return AE_OK; if (*ts_adev) { diff --git a/drivers/platform/surface/surface_acpi_notify.c b/drivers/platform/surface/surface_acpi_notify.c index 8339988d95c1..7b758f8cc137 100644 --- a/drivers/platform/surface/surface_acpi_notify.c +++ b/drivers/platform/surface/surface_acpi_notify.c @@ -770,7 +770,8 @@ static acpi_status san_consumer_setup(acpi_handle handle, u32 lvl, return AE_OK; /* Ignore ACPI devices that are not present. */ - if (acpi_bus_get_device(handle, &adev) != 0) + adev = acpi_fetch_acpi_dev(handle); + if (!adev) return AE_OK; san_consumer_dbg(&pdev->dev, handle, "creating device link\n"); From 34fc68348554d5b0f98def6cae9f252c3eb0c172 Mon Sep 17 00:00:00 2001 From: Christophe JAILLET Date: Sun, 30 Jan 2022 09:36:54 +0100 Subject: [PATCH 015/137] platform/surface: surface3-wmi: Simplify resource management 's3_wmi.input' is a managed resource, so there should be no need to free it explicitly. Moreover, 's3_wmi' is a global variable. 's3_wmi.input' should be NULL when this error handling path is executed, because it has not been assigned yet. All this is puzzling. So simplify it and remove a few lines of code to have it be more straightforward. Fixes: 3dda3b3798f9 ("platform/x86: Add custom surface3 platform device for controlling LID") Signed-off-by: Christophe JAILLET Reviewed-by: Andy Shevchenko Reviewed-by: Maximilian Luz Link: https://lore.kernel.org/r/8b1a6d05036d5d9527241b2345482b369331ce5c.1643531799.git.christophe.jaillet@wanadoo.fr Reviewed-by: Hans de Goede Signed-off-by: Hans de Goede --- drivers/platform/surface/surface3-wmi.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/drivers/platform/surface/surface3-wmi.c b/drivers/platform/surface/surface3-wmi.c index 089a8a48d920..ca4602bcc7de 100644 --- a/drivers/platform/surface/surface3-wmi.c +++ b/drivers/platform/surface/surface3-wmi.c @@ -186,14 +186,11 @@ static int s3_wmi_create_and_register_input(struct platform_device *pdev) error = input_register_device(input); if (error) - goto out_err; + return error; s3_wmi.input = input; return 0; - out_err: - input_free_device(s3_wmi.input); - return error; } static int __init s3_wmi_probe(struct platform_device *pdev) From d717e4509af0380a94dbc28b61839df39f17e1eb Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Sat, 5 Feb 2022 12:28:40 +0100 Subject: [PATCH 016/137] platform/x86: asus-wmi: Fix regression when probing for fan curve control The fan curve control patches introduced a regression for at least the TUF FX506 and possibly other TUF series laptops that do not have support for fan curve control. As part of the probing process, asus_wmi_evaluate_method_buf is called to get the factory default fan curve . The WMI management function returns 0 on certain laptops to indicate lack of fan curve control instead of ASUS_WMI_UNSUPPORTED_METHOD. This 0 is transformed to -ENODATA which results in failure when probing. Fixes: 0f0ac158d28f ("platform/x86: asus-wmi: Add support for custom fan curves") Reported-and-tested-by: Abhijeet V Signed-off-by: Hans de Goede Link: https://lore.kernel.org/r/20220205112840.33095-1-hdegoede@redhat.com --- drivers/platform/x86/asus-wmi.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/platform/x86/asus-wmi.c b/drivers/platform/x86/asus-wmi.c index a3b83b22a3b1..2104a2621e50 100644 --- a/drivers/platform/x86/asus-wmi.c +++ b/drivers/platform/x86/asus-wmi.c @@ -2223,7 +2223,7 @@ static int fan_curve_check_present(struct asus_wmi *asus, bool *available, err = fan_curve_get_factory_default(asus, fan_dev); if (err) { - if (err == -ENODEV) + if (err == -ENODEV || err == -ENODATA) return 0; return err; } From 80c78fbeef10121a0fbba3fd2ed333f5de118f6d Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Tue, 1 Feb 2022 17:27:56 +0200 Subject: [PATCH 017/137] gpiolib: Introduce for_each_gpio_desc_with_flag() macro In a few places we are using a loop against all GPIO descriptors with a given flag for a given device. Replace it with a consolidated for_each type of macro. Signed-off-by: Andy Shevchenko Reviewed-by: Linus Walleij Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpiolib-of.c | 10 ++++------ drivers/gpio/gpiolib-sysfs.c | 7 ++----- drivers/gpio/gpiolib.c | 7 +++---- drivers/gpio/gpiolib.h | 7 +++++++ 4 files changed, 16 insertions(+), 15 deletions(-) diff --git a/drivers/gpio/gpiolib-of.c b/drivers/gpio/gpiolib-of.c index 91dcf2c6cdd8..ae1ce319cd78 100644 --- a/drivers/gpio/gpiolib-of.c +++ b/drivers/gpio/gpiolib-of.c @@ -711,14 +711,12 @@ static int of_gpiochip_scan_gpios(struct gpio_chip *chip) static void of_gpiochip_remove_hog(struct gpio_chip *chip, struct device_node *hog) { - struct gpio_desc *descs = chip->gpiodev->descs; + struct gpio_desc *desc; unsigned int i; - for (i = 0; i < chip->ngpio; i++) { - if (test_bit(FLAG_IS_HOGGED, &descs[i].flags) && - descs[i].hog == hog) - gpiochip_free_own_desc(&descs[i]); - } + for_each_gpio_desc_with_flag(i, chip, desc, FLAG_IS_HOGGED) + if (desc->hog == hog) + gpiochip_free_own_desc(desc); } static int of_gpiochip_match_node(struct gpio_chip *chip, void *data) diff --git a/drivers/gpio/gpiolib-sysfs.c b/drivers/gpio/gpiolib-sysfs.c index 4098bc7f88b7..fc9ec6aa2fc2 100644 --- a/drivers/gpio/gpiolib-sysfs.c +++ b/drivers/gpio/gpiolib-sysfs.c @@ -793,11 +793,8 @@ void gpiochip_sysfs_unregister(struct gpio_device *gdev) mutex_unlock(&sysfs_lock); /* unregister gpiod class devices owned by sysfs */ - for (i = 0; i < chip->ngpio; i++) { - desc = &gdev->descs[i]; - if (test_and_clear_bit(FLAG_SYSFS, &desc->flags)) - gpiod_free(desc); - } + for_each_gpio_desc_with_flag(i, chip, desc, FLAG_SYSFS) + gpiod_free(desc); } static int __init gpiolib_sysfs_init(void) diff --git a/drivers/gpio/gpiolib.c b/drivers/gpio/gpiolib.c index 3859911b61e9..e3702bc1b533 100644 --- a/drivers/gpio/gpiolib.c +++ b/drivers/gpio/gpiolib.c @@ -4102,12 +4102,11 @@ int gpiod_hog(struct gpio_desc *desc, const char *name, */ static void gpiochip_free_hogs(struct gpio_chip *gc) { + struct gpio_desc *desc; int id; - for (id = 0; id < gc->ngpio; id++) { - if (test_bit(FLAG_IS_HOGGED, &gc->gpiodev->descs[id].flags)) - gpiochip_free_own_desc(&gc->gpiodev->descs[id]); - } + for_each_gpio_desc_with_flag(id, gc, desc, FLAG_IS_HOGGED) + gpiochip_free_own_desc(desc); } /** diff --git a/drivers/gpio/gpiolib.h b/drivers/gpio/gpiolib.h index 30bc3f80f83e..e1fff54a937d 100644 --- a/drivers/gpio/gpiolib.h +++ b/drivers/gpio/gpiolib.h @@ -82,6 +82,13 @@ struct gpio_array { }; struct gpio_desc *gpiochip_get_desc(struct gpio_chip *gc, unsigned int hwnum); + +#define for_each_gpio_desc_with_flag(i, gc, desc, flag) \ + for (i = 0, desc = gpiochip_get_desc(gc, i); \ + i < gc->ngpio; \ + i++, desc = gpiochip_get_desc(gc, i)) \ + if (!test_bit(flag, &desc->flags)) {} else + int gpiod_get_array_value_complex(bool raw, bool can_sleep, unsigned int array_size, struct gpio_desc **desc_array, From 322b86e7a73042300303844c9bc51bf6995a47ef Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Tue, 1 Feb 2022 17:27:57 +0200 Subject: [PATCH 018/137] gpiolib: Use short form of ternary operator in gpiod_get_index() Instead of repeating first argument for true branch, use short form of the ternary operator, i.e. ?:. While at it, fix a typo in the comment. Signed-off-by: Andy Shevchenko Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpiolib.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/drivers/gpio/gpiolib.c b/drivers/gpio/gpiolib.c index e3702bc1b533..daedf8207173 100644 --- a/drivers/gpio/gpiolib.c +++ b/drivers/gpio/gpiolib.c @@ -3931,19 +3931,18 @@ struct gpio_desc *__must_check gpiod_get_index(struct device *dev, * If a connection label was passed use that, else attempt to use * the device name as label */ - ret = gpiod_request(desc, con_id ? con_id : devname); + ret = gpiod_request(desc, con_id ?: devname); if (ret) { if (ret == -EBUSY && flags & GPIOD_FLAGS_BIT_NONEXCLUSIVE) { /* * This happens when there are several consumers for * the same GPIO line: we just return here without - * further initialization. It is a bit if a hack. + * further initialization. It is a bit of a hack. * This is necessary to support fixed regulators. * * FIXME: Make this more sane and safe. */ - dev_info(dev, "nonexclusive access to GPIO for %s\n", - con_id ? con_id : devname); + dev_info(dev, "nonexclusive access to GPIO for %s\n", con_id ?: devname); return desc; } else { return ERR_PTR(ret); From 6105b2e391879df0ebee20c764bbdd406cb74fb2 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Tue, 1 Feb 2022 17:27:58 +0200 Subject: [PATCH 019/137] gpiolib: Simplify error path in gpiod_get_index() when requesting GPIO Simplify error path in the gpiod_get_index() when requesting a GPIO line by: - checking for error condition first - dropping redundant 'else' As a result, decrease the indentation level for better readability. Signed-off-by: Andy Shevchenko Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpiolib.c | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/drivers/gpio/gpiolib.c b/drivers/gpio/gpiolib.c index daedf8207173..b4d8bf3a1121 100644 --- a/drivers/gpio/gpiolib.c +++ b/drivers/gpio/gpiolib.c @@ -3933,20 +3933,19 @@ struct gpio_desc *__must_check gpiod_get_index(struct device *dev, */ ret = gpiod_request(desc, con_id ?: devname); if (ret) { - if (ret == -EBUSY && flags & GPIOD_FLAGS_BIT_NONEXCLUSIVE) { - /* - * This happens when there are several consumers for - * the same GPIO line: we just return here without - * further initialization. It is a bit of a hack. - * This is necessary to support fixed regulators. - * - * FIXME: Make this more sane and safe. - */ - dev_info(dev, "nonexclusive access to GPIO for %s\n", con_id ?: devname); - return desc; - } else { + if (!(ret == -EBUSY && flags & GPIOD_FLAGS_BIT_NONEXCLUSIVE)) return ERR_PTR(ret); - } + + /* + * This happens when there are several consumers for + * the same GPIO line: we just return here without + * further initialization. It is a bit of a hack. + * This is necessary to support fixed regulators. + * + * FIXME: Make this more sane and safe. + */ + dev_info(dev, "nonexclusive access to GPIO for %s\n", con_id ?: devname); + return desc; } ret = gpiod_configure_flags(desc, con_id, lookupflags, flags); From 4f351edd26758702b0707aaa4c0aa277e4b52f07 Mon Sep 17 00:00:00 2001 From: Peng Fan Date: Mon, 7 Feb 2022 20:25:30 +0800 Subject: [PATCH 020/137] dt-bindings: gpio: gpio-vf610: Add imx93 compatible string Add the compatible string for i.MX93 Signed-off-by: Peng Fan Signed-off-by: Bartosz Golaszewski --- Documentation/devicetree/bindings/gpio/gpio-vf610.yaml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/gpio/gpio-vf610.yaml b/Documentation/devicetree/bindings/gpio/gpio-vf610.yaml index e1359391d3a4..d2c39dba56ad 100644 --- a/Documentation/devicetree/bindings/gpio/gpio-vf610.yaml +++ b/Documentation/devicetree/bindings/gpio/gpio-vf610.yaml @@ -25,7 +25,9 @@ properties: - const: fsl,imx7ulp-gpio - const: fsl,vf610-gpio - items: - - const: fsl,imx8ulp-gpio + - enum: + - fsl,imx93-gpio + - fsl,imx8ulp-gpio - const: fsl,imx7ulp-gpio reg: From dec09a4dc165bb034830337f7fb4ed489d418b35 Mon Sep 17 00:00:00 2001 From: Corentin Labbe Date: Wed, 9 Feb 2022 08:52:58 +0000 Subject: [PATCH 021/137] dt-bindings: gpio: convert faraday,ftgpio01 to yaml Converts gpio/faraday,ftgpio010.txt to yaml. Reviewed-by: Linus Walleij Reviewed-by: Rob Herring Signed-off-by: Corentin Labbe Signed-off-by: Bartosz Golaszewski --- .../bindings/gpio/faraday,ftgpio010.txt | 27 -------- .../bindings/gpio/faraday,ftgpio010.yaml | 65 +++++++++++++++++++ 2 files changed, 65 insertions(+), 27 deletions(-) delete mode 100644 Documentation/devicetree/bindings/gpio/faraday,ftgpio010.txt create mode 100644 Documentation/devicetree/bindings/gpio/faraday,ftgpio010.yaml diff --git a/Documentation/devicetree/bindings/gpio/faraday,ftgpio010.txt b/Documentation/devicetree/bindings/gpio/faraday,ftgpio010.txt deleted file mode 100644 index d04236558619..000000000000 --- a/Documentation/devicetree/bindings/gpio/faraday,ftgpio010.txt +++ /dev/null @@ -1,27 +0,0 @@ -Faraday Technology FTGPIO010 GPIO Controller - -Required properties: - -- compatible : Should be one of - "cortina,gemini-gpio", "faraday,ftgpio010" - "moxa,moxart-gpio", "faraday,ftgpio010" - "faraday,ftgpio010" -- reg : Should contain registers location and length -- interrupts : Should contain the interrupt line for the GPIO block -- gpio-controller : marks this as a GPIO controller -- #gpio-cells : Should be 2, see gpio/gpio.txt -- interrupt-controller : marks this as an interrupt controller -- #interrupt-cells : a standard two-cell interrupt flag, see - interrupt-controller/interrupts.txt - -Example: - -gpio@4d000000 { - compatible = "cortina,gemini-gpio", "faraday,ftgpio010"; - reg = <0x4d000000 0x100>; - interrupts = <22 IRQ_TYPE_LEVEL_HIGH>; - gpio-controller; - #gpio-cells = <2>; - interrupt-controller; - #interrupt-cells = <2>; -}; diff --git a/Documentation/devicetree/bindings/gpio/faraday,ftgpio010.yaml b/Documentation/devicetree/bindings/gpio/faraday,ftgpio010.yaml new file mode 100644 index 000000000000..640da5b9b0cc --- /dev/null +++ b/Documentation/devicetree/bindings/gpio/faraday,ftgpio010.yaml @@ -0,0 +1,65 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/gpio/faraday,ftgpio010.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Faraday Technology FTGPIO010 GPIO Controller + +maintainers: + - Linus Walleij + +properties: + compatible: + oneOf: + - items: + - const: cortina,gemini-gpio + - const: faraday,ftgpio010 + - items: + - const: moxa,moxart-gpio + - const: faraday,ftgpio010 + - const: faraday,ftgpio010 + + reg: + maxItems: 1 + + resets: + maxItems: 1 + + clocks: + maxItems: 1 + + interrupts: + maxItems: 1 + description: Should contain the interrupt line for the GPIO block + + gpio-controller: true + "#gpio-cells": + const: 2 + + interrupt-controller: true + "#interrupt-cells": + const: 2 + +required: + - compatible + - reg + - interrupts + - "#gpio-cells" + - interrupt-controller + - "#interrupt-cells" + +additionalProperties: false + +examples: + - | + #include + gpio@4d000000 { + compatible = "cortina,gemini-gpio", "faraday,ftgpio010"; + reg = <0x4d000000 0x100>; + interrupts = <22 IRQ_TYPE_LEVEL_HIGH>; + gpio-controller; + #gpio-cells = <2>; + interrupt-controller; + #interrupt-cells = <2>; + }; From 4398693a9e24bcab0b99ea219073917991d0792b Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Tue, 8 Feb 2022 11:48:31 +0100 Subject: [PATCH 022/137] gpiolib: make struct comments into real kernel docs We have several comments that start with '/**' but don't conform to the kernel doc standard. Add proper detailed descriptions for the affected definitions and move the docs from the forward declarations to the struct definitions where applicable. Reported-by: Randy Dunlap Signed-off-by: Bartosz Golaszewski Reviewed-by: Andy Shevchenko Tested-by: Randy Dunlap --- drivers/gpio/gpiolib.h | 34 ++++++++++++++++++++++++++++++++++ include/linux/gpio/consumer.h | 35 ++++++++++++++++------------------- 2 files changed, 50 insertions(+), 19 deletions(-) diff --git a/drivers/gpio/gpiolib.h b/drivers/gpio/gpiolib.h index e1fff54a937d..9380b62e152c 100644 --- a/drivers/gpio/gpiolib.h +++ b/drivers/gpio/gpiolib.h @@ -37,6 +37,9 @@ * or name of the IP component in a System on Chip. * @data: per-instance data assigned by the driver * @list: links gpio_device:s together for traversal + * @notifier: used to notify subscribers about lines being requested, released + * or reconfigured + * @pin_ranges: range of pins served by the GPIO driver * * This state container holds most of the runtime variable data * for a GPIO device and can hold references and live on after the @@ -72,6 +75,20 @@ struct gpio_device { /* gpio suffixes used for ACPI and device tree lookup */ static __maybe_unused const char * const gpio_suffixes[] = { "gpios", "gpio" }; +/** + * struct gpio_array - Opaque descriptor for a structure of GPIO array attributes + * + * @desc: Array of pointers to the GPIO descriptors + * @size: Number of elements in desc + * @chip: Parent GPIO chip + * @get_mask: Get mask used in fastpath + * @set_mask: Set mask used in fastpath + * @invert_mask: Invert mask used in fastpath + * + * This structure is attached to struct gpiod_descs obtained from + * gpiod_get_array() and can be passed back to get/set array functions in order + * to activate fast processing path if applicable. + */ struct gpio_array { struct gpio_desc **desc; unsigned int size; @@ -103,6 +120,23 @@ int gpiod_set_array_value_complex(bool raw, bool can_sleep, extern spinlock_t gpio_lock; extern struct list_head gpio_devices; + +/** + * struct gpio_desc - Opaque descriptor for a GPIO + * + * @gdev: Pointer to the parent GPIO device + * @flags: Binary descriptor flags + * @label: Name of the consumer + * @name: Line name + * @hog: Pointer to the device node that hogs this line (if any) + * @debounce_period_us: Debounce period in microseconds + * + * These are obtained using gpiod_get() and are preferable to the old + * integer-based handles. + * + * Contrary to integers, a pointer to a &struct gpio_desc is guaranteed to be + * valid until the GPIO is released. + */ struct gpio_desc { struct gpio_device *gdev; unsigned long flags; diff --git a/include/linux/gpio/consumer.h b/include/linux/gpio/consumer.h index 3ad67b4a72be..c3aa8b330e1c 100644 --- a/include/linux/gpio/consumer.h +++ b/include/linux/gpio/consumer.h @@ -8,27 +8,16 @@ #include struct device; - -/** - * Opaque descriptor for a GPIO. These are obtained using gpiod_get() and are - * preferable to the old integer-based handles. - * - * Contrary to integers, a pointer to a gpio_desc is guaranteed to be valid - * until the GPIO is released. - */ struct gpio_desc; - -/** - * Opaque descriptor for a structure of GPIO array attributes. This structure - * is attached to struct gpiod_descs obtained from gpiod_get_array() and can be - * passed back to get/set array functions in order to activate fast processing - * path if applicable. - */ struct gpio_array; /** - * Struct containing an array of descriptors that can be obtained using - * gpiod_get_array(). + * struct gpio_descs - Struct containing an array of descriptors that can be + * obtained using gpiod_get_array() + * + * @info: Pointer to the opaque gpio_array structure + * @ndescs: Number of held descriptors + * @desc: Array of pointers to GPIO descriptors */ struct gpio_descs { struct gpio_array *info; @@ -43,8 +32,16 @@ struct gpio_descs { #define GPIOD_FLAGS_BIT_NONEXCLUSIVE BIT(4) /** - * Optional flags that can be passed to one of gpiod_* to configure direction - * and output value. These values cannot be OR'd. + * enum gpiod_flags - Optional flags that can be passed to one of gpiod_* to + * configure direction and output value. These values + * cannot be OR'd. + * + * @GPIOD_ASIS: Don't change anything + * @GPIOD_IN: Set lines to input mode + * @GPIOD_OUT_LOW: Set lines to output and drive them low + * @GPIOD_OUT_HIGH: Set lines to output and drive them high + * @GPIOD_OUT_LOW_OPEN_DRAIN: Set lines to open-drain output and drive them low + * @GPIOD_OUT_HIGH_OPEN_DRAIN: Set lines to open-drain output and drive them high */ enum gpiod_flags { GPIOD_ASIS = 0, From 5c8e4c8662fff4f50bd4fdb9512164d57550132f Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Sat, 5 Feb 2022 20:13:51 +0100 Subject: [PATCH 023/137] platform/x86: x86-android-tablets: Add battery swnode support power_supply_get_battery_info() which is used by charger and fuel-gauge drivers on x86-android-tablets, expects the battery properties to be described in a stand-alone battery fwnode which is then referenced from both the charger and fuel-gauge device's fwnodes. Add support for registering + unregistering a swnode for this. Signed-off-by: Hans de Goede Reviewed-by: Andy Shevchenko Link: https://lore.kernel.org/r/20220205191356.225505-1-hdegoede@redhat.com --- drivers/platform/x86/x86-android-tablets.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/drivers/platform/x86/x86-android-tablets.c b/drivers/platform/x86/x86-android-tablets.c index 3120acf9837c..80d113c41623 100644 --- a/drivers/platform/x86/x86-android-tablets.c +++ b/drivers/platform/x86/x86-android-tablets.c @@ -146,6 +146,7 @@ struct x86_serdev_info { struct x86_dev_info { char *invalid_aei_gpiochip; const char * const *modules; + const struct software_node *bat_swnode; struct gpiod_lookup_table * const *gpiod_lookup_tables; const struct x86_i2c_client_info *i2c_client_info; const struct platform_device_info *pdev_info; @@ -727,6 +728,7 @@ static struct i2c_client **i2c_clients; static struct platform_device **pdevs; static struct serdev_device **serdevs; static struct gpiod_lookup_table * const *gpiod_lookup_tables; +static const struct software_node *bat_swnode; static void (*exit_handler)(void); static __init int x86_instantiate_i2c_client(const struct x86_dev_info *dev_info, @@ -850,6 +852,8 @@ static void x86_android_tablet_cleanup(void) for (i = 0; gpiod_lookup_tables && gpiod_lookup_tables[i]; i++) gpiod_remove_lookup_table(gpiod_lookup_tables[i]); + + software_node_unregister(bat_swnode); } static __init int x86_android_tablet_init(void) @@ -886,6 +890,13 @@ static __init int x86_android_tablet_init(void) for (i = 0; dev_info->modules && dev_info->modules[i]; i++) request_module(dev_info->modules[i]); + bat_swnode = dev_info->bat_swnode; + if (bat_swnode) { + ret = software_node_register(bat_swnode); + if (ret) + return ret; + } + gpiod_lookup_tables = dev_info->gpiod_lookup_tables; for (i = 0; gpiod_lookup_tables && gpiod_lookup_tables[i]; i++) gpiod_add_lookup_table(gpiod_lookup_tables[i]); From de7601a681bf74ea0ce025f0461e9452afeaeead Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Sat, 5 Feb 2022 20:13:52 +0100 Subject: [PATCH 024/137] platform/x86: x86-android-tablets: Add Asus ME176C/TF103C charger and fuelgauge props Add properties describing the battery on the Asus ME176C / TF103C tablets. The max constant charge volt / current settings were taken from the factory Android image on these tablets. Signed-off-by: Hans de Goede Reviewed-by: Andy Shevchenko Link: https://lore.kernel.org/r/20220205191356.225505-2-hdegoede@redhat.com --- drivers/platform/x86/x86-android-tablets.c | 95 +++++++++++++++++++--- 1 file changed, 84 insertions(+), 11 deletions(-) diff --git a/drivers/platform/x86/x86-android-tablets.c b/drivers/platform/x86/x86-android-tablets.c index 80d113c41623..45a951002401 100644 --- a/drivers/platform/x86/x86-android-tablets.c +++ b/drivers/platform/x86/x86-android-tablets.c @@ -158,21 +158,27 @@ struct x86_dev_info { void (*exit)(void); }; -/* Generic / shared bq24190 settings */ -static const char * const bq24190_suppliers[] = { "tusb1210-psy" }; +/* Generic / shared charger / battery settings */ +static const char * const bq24190_suppliers[] = { "tusb1211-charger-detect" }; +static const char * const ug3105_suppliers[] = { "bq24190-charger" }; -static const struct property_entry bq24190_props[] = { - PROPERTY_ENTRY_STRING_ARRAY("supplied-from", bq24190_suppliers), - PROPERTY_ENTRY_BOOL("omit-battery-class"), - PROPERTY_ENTRY_BOOL("disable-reset"), +/* LiPo HighVoltage (max 4.35V) settings used by most devs with a HV bat. */ +static const struct property_entry generic_lipo_hv_4v35_battery_props[] = { + PROPERTY_ENTRY_STRING("compatible", "simple-battery"), + PROPERTY_ENTRY_STRING("device-chemistry", "lithium-ion"), + PROPERTY_ENTRY_U32("precharge-current-microamp", 256000), + PROPERTY_ENTRY_U32("charge-term-current-microamp", 128000), + PROPERTY_ENTRY_U32("constant-charge-current-max-microamp", 1856000), + PROPERTY_ENTRY_U32("constant-charge-voltage-max-microvolt", 4352000), + PROPERTY_ENTRY_U32("factory-internal-resistance-micro-ohms", 150000), { } }; -static const struct software_node bq24190_node = { - .properties = bq24190_props, +static const struct software_node generic_lipo_hv_4v35_battery_node = { + .properties = generic_lipo_hv_4v35_battery_props, }; -/* For enableing the bq24190 5V boost based on id-pin */ +/* For enabling the bq24190 5V boost based on id-pin */ static struct regulator_consumer_supply intel_int3496_consumer = { .supply = "vbus", .dev_name = "intel-int3496", @@ -230,6 +236,30 @@ static const struct software_node asus_me176c_accel_node = { .properties = asus_me176c_accel_props, }; +static const struct property_entry asus_me176c_bq24190_props[] = { + PROPERTY_ENTRY_STRING_ARRAY("supplied-from", bq24190_suppliers), + PROPERTY_ENTRY_REF("monitored-battery", &generic_lipo_hv_4v35_battery_node), + PROPERTY_ENTRY_U32("ti,system-minimum-microvolt", 3600000), + PROPERTY_ENTRY_BOOL("omit-battery-class"), + PROPERTY_ENTRY_BOOL("disable-reset"), + { } +}; + +static const struct software_node asus_me176c_bq24190_node = { + .properties = asus_me176c_bq24190_props, +}; + +static const struct property_entry asus_me176c_ug3105_props[] = { + PROPERTY_ENTRY_STRING_ARRAY("supplied-from", ug3105_suppliers), + PROPERTY_ENTRY_REF("monitored-battery", &generic_lipo_hv_4v35_battery_node), + PROPERTY_ENTRY_U32("upi,rsns-microohm", 10000), + { } +}; + +static const struct software_node asus_me176c_ug3105_node = { + .properties = asus_me176c_ug3105_props, +}; + static const struct x86_i2c_client_info asus_me176c_i2c_clients[] __initconst = { { /* bq24190 battery charger */ @@ -237,7 +267,7 @@ static const struct x86_i2c_client_info asus_me176c_i2c_clients[] __initconst = .type = "bq24190", .addr = 0x6b, .dev_name = "bq24190", - .swnode = &bq24190_node, + .swnode = &asus_me176c_bq24190_node, .platform_data = &bq24190_pdata, }, .adapter_path = "\\_SB_.I2C1", @@ -253,6 +283,7 @@ static const struct x86_i2c_client_info asus_me176c_i2c_clients[] __initconst = .type = "ug3105", .addr = 0x70, .dev_name = "ug3105", + .swnode = &asus_me176c_ug3105_node, }, .adapter_path = "\\_SB_.I2C1", }, { @@ -321,6 +352,7 @@ static const struct x86_dev_info asus_me176c_info __initconst = { .serdev_info = asus_me176c_serdevs, .serdev_count = ARRAY_SIZE(asus_me176c_serdevs), .gpiod_lookup_tables = asus_me176c_gpios, + .bat_swnode = &generic_lipo_hv_4v35_battery_node, .modules = bq24190_modules, .invalid_aei_gpiochip = "INT33FC:02", }; @@ -350,6 +382,45 @@ static const struct software_node asus_tf103c_touchscreen_node = { .properties = asus_tf103c_touchscreen_props, }; +static const struct property_entry asus_tf103c_battery_props[] = { + PROPERTY_ENTRY_STRING("compatible", "simple-battery"), + PROPERTY_ENTRY_STRING("device-chemistry", "lithium-ion-polymer"), + PROPERTY_ENTRY_U32("precharge-current-microamp", 256000), + PROPERTY_ENTRY_U32("charge-term-current-microamp", 128000), + PROPERTY_ENTRY_U32("constant-charge-current-max-microamp", 2048000), + PROPERTY_ENTRY_U32("constant-charge-voltage-max-microvolt", 4208000), + PROPERTY_ENTRY_U32("factory-internal-resistance-micro-ohms", 150000), + { } +}; + +static const struct software_node asus_tf103c_battery_node = { + .properties = asus_tf103c_battery_props, +}; + +static const struct property_entry asus_tf103c_bq24190_props[] = { + PROPERTY_ENTRY_STRING_ARRAY("supplied-from", bq24190_suppliers), + PROPERTY_ENTRY_REF("monitored-battery", &asus_tf103c_battery_node), + PROPERTY_ENTRY_U32("ti,system-minimum-microvolt", 3600000), + PROPERTY_ENTRY_BOOL("omit-battery-class"), + PROPERTY_ENTRY_BOOL("disable-reset"), + { } +}; + +static const struct software_node asus_tf103c_bq24190_node = { + .properties = asus_tf103c_bq24190_props, +}; + +static const struct property_entry asus_tf103c_ug3105_props[] = { + PROPERTY_ENTRY_STRING_ARRAY("supplied-from", ug3105_suppliers), + PROPERTY_ENTRY_REF("monitored-battery", &asus_tf103c_battery_node), + PROPERTY_ENTRY_U32("upi,rsns-microohm", 5000), + { } +}; + +static const struct software_node asus_tf103c_ug3105_node = { + .properties = asus_tf103c_ug3105_props, +}; + static const struct x86_i2c_client_info asus_tf103c_i2c_clients[] __initconst = { { /* bq24190 battery charger */ @@ -357,7 +428,7 @@ static const struct x86_i2c_client_info asus_tf103c_i2c_clients[] __initconst = .type = "bq24190", .addr = 0x6b, .dev_name = "bq24190", - .swnode = &bq24190_node, + .swnode = &asus_tf103c_bq24190_node, .platform_data = &bq24190_pdata, }, .adapter_path = "\\_SB_.I2C1", @@ -373,6 +444,7 @@ static const struct x86_i2c_client_info asus_tf103c_i2c_clients[] __initconst = .type = "ug3105", .addr = 0x70, .dev_name = "ug3105", + .swnode = &asus_tf103c_ug3105_node, }, .adapter_path = "\\_SB_.I2C1", }, { @@ -422,6 +494,7 @@ static const struct x86_dev_info asus_tf103c_info __initconst = { .pdev_info = int3496_pdevs, .pdev_count = ARRAY_SIZE(int3496_pdevs), .gpiod_lookup_tables = asus_tf103c_gpios, + .bat_swnode = &asus_tf103c_battery_node, .modules = bq24190_modules, .invalid_aei_gpiochip = "INT33FC:02", }; From 381d785d9eefd2516985f662c71fde1f028b014d Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Sat, 5 Feb 2022 20:13:53 +0100 Subject: [PATCH 025/137] platform/x86: x86-android-tablets: Add x86_android_tablet_get_gpiod() helper Factor the code to go from a gpiochip label + pin-numer to a gpio_desc out of x86_acpi_irq_helper_get() and make it into a new x86_android_tablet_get_gpiod() helper, as this will be necessary in some x86_dev_info.init() functions too. Signed-off-by: Hans de Goede Reviewed-by: Andy Shevchenko Link: https://lore.kernel.org/r/20220205191356.225505-3-hdegoede@redhat.com --- drivers/platform/x86/x86-android-tablets.c | 35 ++++++++++++++-------- 1 file changed, 23 insertions(+), 12 deletions(-) diff --git a/drivers/platform/x86/x86-android-tablets.c b/drivers/platform/x86/x86-android-tablets.c index 45a951002401..40243ea3ae39 100644 --- a/drivers/platform/x86/x86-android-tablets.c +++ b/drivers/platform/x86/x86-android-tablets.c @@ -53,13 +53,33 @@ static int gpiochip_find_match_label(struct gpio_chip *gc, void *data) return gc->label && !strcmp(gc->label, data); } +static int x86_android_tablet_get_gpiod(char *label, int pin, struct gpio_desc **desc) +{ + struct gpio_desc *gpiod; + struct gpio_chip *chip; + + chip = gpiochip_find(label, gpiochip_find_match_label); + if (!chip) { + pr_err("error cannot find GPIO chip %s\n", label); + return -ENODEV; + } + + gpiod = gpiochip_get_desc(chip, pin); + if (IS_ERR(gpiod)) { + pr_err("error %ld getting GPIO %s %d\n", PTR_ERR(gpiod), label, pin); + return PTR_ERR(gpiod); + } + + *desc = gpiod; + return 0; +} + static int x86_acpi_irq_helper_get(const struct x86_acpi_irq_data *data) { struct irq_fwspec fwspec = { }; struct irq_domain *domain; struct acpi_device *adev; struct gpio_desc *gpiod; - struct gpio_chip *chip; unsigned int irq_type; acpi_handle handle; acpi_status status; @@ -74,18 +94,9 @@ static int x86_acpi_irq_helper_get(const struct x86_acpi_irq_data *data) return irq; case X86_ACPI_IRQ_TYPE_GPIOINT: /* Like acpi_dev_gpio_irq_get(), but without parsing ACPI resources */ - chip = gpiochip_find(data->chip, gpiochip_find_match_label); - if (!chip) { - pr_err("error cannot find GPIO chip %s\n", data->chip); - return -ENODEV; - } - - gpiod = gpiochip_get_desc(chip, data->index); - if (IS_ERR(gpiod)) { - ret = PTR_ERR(gpiod); - pr_err("error %d getting GPIO %s %d\n", ret, data->chip, data->index); + ret = x86_android_tablet_get_gpiod(data->chip, data->index, &gpiod); + if (ret) return ret; - } irq = gpiod_to_irq(gpiod); if (irq < 0) { From 66d1d6665a6166078112d4fbbf76153080b2e4d9 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Sat, 5 Feb 2022 20:13:54 +0100 Subject: [PATCH 026/137] platform/x86: x86-android-tablets: Add lid-switch gpio-keys pdev to Asus ME176C + TF103C The Asus ME176C + TF103C both have a lid-switch (for a cover in the ME176C case), add a gpio-keys platform-device and platform-data describing the lid-switch on both. Note the "intel-int3496" in the asus_me176c_tf103c_pdevs[] array is not new / not a change. This was already present in the generic int3496_pdevs[] array, to which pdev_info pointed before. The int3496_pdevs[] array contains just this entry for boards which only need that single pdev. Signed-off-by: Hans de Goede Reviewed-by: Andy Shevchenko Link: https://lore.kernel.org/r/20220205191356.225505-4-hdegoede@redhat.com --- drivers/platform/x86/x86-android-tablets.c | 57 ++++++++++++++++++++-- 1 file changed, 53 insertions(+), 4 deletions(-) diff --git a/drivers/platform/x86/x86-android-tablets.c b/drivers/platform/x86/x86-android-tablets.c index 40243ea3ae39..6174aad572bc 100644 --- a/drivers/platform/x86/x86-android-tablets.c +++ b/drivers/platform/x86/x86-android-tablets.c @@ -12,10 +12,12 @@ #include #include +#include #include #include #include #include +#include #include #include #include @@ -231,6 +233,51 @@ static struct gpiod_lookup_table int3496_gpo2_pin22_gpios = { }, }; +/* Asus ME176C and TF103C tablets shared data */ +static struct gpio_keys_button asus_me176c_tf103c_lid = { + .code = SW_LID, + /* .gpio gets filled in by asus_me176c_tf103c_init() */ + .active_low = true, + .desc = "lid_sw", + .type = EV_SW, + .wakeup = true, + .debounce_interval = 50, +}; + +static const struct gpio_keys_platform_data asus_me176c_tf103c_lid_pdata __initconst = { + .buttons = &asus_me176c_tf103c_lid, + .nbuttons = 1, + .name = "lid_sw", +}; + +static const struct platform_device_info asus_me176c_tf103c_pdevs[] __initconst = { + { + .name = "gpio-keys", + .id = PLATFORM_DEVID_AUTO, + .data = &asus_me176c_tf103c_lid_pdata, + .size_data = sizeof(asus_me176c_tf103c_lid_pdata), + }, + { + /* For micro USB ID pin handling */ + .name = "intel-int3496", + .id = PLATFORM_DEVID_NONE, + }, +}; + +static int __init asus_me176c_tf103c_init(void) +{ + struct gpio_desc *gpiod; + int ret; + + ret = x86_android_tablet_get_gpiod("INT33FC:02", 12, &gpiod); + if (ret < 0) + return ret; + asus_me176c_tf103c_lid.gpio = desc_to_gpio(gpiod); + + return 0; +} + + /* Asus ME176C tablets have an Android factory img with everything hardcoded */ static const char * const asus_me176c_accel_mount_matrix[] = { "-1", "0", "0", @@ -358,14 +405,15 @@ static struct gpiod_lookup_table * const asus_me176c_gpios[] = { static const struct x86_dev_info asus_me176c_info __initconst = { .i2c_client_info = asus_me176c_i2c_clients, .i2c_client_count = ARRAY_SIZE(asus_me176c_i2c_clients), - .pdev_info = int3496_pdevs, - .pdev_count = ARRAY_SIZE(int3496_pdevs), + .pdev_info = asus_me176c_tf103c_pdevs, + .pdev_count = ARRAY_SIZE(asus_me176c_tf103c_pdevs), .serdev_info = asus_me176c_serdevs, .serdev_count = ARRAY_SIZE(asus_me176c_serdevs), .gpiod_lookup_tables = asus_me176c_gpios, .bat_swnode = &generic_lipo_hv_4v35_battery_node, .modules = bq24190_modules, .invalid_aei_gpiochip = "INT33FC:02", + .init = asus_me176c_tf103c_init, }; /* Asus TF103C tablets have an Android factory img with everything hardcoded */ @@ -502,12 +550,13 @@ static struct gpiod_lookup_table * const asus_tf103c_gpios[] = { static const struct x86_dev_info asus_tf103c_info __initconst = { .i2c_client_info = asus_tf103c_i2c_clients, .i2c_client_count = ARRAY_SIZE(asus_tf103c_i2c_clients), - .pdev_info = int3496_pdevs, - .pdev_count = ARRAY_SIZE(int3496_pdevs), + .pdev_info = asus_me176c_tf103c_pdevs, + .pdev_count = ARRAY_SIZE(asus_me176c_tf103c_pdevs), .gpiod_lookup_tables = asus_tf103c_gpios, .bat_swnode = &asus_tf103c_battery_node, .modules = bq24190_modules, .invalid_aei_gpiochip = "INT33FC:02", + .init = asus_me176c_tf103c_init, }; /* From fdac7c8a6ee5662fc7cd27f939984f8f91eadfbf Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Sat, 5 Feb 2022 20:13:55 +0100 Subject: [PATCH 027/137] platform/x86: x86-android-tablets: Add IRQ to Asus ME176C accelerometer info Add the IRQ for the accelerometer to the Asus ME176C board info. Signed-off-by: Hans de Goede Reviewed-by: Andy Shevchenko Link: https://lore.kernel.org/r/20220205191356.225505-5-hdegoede@redhat.com --- drivers/platform/x86/x86-android-tablets.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/platform/x86/x86-android-tablets.c b/drivers/platform/x86/x86-android-tablets.c index 6174aad572bc..6d3a453d90eb 100644 --- a/drivers/platform/x86/x86-android-tablets.c +++ b/drivers/platform/x86/x86-android-tablets.c @@ -361,6 +361,12 @@ static const struct x86_i2c_client_info asus_me176c_i2c_clients[] __initconst = .swnode = &asus_me176c_accel_node, }, .adapter_path = "\\_SB_.I2C5", + .irq_data = { + .type = X86_ACPI_IRQ_TYPE_APIC, + .index = 0x44, + .trigger = ACPI_EDGE_SENSITIVE, + .polarity = ACPI_ACTIVE_LOW, + }, }, { /* goodix touchscreen */ .board_info = { From 09dd99dd201fb050148c773944a219fee332e361 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Sat, 5 Feb 2022 20:13:56 +0100 Subject: [PATCH 028/137] platform/x86: x86-android-tablets: Add Nextbook Ares 8 data The Nextbook Ares 8 is a x86 ACPI tablet which ships with Android x86 as factory OS. Its DSDT contains a bunch of I2C devices which are not actually there, causing various resource conflicts. Enumeration of these is skipped through the acpi_quirk_skip_i2c_client_enumeration(). Add support for manually instantiating the I2C devices which are actually present on this tablet by adding the necessary device info to the x86-android-tablets module. Signed-off-by: Hans de Goede Reviewed-by: Andy Shevchenko Link: https://lore.kernel.org/r/20220205191356.225505-6-hdegoede@redhat.com --- drivers/platform/x86/x86-android-tablets.c | 88 +++++++++++++++++++++- 1 file changed, 87 insertions(+), 1 deletion(-) diff --git a/drivers/platform/x86/x86-android-tablets.c b/drivers/platform/x86/x86-android-tablets.c index 6d3a453d90eb..838d667126e5 100644 --- a/drivers/platform/x86/x86-android-tablets.c +++ b/drivers/platform/x86/x86-android-tablets.c @@ -669,6 +669,84 @@ static const struct x86_dev_info czc_p10t __initconst = { .init = czc_p10t_init, }; +/* Nextbook Ares 8 tablets have an Android factory img with everything hardcoded */ +static const char * const nextbook_ares8_accel_mount_matrix[] = { + "0", "-1", "0", + "-1", "0", "0", + "0", "0", "1" +}; + +static const struct property_entry nextbook_ares8_accel_props[] = { + PROPERTY_ENTRY_STRING_ARRAY("mount-matrix", nextbook_ares8_accel_mount_matrix), + { } +}; + +static const struct software_node nextbook_ares8_accel_node = { + .properties = nextbook_ares8_accel_props, +}; + +static const struct property_entry nextbook_ares8_touchscreen_props[] = { + PROPERTY_ENTRY_U32("touchscreen-size-x", 800), + PROPERTY_ENTRY_U32("touchscreen-size-y", 1280), + { } +}; + +static const struct software_node nextbook_ares8_touchscreen_node = { + .properties = nextbook_ares8_touchscreen_props, +}; + +static const struct x86_i2c_client_info nextbook_ares8_i2c_clients[] __initconst = { + { + /* Freescale MMA8653FC accel */ + .board_info = { + .type = "mma8653", + .addr = 0x1d, + .dev_name = "mma8653", + .swnode = &nextbook_ares8_accel_node, + }, + .adapter_path = "\\_SB_.I2C3", + }, { + /* FT5416DQ9 touchscreen controller */ + .board_info = { + .type = "edt-ft5x06", + .addr = 0x38, + .dev_name = "ft5416", + .swnode = &nextbook_ares8_touchscreen_node, + }, + .adapter_path = "\\_SB_.I2C4", + .irq_data = { + .type = X86_ACPI_IRQ_TYPE_GPIOINT, + .chip = "INT33FC:02", + .index = 3, + .trigger = ACPI_EDGE_SENSITIVE, + .polarity = ACPI_ACTIVE_LOW, + }, + }, +}; + +static struct gpiod_lookup_table nextbook_ares8_int3496_gpios = { + .dev_id = "intel-int3496", + .table = { + GPIO_LOOKUP("INT33FC:02", 1, "mux", GPIO_ACTIVE_HIGH), + GPIO_LOOKUP("INT33FC:02", 18, "id", GPIO_ACTIVE_HIGH), + { } + }, +}; + +static struct gpiod_lookup_table * const nextbook_ares8_gpios[] = { + &nextbook_ares8_int3496_gpios, + NULL +}; + +static const struct x86_dev_info nextbook_ares8_info __initconst = { + .i2c_client_info = nextbook_ares8_i2c_clients, + .i2c_client_count = ARRAY_SIZE(nextbook_ares8_i2c_clients), + .pdev_info = int3496_pdevs, + .pdev_count = ARRAY_SIZE(int3496_pdevs), + .gpiod_lookup_tables = nextbook_ares8_gpios, + .invalid_aei_gpiochip = "INT33FC:02", +}; + /* * Whitelabel (sold as various brands) TM800A550L tablets. * These tablet's DSDT contains a whole bunch of bogus ACPI I2C devices @@ -830,7 +908,7 @@ static const struct dmi_system_id x86_android_tablet_ids[] __initconst = { .driver_data = (void *)&czc_p10t, }, { - /* A variant of CZC P10T */ + /* CZC P10T variant */ .ident = "ViewSonic ViewPad 10", .matches = { DMI_MATCH(DMI_SYS_VENDOR, "ViewSonic"), @@ -838,6 +916,14 @@ static const struct dmi_system_id x86_android_tablet_ids[] __initconst = { }, .driver_data = (void *)&czc_p10t, }, + { + /* Nextbook Ares 8 */ + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Insyde"), + DMI_MATCH(DMI_PRODUCT_NAME, "M890BAP"), + }, + .driver_data = (void *)&nextbook_ares8_info, + }, { /* Whitelabel (sold as various brands) TM800A550L */ .matches = { From 5030e8d9ff0d58e6c0fa3b6447b653e192536a5f Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Sun, 6 Feb 2022 23:02:17 +0100 Subject: [PATCH 029/137] platform/x86: x86-android-tablets: Minor charger / fuel-gauge improvements Minor charger / fuel-gauge improvements: 1. Make some of the names of charger / fuel-gauge related globals more generic in preparation for also using them on other boards. 2. Update the dev_name on the Asus ME176C and TF103C to reflect that these are using the bq24297 variant of the bq24190 family. 3. During review of the ug3105 driver the "upi,rsns-microohm" property was renamed to "upisemi,rsns-microohm" as "upisemi" is the correct vendor prefix, update the ug3105 properties accordingly. Signed-off-by: Hans de Goede Link: https://lore.kernel.org/r/20220206220220.88491-1-hdegoede@redhat.com --- drivers/platform/x86/x86-android-tablets.c | 47 +++++++++++----------- 1 file changed, 23 insertions(+), 24 deletions(-) diff --git a/drivers/platform/x86/x86-android-tablets.c b/drivers/platform/x86/x86-android-tablets.c index 838d667126e5..8e6e4f89220f 100644 --- a/drivers/platform/x86/x86-android-tablets.c +++ b/drivers/platform/x86/x86-android-tablets.c @@ -172,8 +172,18 @@ struct x86_dev_info { }; /* Generic / shared charger / battery settings */ -static const char * const bq24190_suppliers[] = { "tusb1211-charger-detect" }; -static const char * const ug3105_suppliers[] = { "bq24190-charger" }; +static const char * const tusb1211_chg_det_psy[] = { "tusb1211-charger-detect" }; +static const char * const bq24190_psy[] = { "bq24190-charger" }; +static const char * const bq25890_psy[] = { "bq25890-charger" }; + +static const struct property_entry fg_bq25890_supply_props[] = { + PROPERTY_ENTRY_STRING_ARRAY("supplied-from", bq25890_psy), + { } +}; + +static const struct software_node fg_bq25890_supply_node = { + .properties = fg_bq25890_supply_props, +}; /* LiPo HighVoltage (max 4.35V) settings used by most devs with a HV bat. */ static const struct property_entry generic_lipo_hv_4v35_battery_props[] = { @@ -295,7 +305,7 @@ static const struct software_node asus_me176c_accel_node = { }; static const struct property_entry asus_me176c_bq24190_props[] = { - PROPERTY_ENTRY_STRING_ARRAY("supplied-from", bq24190_suppliers), + PROPERTY_ENTRY_STRING_ARRAY("supplied-from", tusb1211_chg_det_psy), PROPERTY_ENTRY_REF("monitored-battery", &generic_lipo_hv_4v35_battery_node), PROPERTY_ENTRY_U32("ti,system-minimum-microvolt", 3600000), PROPERTY_ENTRY_BOOL("omit-battery-class"), @@ -308,9 +318,9 @@ static const struct software_node asus_me176c_bq24190_node = { }; static const struct property_entry asus_me176c_ug3105_props[] = { - PROPERTY_ENTRY_STRING_ARRAY("supplied-from", ug3105_suppliers), + PROPERTY_ENTRY_STRING_ARRAY("supplied-from", bq24190_psy), PROPERTY_ENTRY_REF("monitored-battery", &generic_lipo_hv_4v35_battery_node), - PROPERTY_ENTRY_U32("upi,rsns-microohm", 10000), + PROPERTY_ENTRY_U32("upisemi,rsns-microohm", 10000), { } }; @@ -320,11 +330,11 @@ static const struct software_node asus_me176c_ug3105_node = { static const struct x86_i2c_client_info asus_me176c_i2c_clients[] __initconst = { { - /* bq24190 battery charger */ + /* bq24297 battery charger */ .board_info = { .type = "bq24190", .addr = 0x6b, - .dev_name = "bq24190", + .dev_name = "bq24297", .swnode = &asus_me176c_bq24190_node, .platform_data = &bq24190_pdata, }, @@ -463,7 +473,7 @@ static const struct software_node asus_tf103c_battery_node = { }; static const struct property_entry asus_tf103c_bq24190_props[] = { - PROPERTY_ENTRY_STRING_ARRAY("supplied-from", bq24190_suppliers), + PROPERTY_ENTRY_STRING_ARRAY("supplied-from", tusb1211_chg_det_psy), PROPERTY_ENTRY_REF("monitored-battery", &asus_tf103c_battery_node), PROPERTY_ENTRY_U32("ti,system-minimum-microvolt", 3600000), PROPERTY_ENTRY_BOOL("omit-battery-class"), @@ -476,9 +486,9 @@ static const struct software_node asus_tf103c_bq24190_node = { }; static const struct property_entry asus_tf103c_ug3105_props[] = { - PROPERTY_ENTRY_STRING_ARRAY("supplied-from", ug3105_suppliers), + PROPERTY_ENTRY_STRING_ARRAY("supplied-from", bq24190_psy), PROPERTY_ENTRY_REF("monitored-battery", &asus_tf103c_battery_node), - PROPERTY_ENTRY_U32("upi,rsns-microohm", 5000), + PROPERTY_ENTRY_U32("upisemi,rsns-microohm", 5000), { } }; @@ -488,11 +498,11 @@ static const struct software_node asus_tf103c_ug3105_node = { static const struct x86_i2c_client_info asus_tf103c_i2c_clients[] __initconst = { { - /* bq24190 battery charger */ + /* bq24297 battery charger */ .board_info = { .type = "bq24190", .addr = 0x6b, - .dev_name = "bq24190", + .dev_name = "bq24297", .swnode = &asus_tf103c_bq24190_node, .platform_data = &bq24190_pdata, }, @@ -834,17 +844,6 @@ static const struct x86_dev_info whitelabel_tm800a550l_info __initconst = { * * This takes care of instantiating the hidden devices manually. */ -static const char * const bq27520_suppliers[] = { "bq25890-charger" }; - -static const struct property_entry bq27520_props[] = { - PROPERTY_ENTRY_STRING_ARRAY("supplied-from", bq27520_suppliers), - { } -}; - -static const struct software_node bq27520_node = { - .properties = bq27520_props, -}; - static const struct x86_i2c_client_info xiaomi_mipad2_i2c_clients[] __initconst = { { /* BQ27520 fuel-gauge */ @@ -852,7 +851,7 @@ static const struct x86_i2c_client_info xiaomi_mipad2_i2c_clients[] __initconst .type = "bq27520", .addr = 0x55, .dev_name = "bq27520", - .swnode = &bq27520_node, + .swnode = &fg_bq25890_supply_node, }, .adapter_path = "\\_SB_.PCI0.I2C1", }, { From 915623a80b5a0e4f214197519740c0faa297f6d8 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Sun, 6 Feb 2022 23:02:18 +0100 Subject: [PATCH 030/137] platform/x86: intel_cht_int33fe: Switch to DMI modalias based loading The intel_cht_int33fe driver is intended to deal with ACPI INT33FE firmware-nodes on Cherry Trail devices with a Whiskey Cove PMIC. The original version of the driver only dealt with the GPD win and GPD pocket boards where the WC PMIC is connected to a TI BQ24292i charger, paired with a Maxim MAX17047 fuelgauge + a FUSB302 USB Type-C Controller + a PI3USB30532 USB switch, for a fully functional Type-C port. Later it was split into a Type-C and a Micro-B variant to deal with the Lenovo Yoga Book YB1-X90 / Lenovo Yoga Book YB1-X91 boards where the ACPI INT33FE firmware-node only describes the TI BQ27542 fuelgauge. Currently the driver differentiates between these 2 models by counting the number of I2cSerialBus resources in the firmware-node. There are a number of problems with this approach: 1. The driver autoloads based on the acpi:INT33FE modalias causing it to get loaded on almost all Bay Trail and Cherry Trail devices. It checks for the presence of a WC PMIC, so it won't bind but the loading still wastes time and memory. 2. Both code paths in the driver are really only designed for a single board and have harcoded various assumptions about these boards, if another design matching the current checks ever shows up the driver may end up doing something completely wrong. Avoid both issues by switching to using DMI based autoloading of the module, which has neither of these problems. Note this splits the previous intel_cht_int33fe kernel module into two modules: intel_cht_int33fe_typec and intel_cht_int33fe_microb, one for each model. Signed-off-by: Hans de Goede Link: https://lore.kernel.org/r/20220206220220.88491-2-hdegoede@redhat.com --- drivers/platform/x86/intel/int33fe/Makefile | 5 +- .../intel/int33fe/intel_cht_int33fe_common.c | 118 ------------------ .../intel/int33fe/intel_cht_int33fe_common.h | 41 ------ .../intel/int33fe/intel_cht_int33fe_microb.c | 53 +++++++- .../intel/int33fe/intel_cht_int33fe_typec.c | 65 +++++++++- 5 files changed, 111 insertions(+), 171 deletions(-) delete mode 100644 drivers/platform/x86/intel/int33fe/intel_cht_int33fe_common.c delete mode 100644 drivers/platform/x86/intel/int33fe/intel_cht_int33fe_common.h diff --git a/drivers/platform/x86/intel/int33fe/Makefile b/drivers/platform/x86/intel/int33fe/Makefile index 9456e3b37f6f..4672fcbec896 100644 --- a/drivers/platform/x86/intel/int33fe/Makefile +++ b/drivers/platform/x86/intel/int33fe/Makefile @@ -1,5 +1,2 @@ # SPDX-License-Identifier: GPL-2.0-only -obj-$(CONFIG_INTEL_CHT_INT33FE) += intel_cht_int33fe.o -intel_cht_int33fe-y := intel_cht_int33fe_common.o \ - intel_cht_int33fe_typec.o \ - intel_cht_int33fe_microb.o +obj-$(CONFIG_INTEL_CHT_INT33FE) += intel_cht_int33fe_typec.o intel_cht_int33fe_microb.o diff --git a/drivers/platform/x86/intel/int33fe/intel_cht_int33fe_common.c b/drivers/platform/x86/intel/int33fe/intel_cht_int33fe_common.c deleted file mode 100644 index 463222521e61..000000000000 --- a/drivers/platform/x86/intel/int33fe/intel_cht_int33fe_common.c +++ /dev/null @@ -1,118 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * Common code for Intel Cherry Trail ACPI INT33FE pseudo device drivers - * (USB Micro-B and Type-C connector variants). - * - * Copyright (c) 2019 Yauhen Kharuzhy - */ - -#include -#include -#include -#include -#include - -#include "intel_cht_int33fe_common.h" - -#define EXPECTED_PTYPE 4 - -static int cht_int33fe_check_hw_type(struct device *dev) -{ - unsigned long long ptyp; - acpi_status status; - int ret; - - status = acpi_evaluate_integer(ACPI_HANDLE(dev), "PTYP", NULL, &ptyp); - if (ACPI_FAILURE(status)) { - dev_err(dev, "Error getting PTYPE\n"); - return -ENODEV; - } - - /* - * The same ACPI HID is used for different configurations check PTYP - * to ensure that we are dealing with the expected config. - */ - if (ptyp != EXPECTED_PTYPE) - return -ENODEV; - - /* Check presence of INT34D3 (hardware-rev 3) expected for ptype == 4 */ - if (!acpi_dev_present("INT34D3", "1", 3)) { - dev_err(dev, "Error PTYPE == %d, but no INT34D3 device\n", - EXPECTED_PTYPE); - return -ENODEV; - } - - ret = i2c_acpi_client_count(ACPI_COMPANION(dev)); - if (ret < 0) - return ret; - - switch (ret) { - case 2: - return INT33FE_HW_MICROB; - case 4: - return INT33FE_HW_TYPEC; - default: - return -ENODEV; - } -} - -static int cht_int33fe_probe(struct platform_device *pdev) -{ - struct cht_int33fe_data *data; - struct device *dev = &pdev->dev; - int ret; - - ret = cht_int33fe_check_hw_type(dev); - if (ret < 0) - return ret; - - data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); - if (!data) - return -ENOMEM; - - data->dev = dev; - - switch (ret) { - case INT33FE_HW_MICROB: - data->probe = cht_int33fe_microb_probe; - data->remove = cht_int33fe_microb_remove; - break; - - case INT33FE_HW_TYPEC: - data->probe = cht_int33fe_typec_probe; - data->remove = cht_int33fe_typec_remove; - break; - } - - platform_set_drvdata(pdev, data); - - return data->probe(data); -} - -static int cht_int33fe_remove(struct platform_device *pdev) -{ - struct cht_int33fe_data *data = platform_get_drvdata(pdev); - - return data->remove(data); -} - -static const struct acpi_device_id cht_int33fe_acpi_ids[] = { - { "INT33FE", }, - { } -}; -MODULE_DEVICE_TABLE(acpi, cht_int33fe_acpi_ids); - -static struct platform_driver cht_int33fe_driver = { - .driver = { - .name = "Intel Cherry Trail ACPI INT33FE driver", - .acpi_match_table = ACPI_PTR(cht_int33fe_acpi_ids), - }, - .probe = cht_int33fe_probe, - .remove = cht_int33fe_remove, -}; - -module_platform_driver(cht_int33fe_driver); - -MODULE_DESCRIPTION("Intel Cherry Trail ACPI INT33FE pseudo device driver"); -MODULE_AUTHOR("Yauhen Kharuzhy "); -MODULE_LICENSE("GPL v2"); diff --git a/drivers/platform/x86/intel/int33fe/intel_cht_int33fe_common.h b/drivers/platform/x86/intel/int33fe/intel_cht_int33fe_common.h deleted file mode 100644 index 03cd45f4e8cb..000000000000 --- a/drivers/platform/x86/intel/int33fe/intel_cht_int33fe_common.h +++ /dev/null @@ -1,41 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ -/* - * Common code for Intel Cherry Trail ACPI INT33FE pseudo device drivers - * (USB Micro-B and Type-C connector variants), header file - * - * Copyright (c) 2019 Yauhen Kharuzhy - */ - -#ifndef _INTEL_CHT_INT33FE_COMMON_H -#define _INTEL_CHT_INT33FE_COMMON_H - -#include -#include -#include - -enum int33fe_hw_type { - INT33FE_HW_MICROB, - INT33FE_HW_TYPEC, -}; - -struct cht_int33fe_data { - struct device *dev; - - int (*probe)(struct cht_int33fe_data *data); - int (*remove)(struct cht_int33fe_data *data); - - struct i2c_client *battery_fg; - - /* Type-C only */ - struct i2c_client *fusb302; - struct i2c_client *pi3usb30532; - - struct fwnode_handle *dp; -}; - -int cht_int33fe_microb_probe(struct cht_int33fe_data *data); -int cht_int33fe_microb_remove(struct cht_int33fe_data *data); -int cht_int33fe_typec_probe(struct cht_int33fe_data *data); -int cht_int33fe_typec_remove(struct cht_int33fe_data *data); - -#endif /* _INTEL_CHT_INT33FE_COMMON_H */ diff --git a/drivers/platform/x86/intel/int33fe/intel_cht_int33fe_microb.c b/drivers/platform/x86/intel/int33fe/intel_cht_int33fe_microb.c index 673f41cd14b5..6c2feca8a06f 100644 --- a/drivers/platform/x86/intel/int33fe/intel_cht_int33fe_microb.c +++ b/drivers/platform/x86/intel/int33fe/intel_cht_int33fe_microb.c @@ -18,6 +18,7 @@ */ #include +#include #include #include #include @@ -26,7 +27,9 @@ #include #include -#include "intel_cht_int33fe_common.h" +struct cht_int33fe_data { + struct i2c_client *battery_fg; +}; static const char * const bq27xxx_suppliers[] = { "bq25890-charger" }; @@ -39,10 +42,30 @@ static const struct software_node bq27xxx_node = { .properties = bq27xxx_props, }; -int cht_int33fe_microb_probe(struct cht_int33fe_data *data) +static const struct dmi_system_id cht_int33fe_microb_ids[] = { + { + /* Lenovo Yoga Book X90F / X91F / X91L */ + .matches = { + /* Non exact match to match all versions */ + DMI_MATCH(DMI_PRODUCT_NAME, "Lenovo YB1-X9"), + }, + }, + { } +}; +MODULE_DEVICE_TABLE(dmi, cht_int33fe_microb_ids); + +static int cht_int33fe_microb_probe(struct platform_device *pdev) { - struct device *dev = data->dev; struct i2c_board_info board_info; + struct device *dev = &pdev->dev; + struct cht_int33fe_data *data; + + if (!dmi_check_system(cht_int33fe_microb_ids)) + return -ENODEV; + + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; memset(&board_info, 0, sizeof(board_info)); strscpy(board_info.type, "bq27542", ARRAY_SIZE(board_info.type)); @@ -53,9 +76,31 @@ int cht_int33fe_microb_probe(struct cht_int33fe_data *data) return PTR_ERR_OR_ZERO(data->battery_fg); } -int cht_int33fe_microb_remove(struct cht_int33fe_data *data) +static int cht_int33fe_microb_remove(struct platform_device *pdev) { + struct cht_int33fe_data *data = platform_get_drvdata(pdev); + i2c_unregister_device(data->battery_fg); return 0; } + +static const struct acpi_device_id cht_int33fe_acpi_ids[] = { + { "INT33FE", }, + { } +}; + +static struct platform_driver cht_int33fe_microb_driver = { + .driver = { + .name = "Intel Cherry Trail ACPI INT33FE micro-B driver", + .acpi_match_table = ACPI_PTR(cht_int33fe_acpi_ids), + }, + .probe = cht_int33fe_microb_probe, + .remove = cht_int33fe_microb_remove, +}; + +module_platform_driver(cht_int33fe_microb_driver); + +MODULE_DESCRIPTION("Intel Cherry Trail ACPI INT33FE micro-B pseudo device driver"); +MODULE_AUTHOR("Yauhen Kharuzhy "); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/platform/x86/intel/int33fe/intel_cht_int33fe_typec.c b/drivers/platform/x86/intel/int33fe/intel_cht_int33fe_typec.c index d59544167430..0de509fbf020 100644 --- a/drivers/platform/x86/intel/int33fe/intel_cht_int33fe_typec.c +++ b/drivers/platform/x86/intel/int33fe/intel_cht_int33fe_typec.c @@ -17,6 +17,7 @@ * for these chips can bind to the them. */ +#include #include #include #include @@ -26,7 +27,12 @@ #include #include -#include "intel_cht_int33fe_common.h" +struct cht_int33fe_data { + struct i2c_client *battery_fg; + struct i2c_client *fusb302; + struct i2c_client *pi3usb30532; + struct fwnode_handle *dp; +}; /* * Grrr, I severely dislike buggy BIOS-es. At least one BIOS enumerates @@ -272,15 +278,44 @@ cht_int33fe_register_max17047(struct device *dev, struct cht_int33fe_data *data) return PTR_ERR_OR_ZERO(data->battery_fg); } -int cht_int33fe_typec_probe(struct cht_int33fe_data *data) +static const struct dmi_system_id cht_int33fe_typec_ids[] = { + { + /* + * GPD win / GPD pocket mini laptops + * + * This DMI match may not seem unique, but it is. In the 67000+ + * DMI decode dumps from linux-hardware.org only 116 have + * board_vendor set to "AMI Corporation" and of those 116 only + * the GPD win's and pocket's board_name is "Default string". + */ + .matches = { + DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "AMI Corporation"), + DMI_EXACT_MATCH(DMI_BOARD_NAME, "Default string"), + DMI_EXACT_MATCH(DMI_BOARD_SERIAL, "Default string"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Default string"), + }, + }, + { } +}; +MODULE_DEVICE_TABLE(dmi, cht_int33fe_typec_ids); + +static int cht_int33fe_typec_probe(struct platform_device *pdev) { - struct device *dev = data->dev; struct i2c_board_info board_info; + struct device *dev = &pdev->dev; + struct cht_int33fe_data *data; struct fwnode_handle *fwnode; struct regulator *regulator; int fusb302_irq; int ret; + if (!dmi_check_system(cht_int33fe_typec_ids)) + return -ENODEV; + + data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + /* * We expect the WC PMIC to be paired with a TI bq24292i charger-IC. * We check for the bq24292i vbus regulator here, this has 2 purposes: @@ -368,8 +403,10 @@ int cht_int33fe_typec_probe(struct cht_int33fe_data *data) return ret; } -int cht_int33fe_typec_remove(struct cht_int33fe_data *data) +static int cht_int33fe_typec_remove(struct platform_device *pdev) { + struct cht_int33fe_data *data = platform_get_drvdata(pdev); + i2c_unregister_device(data->pi3usb30532); i2c_unregister_device(data->fusb302); i2c_unregister_device(data->battery_fg); @@ -378,3 +415,23 @@ int cht_int33fe_typec_remove(struct cht_int33fe_data *data) return 0; } + +static const struct acpi_device_id cht_int33fe_acpi_ids[] = { + { "INT33FE", }, + { } +}; + +static struct platform_driver cht_int33fe_typec_driver = { + .driver = { + .name = "Intel Cherry Trail ACPI INT33FE Type-C driver", + .acpi_match_table = ACPI_PTR(cht_int33fe_acpi_ids), + }, + .probe = cht_int33fe_typec_probe, + .remove = cht_int33fe_typec_remove, +}; + +module_platform_driver(cht_int33fe_typec_driver); + +MODULE_DESCRIPTION("Intel Cherry Trail ACPI INT33FE Type-C pseudo device driver"); +MODULE_AUTHOR("Hans de Goede "); +MODULE_LICENSE("GPL v2"); From bb22fc518c73394f1041fd79a41f300121665796 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Sun, 6 Feb 2022 23:02:19 +0100 Subject: [PATCH 031/137] platform/x86: intel_cht_int33fe: Drop Lenovo Yogabook YB1-X9x code Move the Lenovo Yogabook YB1-X9x fuel-gauge instantiation code over to the x86-android-tablets module, which already deals with this for various other devices. This removes the need to have a special intel_cht_int33fe_microb module just for Lenovo Yogabook YB1-X9x laptops. Signed-off-by: Hans de Goede Link: https://lore.kernel.org/r/20220206220220.88491-3-hdegoede@redhat.com --- drivers/platform/x86/intel/int33fe/Kconfig | 18 ++- drivers/platform/x86/intel/int33fe/Makefile | 2 +- .../intel/int33fe/intel_cht_int33fe_microb.c | 106 ------------------ drivers/platform/x86/x86-android-tablets.c | 27 +++++ 4 files changed, 35 insertions(+), 118 deletions(-) delete mode 100644 drivers/platform/x86/intel/int33fe/intel_cht_int33fe_microb.c diff --git a/drivers/platform/x86/intel/int33fe/Kconfig b/drivers/platform/x86/intel/int33fe/Kconfig index 2f7329a2e399..60a6cb689299 100644 --- a/drivers/platform/x86/intel/int33fe/Kconfig +++ b/drivers/platform/x86/intel/int33fe/Kconfig @@ -6,19 +6,15 @@ config INTEL_CHT_INT33FE depends on USB_ROLES_INTEL_XHCI=y || (USB_ROLES_INTEL_XHCI=m && m) depends on TYPEC_MUX_PI3USB30532=y || (TYPEC_MUX_PI3USB30532=m && m) help - This driver add support for the INT33FE ACPI device found on - some Intel Cherry Trail devices. + This driver add support for the INT33FE ACPI device found on the + GPD win and the GPD pocket. - There are two kinds of INT33FE ACPI device possible: for hardware - with USB Type-C and Micro-B connectors. This driver supports both. - - The INT33FE ACPI device has a CRS table with I2cSerialBusV2 - resources for Fuel Gauge Controller and (in the Type-C variant) - FUSB302 USB Type-C Controller and PI3USB30532 USB switch. + The INT33FE ACPI device on these mini laptops contains I2cSerialBusV2 + resources for a MAX17042 Fuel Gauge, FUSB302 USB Type-C Controller + and PI3USB30532 USB switch. This driver instantiates i2c-clients for these, so that standard i2c drivers for these chips can bind to the them. If you enable this driver it is advised to also select - CONFIG_BATTERY_BQ27XXX=m or CONFIG_BATTERY_BQ27XXX_I2C=m for Micro-B - device and CONFIG_TYPEC_FUSB302=m and CONFIG_BATTERY_MAX17042=m - for Type-C device. + CONFIG_TYPEC_FUSB302=m, CONFIG_TYPEC_MUX_PI3USB30532=m and + CONFIG_BATTERY_MAX17042=m. diff --git a/drivers/platform/x86/intel/int33fe/Makefile b/drivers/platform/x86/intel/int33fe/Makefile index 4672fcbec896..582aa7808c7a 100644 --- a/drivers/platform/x86/intel/int33fe/Makefile +++ b/drivers/platform/x86/intel/int33fe/Makefile @@ -1,2 +1,2 @@ # SPDX-License-Identifier: GPL-2.0-only -obj-$(CONFIG_INTEL_CHT_INT33FE) += intel_cht_int33fe_typec.o intel_cht_int33fe_microb.o +obj-$(CONFIG_INTEL_CHT_INT33FE) += intel_cht_int33fe_typec.o diff --git a/drivers/platform/x86/intel/int33fe/intel_cht_int33fe_microb.c b/drivers/platform/x86/intel/int33fe/intel_cht_int33fe_microb.c deleted file mode 100644 index 6c2feca8a06f..000000000000 --- a/drivers/platform/x86/intel/int33fe/intel_cht_int33fe_microb.c +++ /dev/null @@ -1,106 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * Intel Cherry Trail ACPI INT33FE pseudo device driver for devices with - * USB Micro-B connector (e.g. without of FUSB302 USB Type-C controller) - * - * Copyright (C) 2019 Yauhen Kharuzhy - * - * At least one Intel Cherry Trail based device which ship with Windows 10 - * (Lenovo YogaBook YB1-X91L/F tablet), have this weird INT33FE ACPI device - * with a CRS table with 2 I2cSerialBusV2 resources, for 2 different chips - * attached to various i2c busses: - * 1. The Whiskey Cove PMIC, which is also described by the INT34D3 ACPI device - * 2. TI BQ27542 Fuel Gauge Controller - * - * So this driver is a stub / pseudo driver whose only purpose is to - * instantiate i2c-client for battery fuel gauge, so that standard i2c driver - * for these chip can bind to the it. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -struct cht_int33fe_data { - struct i2c_client *battery_fg; -}; - -static const char * const bq27xxx_suppliers[] = { "bq25890-charger" }; - -static const struct property_entry bq27xxx_props[] = { - PROPERTY_ENTRY_STRING_ARRAY("supplied-from", bq27xxx_suppliers), - { } -}; - -static const struct software_node bq27xxx_node = { - .properties = bq27xxx_props, -}; - -static const struct dmi_system_id cht_int33fe_microb_ids[] = { - { - /* Lenovo Yoga Book X90F / X91F / X91L */ - .matches = { - /* Non exact match to match all versions */ - DMI_MATCH(DMI_PRODUCT_NAME, "Lenovo YB1-X9"), - }, - }, - { } -}; -MODULE_DEVICE_TABLE(dmi, cht_int33fe_microb_ids); - -static int cht_int33fe_microb_probe(struct platform_device *pdev) -{ - struct i2c_board_info board_info; - struct device *dev = &pdev->dev; - struct cht_int33fe_data *data; - - if (!dmi_check_system(cht_int33fe_microb_ids)) - return -ENODEV; - - data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL); - if (!data) - return -ENOMEM; - - memset(&board_info, 0, sizeof(board_info)); - strscpy(board_info.type, "bq27542", ARRAY_SIZE(board_info.type)); - board_info.dev_name = "bq27542"; - board_info.swnode = &bq27xxx_node; - data->battery_fg = i2c_acpi_new_device(dev, 1, &board_info); - - return PTR_ERR_OR_ZERO(data->battery_fg); -} - -static int cht_int33fe_microb_remove(struct platform_device *pdev) -{ - struct cht_int33fe_data *data = platform_get_drvdata(pdev); - - i2c_unregister_device(data->battery_fg); - - return 0; -} - -static const struct acpi_device_id cht_int33fe_acpi_ids[] = { - { "INT33FE", }, - { } -}; - -static struct platform_driver cht_int33fe_microb_driver = { - .driver = { - .name = "Intel Cherry Trail ACPI INT33FE micro-B driver", - .acpi_match_table = ACPI_PTR(cht_int33fe_acpi_ids), - }, - .probe = cht_int33fe_microb_probe, - .remove = cht_int33fe_microb_remove, -}; - -module_platform_driver(cht_int33fe_microb_driver); - -MODULE_DESCRIPTION("Intel Cherry Trail ACPI INT33FE micro-B pseudo device driver"); -MODULE_AUTHOR("Yauhen Kharuzhy "); -MODULE_LICENSE("GPL v2"); diff --git a/drivers/platform/x86/x86-android-tablets.c b/drivers/platform/x86/x86-android-tablets.c index 8e6e4f89220f..f280c82d5ba5 100644 --- a/drivers/platform/x86/x86-android-tablets.c +++ b/drivers/platform/x86/x86-android-tablets.c @@ -679,6 +679,25 @@ static const struct x86_dev_info czc_p10t __initconst = { .init = czc_p10t_init, }; +/* Lenovo Yoga Book X90F / X91F / X91L need manual instantiation of the fg client */ +static const struct x86_i2c_client_info lenovo_yogabook_x9x_i2c_clients[] __initconst = { + { + /* BQ27542 fuel-gauge */ + .board_info = { + .type = "bq27542", + .addr = 0x55, + .dev_name = "bq27542", + .swnode = &fg_bq25890_supply_node, + }, + .adapter_path = "\\_SB_.PCI0.I2C1", + }, +}; + +static const struct x86_dev_info lenovo_yogabook_x9x_info __initconst = { + .i2c_client_info = lenovo_yogabook_x9x_i2c_clients, + .i2c_client_count = ARRAY_SIZE(lenovo_yogabook_x9x_i2c_clients), +}; + /* Nextbook Ares 8 tablets have an Android factory img with everything hardcoded */ static const char * const nextbook_ares8_accel_mount_matrix[] = { "0", "-1", "0", @@ -915,6 +934,14 @@ static const struct dmi_system_id x86_android_tablet_ids[] __initconst = { }, .driver_data = (void *)&czc_p10t, }, + { + /* Lenovo Yoga Book X90F / X91F / X91L */ + .matches = { + /* Non exact match to match all versions */ + DMI_MATCH(DMI_PRODUCT_NAME, "Lenovo YB1-X9"), + }, + .driver_data = (void *)&lenovo_yogabook_x9x_info, + }, { /* Nextbook Ares 8 */ .matches = { From ae707d0eb707b5fc658e870dbd8b21887b0a5240 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Sun, 6 Feb 2022 23:02:20 +0100 Subject: [PATCH 032/137] platform/x86: intel_cht_int33fe: Move to intel directory Now that there is only 1 c-file left of the intel_cht_int33fe code, move it to the intel directory instead of it having its own int33fe sub-directory. Note this also renames the module from intel_cht_int33fe_typec to intel_chtwc_int33fe, to better match the names of other PMIC related modules like the intel_chtdc_ti_pwrbtn module. Signed-off-by: Hans de Goede Link: https://lore.kernel.org/r/20220206220220.88491-4-hdegoede@redhat.com --- drivers/platform/x86/intel/Kconfig | 21 ++++++++++++++++++- drivers/platform/x86/intel/Makefile | 3 ++- ...el_cht_int33fe_typec.c => chtwc_int33fe.c} | 0 drivers/platform/x86/intel/int33fe/Kconfig | 20 ------------------ drivers/platform/x86/intel/int33fe/Makefile | 2 -- 5 files changed, 22 insertions(+), 24 deletions(-) rename drivers/platform/x86/intel/{int33fe/intel_cht_int33fe_typec.c => chtwc_int33fe.c} (100%) delete mode 100644 drivers/platform/x86/intel/int33fe/Kconfig delete mode 100644 drivers/platform/x86/intel/int33fe/Makefile diff --git a/drivers/platform/x86/intel/Kconfig b/drivers/platform/x86/intel/Kconfig index 8e65086bb6c8..2d9b49eed33d 100644 --- a/drivers/platform/x86/intel/Kconfig +++ b/drivers/platform/x86/intel/Kconfig @@ -5,7 +5,6 @@ source "drivers/platform/x86/intel/atomisp2/Kconfig" source "drivers/platform/x86/intel/int1092/Kconfig" -source "drivers/platform/x86/intel/int33fe/Kconfig" source "drivers/platform/x86/intel/int3472/Kconfig" source "drivers/platform/x86/intel/pmc/Kconfig" source "drivers/platform/x86/intel/pmt/Kconfig" @@ -89,6 +88,26 @@ config INTEL_CHTDC_TI_PWRBTN To compile this driver as a module, choose M here: the module will be called intel_chtdc_ti_pwrbtn. +config INTEL_CHTWC_INT33FE + tristate "Intel Cherry Trail Whiskey Cove ACPI INT33FE Driver" + depends on X86 && ACPI && I2C && REGULATOR + depends on CHARGER_BQ24190=y || (CHARGER_BQ24190=m && m) + depends on USB_ROLES_INTEL_XHCI=y || (USB_ROLES_INTEL_XHCI=m && m) + depends on TYPEC_MUX_PI3USB30532=y || (TYPEC_MUX_PI3USB30532=m && m) + help + This driver add support for the Intel Cherry Trail Whiskey Cove + INT33FE ACPI device found on the GPD win and the GPD pocket. + + The INT33FE ACPI device on these mini laptops contains I2cSerialBusV2 + resources for a MAX17042 Fuel Gauge, FUSB302 USB Type-C Controller + and PI3USB30532 USB switch. + This driver instantiates i2c-clients for these, so that standard + i2c drivers for these chips can bind to the them. + + If you enable this driver it is advised to also select + CONFIG_TYPEC_FUSB302=m, CONFIG_TYPEC_MUX_PI3USB30532=m and + CONFIG_BATTERY_MAX17042=m. + config INTEL_ISHTP_ECLITE tristate "Intel ISHTP eclite controller Driver" depends on INTEL_ISH_HID diff --git a/drivers/platform/x86/intel/Makefile b/drivers/platform/x86/intel/Makefile index 35f2066578b2..053edc457ccb 100644 --- a/drivers/platform/x86/intel/Makefile +++ b/drivers/platform/x86/intel/Makefile @@ -6,7 +6,6 @@ obj-$(CONFIG_INTEL_ATOMISP2_PDX86) += atomisp2/ obj-$(CONFIG_INTEL_SAR_INT1092) += int1092/ -obj-$(CONFIG_INTEL_CHT_INT33FE) += int33fe/ obj-$(CONFIG_INTEL_SKL_INT3472) += int3472/ obj-$(CONFIG_INTEL_PMC_CORE) += pmc/ obj-$(CONFIG_INTEL_PMT_CLASS) += pmt/ @@ -36,6 +35,8 @@ intel_crystal_cove_charger-y := crystal_cove_charger.o obj-$(CONFIG_X86_ANDROID_TABLETS) += intel_crystal_cove_charger.o intel_chtdc_ti_pwrbtn-y := chtdc_ti_pwrbtn.o obj-$(CONFIG_INTEL_CHTDC_TI_PWRBTN) += intel_chtdc_ti_pwrbtn.o +intel_chtwc_int33fe-y := chtwc_int33fe.o +obj-$(CONFIG_INTEL_CHTWC_INT33FE) += intel_chtwc_int33fe.o intel_mrfld_pwrbtn-y := mrfld_pwrbtn.o obj-$(CONFIG_INTEL_MRFLD_PWRBTN) += intel_mrfld_pwrbtn.o intel_punit_ipc-y := punit_ipc.o diff --git a/drivers/platform/x86/intel/int33fe/intel_cht_int33fe_typec.c b/drivers/platform/x86/intel/chtwc_int33fe.c similarity index 100% rename from drivers/platform/x86/intel/int33fe/intel_cht_int33fe_typec.c rename to drivers/platform/x86/intel/chtwc_int33fe.c diff --git a/drivers/platform/x86/intel/int33fe/Kconfig b/drivers/platform/x86/intel/int33fe/Kconfig deleted file mode 100644 index 60a6cb689299..000000000000 --- a/drivers/platform/x86/intel/int33fe/Kconfig +++ /dev/null @@ -1,20 +0,0 @@ -# SPDX-License-Identifier: GPL-2.0-only -config INTEL_CHT_INT33FE - tristate "Intel Cherry Trail ACPI INT33FE Driver" - depends on X86 && ACPI && I2C && REGULATOR - depends on CHARGER_BQ24190=y || (CHARGER_BQ24190=m && m) - depends on USB_ROLES_INTEL_XHCI=y || (USB_ROLES_INTEL_XHCI=m && m) - depends on TYPEC_MUX_PI3USB30532=y || (TYPEC_MUX_PI3USB30532=m && m) - help - This driver add support for the INT33FE ACPI device found on the - GPD win and the GPD pocket. - - The INT33FE ACPI device on these mini laptops contains I2cSerialBusV2 - resources for a MAX17042 Fuel Gauge, FUSB302 USB Type-C Controller - and PI3USB30532 USB switch. - This driver instantiates i2c-clients for these, so that standard - i2c drivers for these chips can bind to the them. - - If you enable this driver it is advised to also select - CONFIG_TYPEC_FUSB302=m, CONFIG_TYPEC_MUX_PI3USB30532=m and - CONFIG_BATTERY_MAX17042=m. diff --git a/drivers/platform/x86/intel/int33fe/Makefile b/drivers/platform/x86/intel/int33fe/Makefile deleted file mode 100644 index 582aa7808c7a..000000000000 --- a/drivers/platform/x86/intel/int33fe/Makefile +++ /dev/null @@ -1,2 +0,0 @@ -# SPDX-License-Identifier: GPL-2.0-only -obj-$(CONFIG_INTEL_CHT_INT33FE) += intel_cht_int33fe_typec.o From f11445ba7a1160b87615e3f863a9e66c85189399 Mon Sep 17 00:00:00 2001 From: Borislav Petkov Date: Fri, 4 Feb 2022 09:30:15 +0100 Subject: [PATCH 033/137] x86/mce: Use arch atomic and bit helpers The arch helpers do not have explicit KASAN instrumentation. Use them in noinstr code. Inline a couple more functions with single call sites, while at it: mce_severity_amd_smca() has a single call-site which is noinstr so force the inlining and fix: vmlinux.o: warning: objtool: mce_severity_amd.constprop.0()+0xca: call to \ mce_severity_amd_smca() leaves .noinstr.text section Always inline mca_msr_reg(): text data bss dec hex filename 16065240 128031326 36405368 180501934 ac23dae vmlinux.before 16065240 128031294 36405368 180501902 ac23d8e vmlinux.after and mce_no_way_out() as the latter one is used only once, to fix: vmlinux.o: warning: objtool: mce_read_aux()+0x53: call to mca_msr_reg() leaves .noinstr.text section vmlinux.o: warning: objtool: do_machine_check()+0xc9: call to mce_no_way_out() leaves .noinstr.text section Signed-off-by: Borislav Petkov Acked-by: Marco Elver Link: https://lore.kernel.org/r/20220204083015.17317-4-bp@alien8.de --- arch/x86/kernel/cpu/mce/core.c | 58 ++++++++++-------------------- arch/x86/kernel/cpu/mce/internal.h | 23 ++++++++++-- arch/x86/kernel/cpu/mce/severity.c | 2 +- 3 files changed, 41 insertions(+), 42 deletions(-) diff --git a/arch/x86/kernel/cpu/mce/core.c b/arch/x86/kernel/cpu/mce/core.c index 5818b837fd4d..0e7147430ec0 100644 --- a/arch/x86/kernel/cpu/mce/core.c +++ b/arch/x86/kernel/cpu/mce/core.c @@ -173,27 +173,6 @@ void mce_unregister_decode_chain(struct notifier_block *nb) } EXPORT_SYMBOL_GPL(mce_unregister_decode_chain); -u32 mca_msr_reg(int bank, enum mca_msr reg) -{ - if (mce_flags.smca) { - switch (reg) { - case MCA_CTL: return MSR_AMD64_SMCA_MCx_CTL(bank); - case MCA_ADDR: return MSR_AMD64_SMCA_MCx_ADDR(bank); - case MCA_MISC: return MSR_AMD64_SMCA_MCx_MISC(bank); - case MCA_STATUS: return MSR_AMD64_SMCA_MCx_STATUS(bank); - } - } - - switch (reg) { - case MCA_CTL: return MSR_IA32_MCx_CTL(bank); - case MCA_ADDR: return MSR_IA32_MCx_ADDR(bank); - case MCA_MISC: return MSR_IA32_MCx_MISC(bank); - case MCA_STATUS: return MSR_IA32_MCx_STATUS(bank); - } - - return 0; -} - static void __print_mce(struct mce *m) { pr_emerg(HW_ERR "CPU %d: Machine Check%s: %Lx Bank %d: %016Lx\n", @@ -814,7 +793,8 @@ EXPORT_SYMBOL_GPL(machine_check_poll); * the severity assessment code. Pretend that EIPV was set, and take the * ip/cs values from the pt_regs that mce_gather_info() ignored earlier. */ -static void quirk_sandybridge_ifu(int bank, struct mce *m, struct pt_regs *regs) +static __always_inline void +quirk_sandybridge_ifu(int bank, struct mce *m, struct pt_regs *regs) { if (bank != 0) return; @@ -838,8 +818,8 @@ static void quirk_sandybridge_ifu(int bank, struct mce *m, struct pt_regs *regs) * Do a quick check if any of the events requires a panic. * This decides if we keep the events around or clear them. */ -static int mce_no_way_out(struct mce *m, char **msg, unsigned long *validp, - struct pt_regs *regs) +static __always_inline int mce_no_way_out(struct mce *m, char **msg, unsigned long *validp, + struct pt_regs *regs) { char *tmp = *msg; int i; @@ -849,7 +829,7 @@ static int mce_no_way_out(struct mce *m, char **msg, unsigned long *validp, if (!(m->status & MCI_STATUS_VAL)) continue; - __set_bit(i, validp); + arch___set_bit(i, validp); if (mce_flags.snb_ifu_quirk) quirk_sandybridge_ifu(i, m, regs); @@ -1015,13 +995,13 @@ static noinstr int mce_start(int *no_way_out) if (!timeout) return ret; - atomic_add(*no_way_out, &global_nwo); + arch_atomic_add(*no_way_out, &global_nwo); /* * Rely on the implied barrier below, such that global_nwo * is updated before mce_callin. */ - order = atomic_inc_return(&mce_callin); - cpumask_clear_cpu(smp_processor_id(), &mce_missing_cpus); + order = arch_atomic_inc_return(&mce_callin); + arch_cpumask_clear_cpu(smp_processor_id(), &mce_missing_cpus); /* Enable instrumentation around calls to external facilities */ instrumentation_begin(); @@ -1029,10 +1009,10 @@ static noinstr int mce_start(int *no_way_out) /* * Wait for everyone. */ - while (atomic_read(&mce_callin) != num_online_cpus()) { + while (arch_atomic_read(&mce_callin) != num_online_cpus()) { if (mce_timed_out(&timeout, "Timeout: Not all CPUs entered broadcast exception handler")) { - atomic_set(&global_nwo, 0); + arch_atomic_set(&global_nwo, 0); goto out; } ndelay(SPINUNIT); @@ -1047,7 +1027,7 @@ static noinstr int mce_start(int *no_way_out) /* * Monarch: Starts executing now, the others wait. */ - atomic_set(&mce_executing, 1); + arch_atomic_set(&mce_executing, 1); } else { /* * Subject: Now start the scanning loop one by one in @@ -1055,10 +1035,10 @@ static noinstr int mce_start(int *no_way_out) * This way when there are any shared banks it will be * only seen by one CPU before cleared, avoiding duplicates. */ - while (atomic_read(&mce_executing) < order) { + while (arch_atomic_read(&mce_executing) < order) { if (mce_timed_out(&timeout, "Timeout: Subject CPUs unable to finish machine check processing")) { - atomic_set(&global_nwo, 0); + arch_atomic_set(&global_nwo, 0); goto out; } ndelay(SPINUNIT); @@ -1068,7 +1048,7 @@ static noinstr int mce_start(int *no_way_out) /* * Cache the global no_way_out state. */ - *no_way_out = atomic_read(&global_nwo); + *no_way_out = arch_atomic_read(&global_nwo); ret = order; @@ -1153,12 +1133,12 @@ static noinstr int mce_end(int order) return ret; } -static void mce_clear_state(unsigned long *toclear) +static __always_inline void mce_clear_state(unsigned long *toclear) { int i; for (i = 0; i < this_cpu_read(mce_num_banks); i++) { - if (test_bit(i, toclear)) + if (arch_test_bit(i, toclear)) mce_wrmsrl(mca_msr_reg(i, MCA_STATUS), 0); } } @@ -1208,8 +1188,8 @@ __mc_scan_banks(struct mce *m, struct pt_regs *regs, struct mce *final, int severity, i, taint = 0; for (i = 0; i < this_cpu_read(mce_num_banks); i++) { - __clear_bit(i, toclear); - if (!test_bit(i, valid_banks)) + arch___clear_bit(i, toclear); + if (!arch_test_bit(i, valid_banks)) continue; if (!mce_banks[i].ctl) @@ -1244,7 +1224,7 @@ __mc_scan_banks(struct mce *m, struct pt_regs *regs, struct mce *final, severity == MCE_UCNA_SEVERITY) && !no_way_out) continue; - __set_bit(i, toclear); + arch___set_bit(i, toclear); /* Machine check event was not enabled. Clear, but ignore. */ if (severity == MCE_NO_SEVERITY) diff --git a/arch/x86/kernel/cpu/mce/internal.h b/arch/x86/kernel/cpu/mce/internal.h index 52c633950b38..a04b61e27827 100644 --- a/arch/x86/kernel/cpu/mce/internal.h +++ b/arch/x86/kernel/cpu/mce/internal.h @@ -182,8 +182,6 @@ enum mca_msr { MCA_MISC, }; -u32 mca_msr_reg(int bank, enum mca_msr reg); - /* Decide whether to add MCE record to MCE event pool or filter it out. */ extern bool filter_mce(struct mce *m); @@ -209,4 +207,25 @@ static inline void winchip_machine_check(struct pt_regs *regs) {} noinstr u64 mce_rdmsrl(u32 msr); +static __always_inline u32 mca_msr_reg(int bank, enum mca_msr reg) +{ + if (mce_flags.smca) { + switch (reg) { + case MCA_CTL: return MSR_AMD64_SMCA_MCx_CTL(bank); + case MCA_ADDR: return MSR_AMD64_SMCA_MCx_ADDR(bank); + case MCA_MISC: return MSR_AMD64_SMCA_MCx_MISC(bank); + case MCA_STATUS: return MSR_AMD64_SMCA_MCx_STATUS(bank); + } + } + + switch (reg) { + case MCA_CTL: return MSR_IA32_MCx_CTL(bank); + case MCA_ADDR: return MSR_IA32_MCx_ADDR(bank); + case MCA_MISC: return MSR_IA32_MCx_MISC(bank); + case MCA_STATUS: return MSR_IA32_MCx_STATUS(bank); + } + + return 0; +} + #endif /* __X86_MCE_INTERNAL_H__ */ diff --git a/arch/x86/kernel/cpu/mce/severity.c b/arch/x86/kernel/cpu/mce/severity.c index 7aa2bda93cbb..ca0d775346fb 100644 --- a/arch/x86/kernel/cpu/mce/severity.c +++ b/arch/x86/kernel/cpu/mce/severity.c @@ -301,7 +301,7 @@ static noinstr int error_context(struct mce *m, struct pt_regs *regs) } } -static int mce_severity_amd_smca(struct mce *m, enum context err_ctx) +static __always_inline int mce_severity_amd_smca(struct mce *m, enum context err_ctx) { u64 mcx_cfg; From cf5b2fb012c037fe20f617a11eb048ef70ce4b9e Mon Sep 17 00:00:00 2001 From: Angela Czubak Date: Mon, 17 Jan 2022 23:26:17 -0800 Subject: [PATCH 034/137] HID: i2c-hid: fix handling numbered reports with IDs of 15 and above Special handling of numbered reports with IDs of 15 and above is only needed when executing what HID-I2C spec is calling "Class Specific Requests", and not when simply sending output reports. Additionally, our mangling of report ID in i2c_hid_set_or_send_report() resulted in incorrect report ID being written into SET_REPORT command payload. To solve it let's move all the report ID manipulation into __i2c_hid_command() where we form the command data structure. Signed-off-by: Angela Czubak Signed-off-by: Dmitry Torokhov Tested-by: Benjamin Tissoires Signed-off-by: Jiri Kosina --- drivers/hid/i2c-hid/i2c-hid-core.c | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/drivers/hid/i2c-hid/i2c-hid-core.c b/drivers/hid/i2c-hid/i2c-hid-core.c index 6726567d7297..899441b9c05b 100644 --- a/drivers/hid/i2c-hid/i2c-hid-core.c +++ b/drivers/hid/i2c-hid/i2c-hid-core.c @@ -97,6 +97,7 @@ union command { __le16 reg; __u8 reportTypeID; __u8 opcode; + __u8 reportID; } __packed c; }; @@ -232,7 +233,13 @@ static int __i2c_hid_command(struct i2c_client *client, if (length > 2) { cmd->c.opcode = command->opcode; - cmd->c.reportTypeID = reportID | reportType << 4; + if (reportID < 0x0F) { + cmd->c.reportTypeID = reportType << 4 | reportID; + } else { + cmd->c.reportTypeID = reportType << 4 | 0x0F; + cmd->c.reportID = reportID; + length++; + } } memcpy(cmd->data + length, args, args_len); @@ -293,18 +300,13 @@ static int i2c_hid_get_report(struct i2c_client *client, u8 reportType, u8 reportID, unsigned char *buf_recv, int data_len) { struct i2c_hid *ihid = i2c_get_clientdata(client); - u8 args[3]; + u8 args[2]; int ret; int args_len = 0; u16 readRegister = le16_to_cpu(ihid->hdesc.wDataRegister); i2c_hid_dbg(ihid, "%s\n", __func__); - if (reportID >= 0x0F) { - args[args_len++] = reportID; - reportID = 0x0F; - } - args[args_len++] = readRegister & 0xFF; args[args_len++] = readRegister >> 8; @@ -350,18 +352,12 @@ static int i2c_hid_set_or_send_report(struct i2c_client *client, u8 reportType, size = 2 /* size */ + (reportID ? 1 : 0) /* reportID */ + data_len /* buf */; - args_len = (reportID >= 0x0F ? 1 : 0) /* optional third byte */ + - 2 /* dataRegister */ + + args_len = 2 /* dataRegister */ + size /* args */; if (!use_data && maxOutputLength == 0) return -ENOSYS; - if (reportID >= 0x0F) { - args[index++] = reportID; - reportID = 0x0F; - } - /* * use the data register for feature reports or if the device does not * support the output register From a5e5e03e94764148a01757b2fa4737d3445c13a6 Mon Sep 17 00:00:00 2001 From: Dmitry Torokhov Date: Mon, 17 Jan 2022 23:26:18 -0800 Subject: [PATCH 035/137] HID: i2c-hid: fix GET/SET_REPORT for unnumbered reports Internally kernel prepends all report buffers, for both numbered and unnumbered reports, with report ID, therefore to properly handle unnumbered reports we should prepend it ourselves. For the same reason we should skip the first byte of the buffer when calling i2c_hid_set_or_send_report() which then will take care of properly formatting the transfer buffer based on its separate report ID argument along with report payload. [jkosina@suse.cz: finalize trimmed sentence in changelog as spotted by Benjamin] Fixes: 9b5a9ae88573 ("HID: i2c-hid: implement ll_driver transport-layer callbacks") Signed-off-by: Dmitry Torokhov Tested-by: Benjamin Tissoires Signed-off-by: Jiri Kosina --- drivers/hid/i2c-hid/i2c-hid-core.c | 32 ++++++++++++++++++++++-------- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/drivers/hid/i2c-hid/i2c-hid-core.c b/drivers/hid/i2c-hid/i2c-hid-core.c index 899441b9c05b..748619e36f95 100644 --- a/drivers/hid/i2c-hid/i2c-hid-core.c +++ b/drivers/hid/i2c-hid/i2c-hid-core.c @@ -614,6 +614,17 @@ static int i2c_hid_get_raw_report(struct hid_device *hid, if (report_type == HID_OUTPUT_REPORT) return -EINVAL; + /* + * In case of unnumbered reports the response from the device will + * not have the report ID that the upper layers expect, so we need + * to stash it the buffer ourselves and adjust the data size. + */ + if (!report_number) { + buf[0] = 0; + buf++; + count--; + } + /* +2 bytes to include the size of the reply in the query buffer */ ask_count = min(count + 2, (size_t)ihid->bufsize); @@ -635,6 +646,9 @@ static int i2c_hid_get_raw_report(struct hid_device *hid, count = min(count, ret_count - 2); memcpy(buf, ihid->rawbuf + 2, count); + if (!report_number) + count++; + return count; } @@ -651,17 +665,19 @@ static int i2c_hid_output_raw_report(struct hid_device *hid, __u8 *buf, mutex_lock(&ihid->reset_lock); - if (report_id) { - buf++; - count--; - } - + /* + * Note that both numbered and unnumbered reports passed here + * are supposed to have report ID stored in the 1st byte of the + * buffer, so we strip it off unconditionally before passing payload + * to i2c_hid_set_or_send_report which takes care of encoding + * everything properly. + */ ret = i2c_hid_set_or_send_report(client, report_type == HID_FEATURE_REPORT ? 0x03 : 0x02, - report_id, buf, count, use_data); + report_id, buf + 1, count - 1, use_data); - if (report_id && ret >= 0) - ret++; /* add report_id to the number of transfered bytes */ + if (ret >= 0) + ret++; /* add report_id to the number of transferred bytes */ mutex_unlock(&ihid->reset_lock); From d34c6105499bd0c3861c4a514bb7d953b851bc91 Mon Sep 17 00:00:00 2001 From: Dmitry Torokhov Date: Mon, 17 Jan 2022 23:26:19 -0800 Subject: [PATCH 036/137] HID: i2c-hid: use "struct i2c_hid" as argument in most calls The main object in the driver is struct i2c_hid so it makes more sense to pass it around instead of passing i2c_client and then fetching i2c_hid associated with it. Signed-off-by: Dmitry Torokhov Tested-by: Benjamin Tissoires Signed-off-by: Jiri Kosina --- drivers/hid/i2c-hid/i2c-hid-core.c | 75 ++++++++++++++---------------- 1 file changed, 36 insertions(+), 39 deletions(-) diff --git a/drivers/hid/i2c-hid/i2c-hid-core.c b/drivers/hid/i2c-hid/i2c-hid-core.c index 748619e36f95..4f5f7574c823 100644 --- a/drivers/hid/i2c-hid/i2c-hid-core.c +++ b/drivers/hid/i2c-hid/i2c-hid-core.c @@ -208,12 +208,12 @@ static u32 i2c_hid_lookup_quirk(const u16 idVendor, const u16 idProduct) return quirks; } -static int __i2c_hid_command(struct i2c_client *client, +static int __i2c_hid_command(struct i2c_hid *ihid, const struct i2c_hid_cmd *command, u8 reportID, u8 reportType, u8 *args, int args_len, unsigned char *buf_recv, int data_len) { - struct i2c_hid *ihid = i2c_get_clientdata(client); + struct i2c_client *client = ihid->client; union command *cmd = (union command *)ihid->cmdbuf; int ret; struct i2c_msg msg[2]; @@ -288,18 +288,17 @@ static int __i2c_hid_command(struct i2c_client *client, return ret; } -static int i2c_hid_command(struct i2c_client *client, +static int i2c_hid_command(struct i2c_hid *ihid, const struct i2c_hid_cmd *command, unsigned char *buf_recv, int data_len) { - return __i2c_hid_command(client, command, 0, 0, NULL, 0, + return __i2c_hid_command(ihid, command, 0, 0, NULL, 0, buf_recv, data_len); } -static int i2c_hid_get_report(struct i2c_client *client, u8 reportType, +static int i2c_hid_get_report(struct i2c_hid *ihid, u8 reportType, u8 reportID, unsigned char *buf_recv, int data_len) { - struct i2c_hid *ihid = i2c_get_clientdata(client); u8 args[2]; int ret; int args_len = 0; @@ -310,10 +309,10 @@ static int i2c_hid_get_report(struct i2c_client *client, u8 reportType, args[args_len++] = readRegister & 0xFF; args[args_len++] = readRegister >> 8; - ret = __i2c_hid_command(client, &hid_get_report_cmd, reportID, + ret = __i2c_hid_command(ihid, &hid_get_report_cmd, reportID, reportType, args, args_len, buf_recv, data_len); if (ret) { - dev_err(&client->dev, + dev_err(&ihid->client->dev, "failed to retrieve report from device.\n"); return ret; } @@ -323,17 +322,16 @@ static int i2c_hid_get_report(struct i2c_client *client, u8 reportType, /** * i2c_hid_set_or_send_report: forward an incoming report to the device - * @client: the i2c_client of the device + * @ihid: the i2c hid device * @reportType: 0x03 for HID_FEATURE_REPORT ; 0x02 for HID_OUTPUT_REPORT * @reportID: the report ID * @buf: the actual data to transfer, without the report ID * @data_len: size of buf * @use_data: true: use SET_REPORT HID command, false: send plain OUTPUT report */ -static int i2c_hid_set_or_send_report(struct i2c_client *client, u8 reportType, +static int i2c_hid_set_or_send_report(struct i2c_hid *ihid, u8 reportType, u8 reportID, unsigned char *buf, size_t data_len, bool use_data) { - struct i2c_hid *ihid = i2c_get_clientdata(client); u8 *args = ihid->argsbuf; const struct i2c_hid_cmd *hidcmd; int ret; @@ -380,19 +378,19 @@ static int i2c_hid_set_or_send_report(struct i2c_client *client, u8 reportType, memcpy(&args[index], buf, data_len); - ret = __i2c_hid_command(client, hidcmd, reportID, + ret = __i2c_hid_command(ihid, hidcmd, reportID, reportType, args, args_len, NULL, 0); if (ret) { - dev_err(&client->dev, "failed to set a report to device.\n"); + dev_err(&ihid->client->dev, + "failed to set a report to device.\n"); return ret; } return data_len; } -static int i2c_hid_set_power(struct i2c_client *client, int power_state) +static int i2c_hid_set_power(struct i2c_hid *ihid, int power_state) { - struct i2c_hid *ihid = i2c_get_clientdata(client); int ret; i2c_hid_dbg(ihid, "%s\n", __func__); @@ -404,18 +402,18 @@ static int i2c_hid_set_power(struct i2c_client *client, int power_state) */ if (power_state == I2C_HID_PWR_ON && ihid->quirks & I2C_HID_QUIRK_SET_PWR_WAKEUP_DEV) { - ret = i2c_hid_command(client, &hid_set_power_cmd, NULL, 0); + ret = i2c_hid_command(ihid, &hid_set_power_cmd, NULL, 0); /* Device was already activated */ if (!ret) goto set_pwr_exit; } - ret = __i2c_hid_command(client, &hid_set_power_cmd, power_state, + ret = __i2c_hid_command(ihid, &hid_set_power_cmd, power_state, 0, NULL, 0, NULL, 0); - if (ret) - dev_err(&client->dev, "failed to change power setting.\n"); + dev_err(&ihid->client->dev, + "failed to change power setting.\n"); set_pwr_exit: @@ -434,9 +432,8 @@ static int i2c_hid_set_power(struct i2c_client *client, int power_state) return ret; } -static int i2c_hid_hwreset(struct i2c_client *client) +static int i2c_hid_hwreset(struct i2c_hid *ihid) { - struct i2c_hid *ihid = i2c_get_clientdata(client); int ret; i2c_hid_dbg(ihid, "%s\n", __func__); @@ -448,22 +445,22 @@ static int i2c_hid_hwreset(struct i2c_client *client) */ mutex_lock(&ihid->reset_lock); - ret = i2c_hid_set_power(client, I2C_HID_PWR_ON); + ret = i2c_hid_set_power(ihid, I2C_HID_PWR_ON); if (ret) goto out_unlock; i2c_hid_dbg(ihid, "resetting...\n"); - ret = i2c_hid_command(client, &hid_reset_cmd, NULL, 0); + ret = i2c_hid_command(ihid, &hid_reset_cmd, NULL, 0); if (ret) { - dev_err(&client->dev, "failed to reset device.\n"); - i2c_hid_set_power(client, I2C_HID_PWR_SLEEP); + dev_err(&ihid->client->dev, "failed to reset device.\n"); + i2c_hid_set_power(ihid, I2C_HID_PWR_SLEEP); goto out_unlock; } /* At least some SIS devices need this after reset */ if (!(ihid->quirks & I2C_HID_QUIRK_NO_WAKEUP_AFTER_RESET)) - ret = i2c_hid_set_power(client, I2C_HID_PWR_ON); + ret = i2c_hid_set_power(ihid, I2C_HID_PWR_ON); out_unlock: mutex_unlock(&ihid->reset_lock); @@ -628,7 +625,7 @@ static int i2c_hid_get_raw_report(struct hid_device *hid, /* +2 bytes to include the size of the reply in the query buffer */ ask_count = min(count + 2, (size_t)ihid->bufsize); - ret = i2c_hid_get_report(client, + ret = i2c_hid_get_report(ihid, report_type == HID_FEATURE_REPORT ? 0x03 : 0x01, report_number, ihid->rawbuf, ask_count); @@ -672,7 +669,7 @@ static int i2c_hid_output_raw_report(struct hid_device *hid, __u8 *buf, * to i2c_hid_set_or_send_report which takes care of encoding * everything properly. */ - ret = i2c_hid_set_or_send_report(client, + ret = i2c_hid_set_or_send_report(ihid, report_type == HID_FEATURE_REPORT ? 0x03 : 0x02, report_id, buf + 1, count - 1, use_data); @@ -727,7 +724,7 @@ static int i2c_hid_parse(struct hid_device *hid) } do { - ret = i2c_hid_hwreset(client); + ret = i2c_hid_hwreset(ihid); if (ret) msleep(1000); } while (tries-- > 0 && ret); @@ -751,7 +748,7 @@ static int i2c_hid_parse(struct hid_device *hid) i2c_hid_dbg(ihid, "asking HID report descriptor\n"); - ret = i2c_hid_command(client, &hid_report_descr_cmd, + ret = i2c_hid_command(ihid, &hid_report_descr_cmd, rdesc, rsize); if (ret) { hid_err(hid, "reading report descriptor failed\n"); @@ -871,11 +868,11 @@ static int i2c_hid_fetch_hid_descriptor(struct i2c_hid *ihid) *i2c_hid_get_dmi_i2c_hid_desc_override(client->name); } else { i2c_hid_dbg(ihid, "Fetching the HID descriptor\n"); - ret = i2c_hid_command(client, &hid_descr_cmd, + ret = i2c_hid_command(ihid, &hid_descr_cmd, ihid->hdesc_buffer, sizeof(struct i2c_hid_desc)); if (ret) { - dev_err(&client->dev, "hid_descr_cmd failed\n"); + dev_err(&ihid->client->dev, "hid_descr_cmd failed\n"); return -ENODEV; } } @@ -885,7 +882,7 @@ static int i2c_hid_fetch_hid_descriptor(struct i2c_hid *ihid) * bytes 2-3 -> bcdVersion (has to be 1.00) */ /* check bcdVersion == 1.0 */ if (le16_to_cpu(hdesc->bcdVersion) != 0x0100) { - dev_err(&client->dev, + dev_err(&ihid->client->dev, "unexpected HID descriptor bcdVersion (0x%04hx)\n", le16_to_cpu(hdesc->bcdVersion)); return -ENODEV; @@ -894,8 +891,8 @@ static int i2c_hid_fetch_hid_descriptor(struct i2c_hid *ihid) /* Descriptor length should be 30 bytes as per the specification */ dsize = le16_to_cpu(hdesc->wHIDDescLength); if (dsize != sizeof(struct i2c_hid_desc)) { - dev_err(&client->dev, "weird size of HID descriptor (%u)\n", - dsize); + dev_err(&ihid->client->dev, + "weird size of HID descriptor (%u)\n", dsize); return -ENODEV; } i2c_hid_dbg(ihid, "HID Descriptor: %*ph\n", dsize, ihid->hdesc_buffer); @@ -1064,7 +1061,7 @@ void i2c_hid_core_shutdown(struct i2c_client *client) { struct i2c_hid *ihid = i2c_get_clientdata(client); - i2c_hid_set_power(client, I2C_HID_PWR_SLEEP); + i2c_hid_set_power(ihid, I2C_HID_PWR_SLEEP); free_irq(client->irq, ihid); i2c_hid_core_shutdown_tail(ihid); @@ -1085,7 +1082,7 @@ static int i2c_hid_core_suspend(struct device *dev) return ret; /* Save some power */ - i2c_hid_set_power(client, I2C_HID_PWR_SLEEP); + i2c_hid_set_power(ihid, I2C_HID_PWR_SLEEP); disable_irq(client->irq); @@ -1133,9 +1130,9 @@ static int i2c_hid_core_resume(struct device *dev) * let's still reset them here. */ if (ihid->quirks & I2C_HID_QUIRK_RESET_ON_RESUME) - ret = i2c_hid_hwreset(client); + ret = i2c_hid_hwreset(ihid); else - ret = i2c_hid_set_power(client, I2C_HID_PWR_ON); + ret = i2c_hid_set_power(ihid, I2C_HID_PWR_ON); if (ret) return ret; From b26fc3161b7874ae6300151e652d2d6a15f199f9 Mon Sep 17 00:00:00 2001 From: Dmitry Torokhov Date: Mon, 17 Jan 2022 23:26:20 -0800 Subject: [PATCH 037/137] HID: i2c-hid: refactor reset command "Reset" is the only command that needs to wait for interrupt from the device before continuing, so let's factor our waiting logic from __i2c_hid_command() to make it simpler. Signed-off-by: Dmitry Torokhov Tested-by: Benjamin Tissoires Signed-off-by: Jiri Kosina --- drivers/hid/i2c-hid/i2c-hid-core.c | 63 ++++++++++++++++++------------ 1 file changed, 38 insertions(+), 25 deletions(-) diff --git a/drivers/hid/i2c-hid/i2c-hid-core.c b/drivers/hid/i2c-hid/i2c-hid-core.c index 4f5f7574c823..57f109bc8ec9 100644 --- a/drivers/hid/i2c-hid/i2c-hid-core.c +++ b/drivers/hid/i2c-hid/i2c-hid-core.c @@ -88,7 +88,6 @@ struct i2c_hid_cmd { unsigned int registerIndex; __u8 opcode; unsigned int length; - bool wait; }; union command { @@ -114,8 +113,7 @@ static const struct i2c_hid_cmd hid_report_descr_cmd = { .opcode = 0x00, .length = 2 }; /* commands */ -static const struct i2c_hid_cmd hid_reset_cmd = { I2C_HID_CMD(0x01), - .wait = true }; +static const struct i2c_hid_cmd hid_reset_cmd = { I2C_HID_CMD(0x01) }; static const struct i2c_hid_cmd hid_get_report_cmd = { I2C_HID_CMD(0x02) }; static const struct i2c_hid_cmd hid_set_report_cmd = { I2C_HID_CMD(0x03) }; static const struct i2c_hid_cmd hid_set_power_cmd = { I2C_HID_CMD(0x08) }; @@ -220,7 +218,6 @@ static int __i2c_hid_command(struct i2c_hid *ihid, int msg_num = 1; int length = command->length; - bool wait = command->wait; unsigned int registerIndex = command->registerIndex; /* special case for hid_descr_cmd */ @@ -261,9 +258,6 @@ static int __i2c_hid_command(struct i2c_hid *ihid, set_bit(I2C_HID_READ_PENDING, &ihid->flags); } - if (wait) - set_bit(I2C_HID_RESET_PENDING, &ihid->flags); - ret = i2c_transfer(client->adapter, msg, msg_num); if (data_len > 0) @@ -272,20 +266,7 @@ static int __i2c_hid_command(struct i2c_hid *ihid, if (ret != msg_num) return ret < 0 ? ret : -EIO; - ret = 0; - - if (wait && (ihid->quirks & I2C_HID_QUIRK_NO_IRQ_AFTER_RESET)) { - msleep(100); - } else if (wait) { - i2c_hid_dbg(ihid, "%s: waiting...\n", __func__); - if (!wait_event_timeout(ihid->wait, - !test_bit(I2C_HID_RESET_PENDING, &ihid->flags), - msecs_to_jiffies(5000))) - ret = -ENODATA; - i2c_hid_dbg(ihid, "%s: finished.\n", __func__); - } - - return ret; + return 0; } static int i2c_hid_command(struct i2c_hid *ihid, @@ -432,6 +413,39 @@ static int i2c_hid_set_power(struct i2c_hid *ihid, int power_state) return ret; } +static int i2c_hid_execute_reset(struct i2c_hid *ihid) +{ + int ret; + + i2c_hid_dbg(ihid, "resetting...\n"); + + set_bit(I2C_HID_RESET_PENDING, &ihid->flags); + + ret = i2c_hid_command(ihid, &hid_reset_cmd, NULL, 0); + if (ret) { + dev_err(&ihid->client->dev, "failed to reset device.\n"); + goto out; + } + + if (ihid->quirks & I2C_HID_QUIRK_NO_IRQ_AFTER_RESET) { + msleep(100); + goto out; + } + + i2c_hid_dbg(ihid, "%s: waiting...\n", __func__); + if (!wait_event_timeout(ihid->wait, + !test_bit(I2C_HID_RESET_PENDING, &ihid->flags), + msecs_to_jiffies(5000))) { + ret = -ENODATA; + goto out; + } + i2c_hid_dbg(ihid, "%s: finished.\n", __func__); + +out: + clear_bit(I2C_HID_RESET_PENDING, &ihid->flags); + return ret; +} + static int i2c_hid_hwreset(struct i2c_hid *ihid) { int ret; @@ -449,11 +463,10 @@ static int i2c_hid_hwreset(struct i2c_hid *ihid) if (ret) goto out_unlock; - i2c_hid_dbg(ihid, "resetting...\n"); - - ret = i2c_hid_command(ihid, &hid_reset_cmd, NULL, 0); + ret = i2c_hid_execute_reset(ihid); if (ret) { - dev_err(&ihid->client->dev, "failed to reset device.\n"); + dev_err(&ihid->client->dev, + "failed to reset device: %d\n", ret); i2c_hid_set_power(ihid, I2C_HID_PWR_SLEEP); goto out_unlock; } From dbe0dd5fd2e0c687ad50fc590d5a90400f26adcc Mon Sep 17 00:00:00 2001 From: Dmitry Torokhov Date: Mon, 17 Jan 2022 23:26:21 -0800 Subject: [PATCH 038/137] HID: i2c-hid: explicitly code setting and sending reports Instead of relying on __i2c_hid_command() that tries to handle all commands and because of that is very complicated, let's define a new dumb helper i2c_hid_xfer() that actually transfers (write and read) data, and use it when sending and setting reports. By doing that we can save on number of copy operations we have to execute, and make logic of sending reports much clearer. Signed-off-by: Dmitry Torokhov Tested-by: Benjamin Tissoires Signed-off-by: Jiri Kosina --- drivers/hid/i2c-hid/i2c-hid-core.c | 269 ++++++++++++++++------------- 1 file changed, 151 insertions(+), 118 deletions(-) diff --git a/drivers/hid/i2c-hid/i2c-hid-core.c b/drivers/hid/i2c-hid/i2c-hid-core.c index 57f109bc8ec9..856778d8592a 100644 --- a/drivers/hid/i2c-hid/i2c-hid-core.c +++ b/drivers/hid/i2c-hid/i2c-hid-core.c @@ -35,6 +35,7 @@ #include #include #include +#include #include "../hid-ids.h" #include "i2c-hid.h" @@ -47,6 +48,15 @@ #define I2C_HID_QUIRK_BAD_INPUT_SIZE BIT(6) #define I2C_HID_QUIRK_NO_WAKEUP_AFTER_RESET BIT(7) +/* Command opcodes */ +#define I2C_HID_OPCODE_RESET 0x01 +#define I2C_HID_OPCODE_GET_REPORT 0x02 +#define I2C_HID_OPCODE_SET_REPORT 0x03 +#define I2C_HID_OPCODE_GET_IDLE 0x04 +#define I2C_HID_OPCODE_SET_IDLE 0x05 +#define I2C_HID_OPCODE_GET_PROTOCOL 0x06 +#define I2C_HID_OPCODE_SET_PROTOCOL 0x07 +#define I2C_HID_OPCODE_SET_POWER 0x08 /* flags */ #define I2C_HID_STARTED 0 @@ -90,16 +100,6 @@ struct i2c_hid_cmd { unsigned int length; }; -union command { - u8 data[0]; - struct cmd { - __le16 reg; - __u8 reportTypeID; - __u8 opcode; - __u8 reportID; - } __packed c; -}; - #define I2C_HID_CMD(opcode_) \ .opcode = opcode_, .length = 4, \ .registerIndex = offsetof(struct i2c_hid_desc, wCommandRegister) @@ -115,9 +115,7 @@ static const struct i2c_hid_cmd hid_report_descr_cmd = { /* commands */ static const struct i2c_hid_cmd hid_reset_cmd = { I2C_HID_CMD(0x01) }; static const struct i2c_hid_cmd hid_get_report_cmd = { I2C_HID_CMD(0x02) }; -static const struct i2c_hid_cmd hid_set_report_cmd = { I2C_HID_CMD(0x03) }; static const struct i2c_hid_cmd hid_set_power_cmd = { I2C_HID_CMD(0x08) }; -static const struct i2c_hid_cmd hid_no_cmd = { .length = 0 }; /* * These definitions are not used here, but are defined by the spec. @@ -144,7 +142,6 @@ struct i2c_hid { u8 *inbuf; /* Input buffer */ u8 *rawbuf; /* Raw Input buffer */ u8 *cmdbuf; /* Command buffer */ - u8 *argsbuf; /* Command arguments buffer */ unsigned long flags; /* device flags */ unsigned long quirks; /* Various quirks */ @@ -206,67 +203,90 @@ static u32 i2c_hid_lookup_quirk(const u16 idVendor, const u16 idProduct) return quirks; } +static int i2c_hid_xfer(struct i2c_hid *ihid, + u8 *send_buf, int send_len, u8 *recv_buf, int recv_len) +{ + struct i2c_client *client = ihid->client; + struct i2c_msg msgs[2] = { 0 }; + int n = 0; + int ret; + + if (send_len) { + i2c_hid_dbg(ihid, "%s: cmd=%*ph\n", + __func__, send_len, send_buf); + + msgs[n].addr = client->addr; + msgs[n].flags = client->flags & I2C_M_TEN; + msgs[n].len = send_len; + msgs[n].buf = send_buf; + n++; + } + + if (recv_len) { + msgs[n].addr = client->addr; + msgs[n].flags = (client->flags & I2C_M_TEN) | I2C_M_RD; + msgs[n].len = recv_len; + msgs[n].buf = recv_buf; + n++; + + set_bit(I2C_HID_READ_PENDING, &ihid->flags); + } + + ret = i2c_transfer(client->adapter, msgs, n); + + if (recv_len) + clear_bit(I2C_HID_READ_PENDING, &ihid->flags); + + if (ret != n) + return ret < 0 ? ret : -EIO; + + return 0; +} + +static size_t i2c_hid_encode_command(u8 *buf, u8 opcode, + int report_type, int report_id) +{ + size_t length = 0; + + if (report_id < 0x0F) { + buf[length++] = report_type << 4 | report_id; + buf[length++] = opcode; + } else { + buf[length++] = report_type << 4 | 0x0F; + buf[length++] = opcode; + buf[length++] = report_id; + } + + return length; +} + static int __i2c_hid_command(struct i2c_hid *ihid, const struct i2c_hid_cmd *command, u8 reportID, u8 reportType, u8 *args, int args_len, unsigned char *buf_recv, int data_len) { - struct i2c_client *client = ihid->client; - union command *cmd = (union command *)ihid->cmdbuf; - int ret; - struct i2c_msg msg[2]; - int msg_num = 1; - int length = command->length; unsigned int registerIndex = command->registerIndex; /* special case for hid_descr_cmd */ if (command == &hid_descr_cmd) { - cmd->c.reg = ihid->wHIDDescRegister; + *(__le16 *)ihid->cmdbuf = ihid->wHIDDescRegister; } else { - cmd->data[0] = ihid->hdesc_buffer[registerIndex]; - cmd->data[1] = ihid->hdesc_buffer[registerIndex + 1]; + ihid->cmdbuf[0] = ihid->hdesc_buffer[registerIndex]; + ihid->cmdbuf[1] = ihid->hdesc_buffer[registerIndex + 1]; } if (length > 2) { - cmd->c.opcode = command->opcode; - if (reportID < 0x0F) { - cmd->c.reportTypeID = reportType << 4 | reportID; - } else { - cmd->c.reportTypeID = reportType << 4 | 0x0F; - cmd->c.reportID = reportID; - length++; - } + length = sizeof(__le16) + /* register */ + i2c_hid_encode_command(ihid->cmdbuf + sizeof(__le16), + command->opcode, + reportType, reportID); } - memcpy(cmd->data + length, args, args_len); + memcpy(ihid->cmdbuf + length, args, args_len); length += args_len; - i2c_hid_dbg(ihid, "%s: cmd=%*ph\n", __func__, length, cmd->data); - - msg[0].addr = client->addr; - msg[0].flags = client->flags & I2C_M_TEN; - msg[0].len = length; - msg[0].buf = cmd->data; - if (data_len > 0) { - msg[1].addr = client->addr; - msg[1].flags = client->flags & I2C_M_TEN; - msg[1].flags |= I2C_M_RD; - msg[1].len = data_len; - msg[1].buf = buf_recv; - msg_num = 2; - set_bit(I2C_HID_READ_PENDING, &ihid->flags); - } - - ret = i2c_transfer(client->adapter, msg, msg_num); - - if (data_len > 0) - clear_bit(I2C_HID_READ_PENDING, &ihid->flags); - - if (ret != msg_num) - return ret < 0 ? ret : -EIO; - - return 0; + return i2c_hid_xfer(ihid, ihid->cmdbuf, length, buf_recv, data_len); } static int i2c_hid_command(struct i2c_hid *ihid, @@ -301,70 +321,81 @@ static int i2c_hid_get_report(struct i2c_hid *ihid, u8 reportType, return 0; } +static size_t i2c_hid_format_report(u8 *buf, int report_id, + const u8 *data, size_t size) +{ + size_t length = sizeof(__le16); /* reserve space to store size */ + + if (report_id) + buf[length++] = report_id; + + memcpy(buf + length, data, size); + length += size; + + /* Store overall size in the beginning of the buffer */ + put_unaligned_le16(length, buf); + + return length; +} + /** * i2c_hid_set_or_send_report: forward an incoming report to the device * @ihid: the i2c hid device - * @reportType: 0x03 for HID_FEATURE_REPORT ; 0x02 for HID_OUTPUT_REPORT - * @reportID: the report ID + * @report_type: 0x03 for HID_FEATURE_REPORT ; 0x02 for HID_OUTPUT_REPORT + * @report_id: the report ID * @buf: the actual data to transfer, without the report ID * @data_len: size of buf - * @use_data: true: use SET_REPORT HID command, false: send plain OUTPUT report + * @do_set: true: use SET_REPORT HID command, false: send plain OUTPUT report */ -static int i2c_hid_set_or_send_report(struct i2c_hid *ihid, u8 reportType, - u8 reportID, unsigned char *buf, size_t data_len, bool use_data) +static int i2c_hid_set_or_send_report(struct i2c_hid *ihid, + u8 report_type, u8 report_id, + const u8 *buf, size_t data_len, + bool do_set) { - u8 *args = ihid->argsbuf; - const struct i2c_hid_cmd *hidcmd; - int ret; - u16 dataRegister = le16_to_cpu(ihid->hdesc.wDataRegister); - u16 outputRegister = le16_to_cpu(ihid->hdesc.wOutputRegister); - u16 maxOutputLength = le16_to_cpu(ihid->hdesc.wMaxOutputLength); - u16 size; - int args_len; - int index = 0; + size_t length = 0; + int error; i2c_hid_dbg(ihid, "%s\n", __func__); if (data_len > ihid->bufsize) return -EINVAL; - size = 2 /* size */ + - (reportID ? 1 : 0) /* reportID */ + - data_len /* buf */; - args_len = 2 /* dataRegister */ + - size /* args */; - - if (!use_data && maxOutputLength == 0) + if (!do_set && le16_to_cpu(ihid->hdesc.wMaxOutputLength) == 0) return -ENOSYS; - /* - * use the data register for feature reports or if the device does not - * support the output register - */ - if (use_data) { - args[index++] = dataRegister & 0xFF; - args[index++] = dataRegister >> 8; - hidcmd = &hid_set_report_cmd; + if (do_set) { + /* Command register goes first */ + *(__le16 *)ihid->cmdbuf = ihid->hdesc.wCommandRegister; + length += sizeof(__le16); + /* Next is SET_REPORT command */ + length += i2c_hid_encode_command(ihid->cmdbuf + length, + I2C_HID_OPCODE_SET_REPORT, + report_type, report_id); + /* + * Report data will go into the data register. Because + * command can be either 2 or 3 bytes destination for + * the data register may be not aligned. + */ + put_unaligned_le16(le16_to_cpu(ihid->hdesc.wDataRegister), + ihid->cmdbuf + length); + length += sizeof(__le16); } else { - args[index++] = outputRegister & 0xFF; - args[index++] = outputRegister >> 8; - hidcmd = &hid_no_cmd; + /* + * With simple "send report" all data goes into the output + * register. + */ + *(__le16 *)ihid->cmdbuf = ihid->hdesc.wOutputRegister;; + length += sizeof(__le16); } - args[index++] = size & 0xFF; - args[index++] = size >> 8; + length += i2c_hid_format_report(ihid->cmdbuf + length, + report_id, buf, data_len); - if (reportID) - args[index++] = reportID; - - memcpy(&args[index], buf, data_len); - - ret = __i2c_hid_command(ihid, hidcmd, reportID, - reportType, args, args_len, NULL, 0); - if (ret) { + error = i2c_hid_xfer(ihid, ihid->cmdbuf, length, NULL, 0); + if (error) { dev_err(&ihid->client->dev, - "failed to set a report to device.\n"); - return ret; + "failed to set a report to device: %d\n", error); + return error; } return data_len; @@ -578,31 +609,33 @@ static void i2c_hid_free_buffers(struct i2c_hid *ihid) { kfree(ihid->inbuf); kfree(ihid->rawbuf); - kfree(ihid->argsbuf); kfree(ihid->cmdbuf); ihid->inbuf = NULL; ihid->rawbuf = NULL; ihid->cmdbuf = NULL; - ihid->argsbuf = NULL; ihid->bufsize = 0; } static int i2c_hid_alloc_buffers(struct i2c_hid *ihid, size_t report_size) { - /* the worst case is computed from the set_report command with a - * reportID > 15 and the maximum report length */ - int args_len = sizeof(__u8) + /* ReportID */ - sizeof(__u8) + /* optional ReportID byte */ - sizeof(__u16) + /* data register */ - sizeof(__u16) + /* size of the report */ - report_size; /* report */ + /* + * The worst case is computed from the set_report command with a + * reportID > 15 and the maximum report length. + */ + int cmd_len = sizeof(__le16) + /* command register */ + sizeof(u8) + /* encoded report type/ID */ + sizeof(u8) + /* opcode */ + sizeof(u8) + /* optional 3rd byte report ID */ + sizeof(__le16) + /* data register */ + sizeof(__le16) + /* report data size */ + sizeof(u8) + /* report ID if numbered report */ + report_size; ihid->inbuf = kzalloc(report_size, GFP_KERNEL); ihid->rawbuf = kzalloc(report_size, GFP_KERNEL); - ihid->argsbuf = kzalloc(args_len, GFP_KERNEL); - ihid->cmdbuf = kzalloc(sizeof(union command) + args_len, GFP_KERNEL); + ihid->cmdbuf = kzalloc(cmd_len, GFP_KERNEL); - if (!ihid->inbuf || !ihid->rawbuf || !ihid->argsbuf || !ihid->cmdbuf) { + if (!ihid->inbuf || !ihid->rawbuf || !ihid->cmdbuf) { i2c_hid_free_buffers(ihid); return -ENOMEM; } @@ -662,8 +695,9 @@ static int i2c_hid_get_raw_report(struct hid_device *hid, return count; } -static int i2c_hid_output_raw_report(struct hid_device *hid, __u8 *buf, - size_t count, unsigned char report_type, bool use_data) +static int i2c_hid_output_raw_report(struct hid_device *hid, + const u8 *buf, size_t count, + u8 report_type, bool do_set) { struct i2c_client *client = hid->driver_data; struct i2c_hid *ihid = i2c_get_clientdata(client); @@ -684,7 +718,7 @@ static int i2c_hid_output_raw_report(struct hid_device *hid, __u8 *buf, */ ret = i2c_hid_set_or_send_report(ihid, report_type == HID_FEATURE_REPORT ? 0x03 : 0x02, - report_id, buf + 1, count - 1, use_data); + report_id, buf + 1, count - 1, do_set); if (ret >= 0) ret++; /* add report_id to the number of transferred bytes */ @@ -694,11 +728,10 @@ static int i2c_hid_output_raw_report(struct hid_device *hid, __u8 *buf, return ret; } -static int i2c_hid_output_report(struct hid_device *hid, __u8 *buf, - size_t count) +static int i2c_hid_output_report(struct hid_device *hid, u8 *buf, size_t count) { return i2c_hid_output_raw_report(hid, buf, count, HID_OUTPUT_REPORT, - false); + false); } static int i2c_hid_raw_request(struct hid_device *hid, unsigned char reportnum, From 8399bd01026eab803d6ebb5372cf28b5e1f335f8 Mon Sep 17 00:00:00 2001 From: Dmitry Torokhov Date: Mon, 17 Jan 2022 23:26:22 -0800 Subject: [PATCH 039/137] HID: i2c-hid: define i2c_hid_read_register() and use it Handling simple read of device registers in __i2c_hid_command() makes it too complicated and the need of special handling for the HID descriptor register adds even more complexity. Instead, let's create simple i2c_hid_read_register() helper on base of i2c_hid_xfer() and use it. Signed-off-by: Dmitry Torokhov Tested-by: Benjamin Tissoires Signed-off-by: Jiri Kosina --- drivers/hid/i2c-hid/i2c-hid-core.c | 45 +++++++++++++++--------------- 1 file changed, 22 insertions(+), 23 deletions(-) diff --git a/drivers/hid/i2c-hid/i2c-hid-core.c b/drivers/hid/i2c-hid/i2c-hid-core.c index 856778d8592a..32d275d2d5dc 100644 --- a/drivers/hid/i2c-hid/i2c-hid-core.c +++ b/drivers/hid/i2c-hid/i2c-hid-core.c @@ -104,14 +104,6 @@ struct i2c_hid_cmd { .opcode = opcode_, .length = 4, \ .registerIndex = offsetof(struct i2c_hid_desc, wCommandRegister) -/* fetch HID descriptor */ -static const struct i2c_hid_cmd hid_descr_cmd = { .length = 2 }; -/* fetch report descriptors */ -static const struct i2c_hid_cmd hid_report_descr_cmd = { - .registerIndex = offsetof(struct i2c_hid_desc, - wReportDescRegister), - .opcode = 0x00, - .length = 2 }; /* commands */ static const struct i2c_hid_cmd hid_reset_cmd = { I2C_HID_CMD(0x01) }; static const struct i2c_hid_cmd hid_get_report_cmd = { I2C_HID_CMD(0x02) }; @@ -243,6 +235,14 @@ static int i2c_hid_xfer(struct i2c_hid *ihid, return 0; } +static int i2c_hid_read_register(struct i2c_hid *ihid, __le16 reg, + void *buf, size_t len) +{ + *(__le16 *)ihid->cmdbuf = reg; + + return i2c_hid_xfer(ihid, ihid->cmdbuf, sizeof(__le16), buf, len); +} + static size_t i2c_hid_encode_command(u8 *buf, u8 opcode, int report_type, int report_id) { @@ -268,13 +268,8 @@ static int __i2c_hid_command(struct i2c_hid *ihid, int length = command->length; unsigned int registerIndex = command->registerIndex; - /* special case for hid_descr_cmd */ - if (command == &hid_descr_cmd) { - *(__le16 *)ihid->cmdbuf = ihid->wHIDDescRegister; - } else { - ihid->cmdbuf[0] = ihid->hdesc_buffer[registerIndex]; - ihid->cmdbuf[1] = ihid->hdesc_buffer[registerIndex + 1]; - } + ihid->cmdbuf[0] = ihid->hdesc_buffer[registerIndex]; + ihid->cmdbuf[1] = ihid->hdesc_buffer[registerIndex + 1]; if (length > 2) { length = sizeof(__le16) + /* register */ @@ -794,8 +789,9 @@ static int i2c_hid_parse(struct hid_device *hid) i2c_hid_dbg(ihid, "asking HID report descriptor\n"); - ret = i2c_hid_command(ihid, &hid_report_descr_cmd, - rdesc, rsize); + ret = i2c_hid_read_register(ihid, + ihid->hdesc.wReportDescRegister, + rdesc, rsize); if (ret) { hid_err(hid, "reading report descriptor failed\n"); kfree(rdesc); @@ -905,7 +901,7 @@ static int i2c_hid_fetch_hid_descriptor(struct i2c_hid *ihid) struct i2c_client *client = ihid->client; struct i2c_hid_desc *hdesc = &ihid->hdesc; unsigned int dsize; - int ret; + int error; /* i2c hid fetch using a fixed descriptor size (30 bytes) */ if (i2c_hid_get_dmi_i2c_hid_desc_override(client->name)) { @@ -914,11 +910,14 @@ static int i2c_hid_fetch_hid_descriptor(struct i2c_hid *ihid) *i2c_hid_get_dmi_i2c_hid_desc_override(client->name); } else { i2c_hid_dbg(ihid, "Fetching the HID descriptor\n"); - ret = i2c_hid_command(ihid, &hid_descr_cmd, - ihid->hdesc_buffer, - sizeof(struct i2c_hid_desc)); - if (ret) { - dev_err(&ihid->client->dev, "hid_descr_cmd failed\n"); + error = i2c_hid_read_register(ihid, + ihid->wHIDDescRegister, + &ihid->hdesc, + sizeof(ihid->hdesc)); + if (error) { + dev_err(&ihid->client->dev, + "failed to fetch HID descriptor: %d\n", + error); return -ENODEV; } } From acb8dd95974dda4b6072410bfe09bc83d2c22d23 Mon Sep 17 00:00:00 2001 From: Dmitry Torokhov Date: Mon, 17 Jan 2022 23:26:23 -0800 Subject: [PATCH 040/137] HID: i2c-hid: create a helper for SET_POWER command Another case where creating a dedicated helper allows for cleaner code that shows exactly what communication happens with the device when toggling its power. Signed-off-by: Dmitry Torokhov Tested-by: Benjamin Tissoires Signed-off-by: Jiri Kosina --- drivers/hid/i2c-hid/i2c-hid-core.c | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/drivers/hid/i2c-hid/i2c-hid-core.c b/drivers/hid/i2c-hid/i2c-hid-core.c index 32d275d2d5dc..7b7127534e24 100644 --- a/drivers/hid/i2c-hid/i2c-hid-core.c +++ b/drivers/hid/i2c-hid/i2c-hid-core.c @@ -107,7 +107,6 @@ struct i2c_hid_cmd { /* commands */ static const struct i2c_hid_cmd hid_reset_cmd = { I2C_HID_CMD(0x01) }; static const struct i2c_hid_cmd hid_get_report_cmd = { I2C_HID_CMD(0x02) }; -static const struct i2c_hid_cmd hid_set_power_cmd = { I2C_HID_CMD(0x08) }; /* * These definitions are not used here, but are defined by the spec. @@ -396,6 +395,22 @@ static int i2c_hid_set_or_send_report(struct i2c_hid *ihid, return data_len; } +static int i2c_hid_set_power_command(struct i2c_hid *ihid, int power_state) +{ + size_t length; + + /* SET_POWER uses command register */ + *(__le16 *)ihid->cmdbuf = ihid->hdesc.wCommandRegister; + length = sizeof(__le16); + + /* Now the command itself */ + length += i2c_hid_encode_command(ihid->cmdbuf + length, + I2C_HID_OPCODE_SET_POWER, + 0, power_state); + + return i2c_hid_xfer(ihid, ihid->cmdbuf, length, NULL, 0); +} + static int i2c_hid_set_power(struct i2c_hid *ihid, int power_state) { int ret; @@ -409,15 +424,14 @@ static int i2c_hid_set_power(struct i2c_hid *ihid, int power_state) */ if (power_state == I2C_HID_PWR_ON && ihid->quirks & I2C_HID_QUIRK_SET_PWR_WAKEUP_DEV) { - ret = i2c_hid_command(ihid, &hid_set_power_cmd, NULL, 0); + ret = i2c_hid_set_power_command(ihid, I2C_HID_PWR_ON); /* Device was already activated */ if (!ret) goto set_pwr_exit; } - ret = __i2c_hid_command(ihid, &hid_set_power_cmd, power_state, - 0, NULL, 0, NULL, 0); + ret = i2c_hid_set_power_command(ihid, power_state); if (ret) dev_err(&ihid->client->dev, "failed to change power setting.\n"); From 50c5249fcafc9f9c64e4a367e336c2e17f1c978f Mon Sep 17 00:00:00 2001 From: Dmitry Torokhov Date: Mon, 17 Jan 2022 23:26:24 -0800 Subject: [PATCH 041/137] HID: i2c-hid: convert i2c_hid_execute_reset() to use i2c_hid_xfer() This will allow us to drop i2c_hid_command() wrapper and get close to removing __i2c_hid_command(). Signed-off-by: Dmitry Torokhov Tested-by: Benjamin Tissoires Signed-off-by: Jiri Kosina --- drivers/hid/i2c-hid/i2c-hid-core.c | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/drivers/hid/i2c-hid/i2c-hid-core.c b/drivers/hid/i2c-hid/i2c-hid-core.c index 7b7127534e24..30b545c67eb8 100644 --- a/drivers/hid/i2c-hid/i2c-hid-core.c +++ b/drivers/hid/i2c-hid/i2c-hid-core.c @@ -105,7 +105,6 @@ struct i2c_hid_cmd { .registerIndex = offsetof(struct i2c_hid_desc, wCommandRegister) /* commands */ -static const struct i2c_hid_cmd hid_reset_cmd = { I2C_HID_CMD(0x01) }; static const struct i2c_hid_cmd hid_get_report_cmd = { I2C_HID_CMD(0x02) }; /* @@ -283,14 +282,6 @@ static int __i2c_hid_command(struct i2c_hid *ihid, return i2c_hid_xfer(ihid, ihid->cmdbuf, length, buf_recv, data_len); } -static int i2c_hid_command(struct i2c_hid *ihid, - const struct i2c_hid_cmd *command, - unsigned char *buf_recv, int data_len) -{ - return __i2c_hid_command(ihid, command, 0, 0, NULL, 0, - buf_recv, data_len); -} - static int i2c_hid_get_report(struct i2c_hid *ihid, u8 reportType, u8 reportID, unsigned char *buf_recv, int data_len) { @@ -455,13 +446,21 @@ static int i2c_hid_set_power(struct i2c_hid *ihid, int power_state) static int i2c_hid_execute_reset(struct i2c_hid *ihid) { + size_t length = 0; int ret; i2c_hid_dbg(ihid, "resetting...\n"); + /* Prepare reset command. Command register goes first. */ + *(__le16 *)ihid->cmdbuf = ihid->hdesc.wCommandRegister; + length += sizeof(__le16); + /* Next is RESET command itself */ + length += i2c_hid_encode_command(ihid->cmdbuf + length, + I2C_HID_OPCODE_RESET, 0, 0); + set_bit(I2C_HID_RESET_PENDING, &ihid->flags); - ret = i2c_hid_command(ihid, &hid_reset_cmd, NULL, 0); + ret = i2c_hid_xfer(ihid, ihid->cmdbuf, length, NULL, 0); if (ret) { dev_err(&ihid->client->dev, "failed to reset device.\n"); goto out; From 85df713377ddc0482071c3e6b64c37bd1e48f1f1 Mon Sep 17 00:00:00 2001 From: Dmitry Torokhov Date: Mon, 17 Jan 2022 23:26:25 -0800 Subject: [PATCH 042/137] HID: i2c-hid: rework i2c_hid_get_report() to use i2c_hid_xfer() Explicitly prepare command for i2c_hid_get_report() which makes the logic clearer and allows us to get rid of __i2c_hid_command() and related command definitions. Signed-off-by: Dmitry Torokhov Tested-by: Benjamin Tissoires Signed-off-by: Jiri Kosina --- drivers/hid/i2c-hid/i2c-hid-core.c | 156 ++++++++++++----------------- 1 file changed, 62 insertions(+), 94 deletions(-) diff --git a/drivers/hid/i2c-hid/i2c-hid-core.c b/drivers/hid/i2c-hid/i2c-hid-core.c index 30b545c67eb8..67c80bffc0db 100644 --- a/drivers/hid/i2c-hid/i2c-hid-core.c +++ b/drivers/hid/i2c-hid/i2c-hid-core.c @@ -94,29 +94,6 @@ struct i2c_hid_desc { __le32 reserved; } __packed; -struct i2c_hid_cmd { - unsigned int registerIndex; - __u8 opcode; - unsigned int length; -}; - -#define I2C_HID_CMD(opcode_) \ - .opcode = opcode_, .length = 4, \ - .registerIndex = offsetof(struct i2c_hid_desc, wCommandRegister) - -/* commands */ -static const struct i2c_hid_cmd hid_get_report_cmd = { I2C_HID_CMD(0x02) }; - -/* - * These definitions are not used here, but are defined by the spec. - * Keeping them here for documentation purposes. - * - * static const struct i2c_hid_cmd hid_get_idle_cmd = { I2C_HID_CMD(0x04) }; - * static const struct i2c_hid_cmd hid_set_idle_cmd = { I2C_HID_CMD(0x05) }; - * static const struct i2c_hid_cmd hid_get_protocol_cmd = { I2C_HID_CMD(0x06) }; - * static const struct i2c_hid_cmd hid_set_protocol_cmd = { I2C_HID_CMD(0x07) }; - */ - /* The main device structure */ struct i2c_hid { struct i2c_client *client; /* i2c client */ @@ -258,52 +235,62 @@ static size_t i2c_hid_encode_command(u8 *buf, u8 opcode, return length; } -static int __i2c_hid_command(struct i2c_hid *ihid, - const struct i2c_hid_cmd *command, u8 reportID, - u8 reportType, u8 *args, int args_len, - unsigned char *buf_recv, int data_len) +static int i2c_hid_get_report(struct i2c_hid *ihid, + u8 report_type, u8 report_id, + u8 *recv_buf, size_t recv_len) { - int length = command->length; - unsigned int registerIndex = command->registerIndex; - - ihid->cmdbuf[0] = ihid->hdesc_buffer[registerIndex]; - ihid->cmdbuf[1] = ihid->hdesc_buffer[registerIndex + 1]; - - if (length > 2) { - length = sizeof(__le16) + /* register */ - i2c_hid_encode_command(ihid->cmdbuf + sizeof(__le16), - command->opcode, - reportType, reportID); - } - - memcpy(ihid->cmdbuf + length, args, args_len); - length += args_len; - - return i2c_hid_xfer(ihid, ihid->cmdbuf, length, buf_recv, data_len); -} - -static int i2c_hid_get_report(struct i2c_hid *ihid, u8 reportType, - u8 reportID, unsigned char *buf_recv, int data_len) -{ - u8 args[2]; - int ret; - int args_len = 0; - u16 readRegister = le16_to_cpu(ihid->hdesc.wDataRegister); + size_t length = 0; + size_t ret_count; + int error; i2c_hid_dbg(ihid, "%s\n", __func__); - args[args_len++] = readRegister & 0xFF; - args[args_len++] = readRegister >> 8; + /* Command register goes first */ + *(__le16 *)ihid->cmdbuf = ihid->hdesc.wCommandRegister; + length += sizeof(__le16); + /* Next is GET_REPORT command */ + length += i2c_hid_encode_command(ihid->cmdbuf + length, + I2C_HID_OPCODE_GET_REPORT, + report_type, report_id); + /* + * Device will send report data through data register. Because + * command can be either 2 or 3 bytes destination for the data + * register may be not aligned. + */ + put_unaligned_le16(le16_to_cpu(ihid->hdesc.wDataRegister), + ihid->cmdbuf + length); + length += sizeof(__le16); - ret = __i2c_hid_command(ihid, &hid_get_report_cmd, reportID, - reportType, args, args_len, buf_recv, data_len); - if (ret) { + /* + * In addition to report data device will supply data length + * in the first 2 bytes of the response, so adjust . + */ + error = i2c_hid_xfer(ihid, ihid->cmdbuf, length, + ihid->rawbuf, recv_len + sizeof(__le16)); + if (error) { dev_err(&ihid->client->dev, - "failed to retrieve report from device.\n"); - return ret; + "failed to set a report to device: %d\n", error); + return error; } - return 0; + /* The buffer is sufficiently aligned */ + ret_count = le16_to_cpup((__le16 *)ihid->rawbuf); + + /* Check for empty report response */ + if (ret_count <= sizeof(__le16)) + return 0; + + recv_len = min(recv_len, ret_count - sizeof(__le16)); + memcpy(recv_buf, ihid->rawbuf + sizeof(__le16), recv_len); + + if (report_id && recv_len != 0 && recv_buf[0] != report_id) { + dev_err(&ihid->client->dev, + "device returned incorrect report (%d vs %d expected)\n", + recv_buf[0], report_id); + return -EINVAL; + } + + return recv_len; } static size_t i2c_hid_format_report(u8 *buf, int report_id, @@ -654,13 +641,12 @@ static int i2c_hid_alloc_buffers(struct i2c_hid *ihid, size_t report_size) } static int i2c_hid_get_raw_report(struct hid_device *hid, - unsigned char report_number, __u8 *buf, size_t count, - unsigned char report_type) + u8 report_type, u8 report_id, + u8 *buf, size_t count) { struct i2c_client *client = hid->driver_data; struct i2c_hid *ihid = i2c_get_clientdata(client); - size_t ret_count, ask_count; - int ret; + int ret_count; if (report_type == HID_OUTPUT_REPORT) return -EINVAL; @@ -670,42 +656,24 @@ static int i2c_hid_get_raw_report(struct hid_device *hid, * not have the report ID that the upper layers expect, so we need * to stash it the buffer ourselves and adjust the data size. */ - if (!report_number) { + if (!report_id) { buf[0] = 0; buf++; count--; } - /* +2 bytes to include the size of the reply in the query buffer */ - ask_count = min(count + 2, (size_t)ihid->bufsize); - - ret = i2c_hid_get_report(ihid, + ret_count = i2c_hid_get_report(ihid, report_type == HID_FEATURE_REPORT ? 0x03 : 0x01, - report_number, ihid->rawbuf, ask_count); + report_id, buf, count); - if (ret < 0) - return ret; + if (ret_count > 0 && !report_id) + ret_count++; - ret_count = ihid->rawbuf[0] | (ihid->rawbuf[1] << 8); - - if (ret_count <= 2) - return 0; - - ret_count = min(ret_count, ask_count); - - /* The query buffer contains the size, dropping it in the reply */ - count = min(count, ret_count - 2); - memcpy(buf, ihid->rawbuf + 2, count); - - if (!report_number) - count++; - - return count; + return ret_count; } -static int i2c_hid_output_raw_report(struct hid_device *hid, - const u8 *buf, size_t count, - u8 report_type, bool do_set) +static int i2c_hid_output_raw_report(struct hid_device *hid, u8 report_type, + const u8 *buf, size_t count, bool do_set) { struct i2c_client *client = hid->driver_data; struct i2c_hid *ihid = i2c_get_clientdata(client); @@ -738,7 +706,7 @@ static int i2c_hid_output_raw_report(struct hid_device *hid, static int i2c_hid_output_report(struct hid_device *hid, u8 *buf, size_t count) { - return i2c_hid_output_raw_report(hid, buf, count, HID_OUTPUT_REPORT, + return i2c_hid_output_raw_report(hid, HID_OUTPUT_REPORT, buf, count, false); } @@ -748,11 +716,11 @@ static int i2c_hid_raw_request(struct hid_device *hid, unsigned char reportnum, { switch (reqtype) { case HID_REQ_GET_REPORT: - return i2c_hid_get_raw_report(hid, reportnum, buf, len, rtype); + return i2c_hid_get_raw_report(hid, rtype, reportnum, buf, len); case HID_REQ_SET_REPORT: if (buf[0] != reportnum) return -EINVAL; - return i2c_hid_output_raw_report(hid, buf, len, rtype, true); + return i2c_hid_output_raw_report(hid, rtype, buf, len, true); default: return -EIO; } From 86fc3fd28157b6d4d044bdc00b3ee72899e554fc Mon Sep 17 00:00:00 2001 From: Dmitry Torokhov Date: Mon, 17 Jan 2022 23:26:26 -0800 Subject: [PATCH 043/137] HID: i2c-hid: use helpers to do endian conversion in i2c_hid_get_input() It is better to use helpers to do endian conversion as it documents and draws attention to it, and might be a bit more performant as well. Signed-off-by: Dmitry Torokhov Tested-by: Benjamin Tissoires Signed-off-by: Jiri Kosina --- drivers/hid/i2c-hid/i2c-hid-core.c | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/drivers/hid/i2c-hid/i2c-hid-core.c b/drivers/hid/i2c-hid/i2c-hid-core.c index 67c80bffc0db..a32502db15e4 100644 --- a/drivers/hid/i2c-hid/i2c-hid-core.c +++ b/drivers/hid/i2c-hid/i2c-hid-core.c @@ -508,9 +508,9 @@ static int i2c_hid_hwreset(struct i2c_hid *ihid) static void i2c_hid_get_input(struct i2c_hid *ihid) { + u16 size = le16_to_cpu(ihid->hdesc.wMaxInputLength); + u16 ret_size; int ret; - u32 ret_size; - int size = le16_to_cpu(ihid->hdesc.wMaxInputLength); if (size > ihid->bufsize) size = ihid->bufsize; @@ -525,8 +525,8 @@ static void i2c_hid_get_input(struct i2c_hid *ihid) return; } - ret_size = ihid->inbuf[0] | ihid->inbuf[1] << 8; - + /* Receiving buffer is properly aligned */ + ret_size = le16_to_cpup((__le16 *)ihid->inbuf); if (!ret_size) { /* host or device initiated RESET completed */ if (test_and_clear_bit(I2C_HID_RESET_PENDING, &ihid->flags)) @@ -534,19 +534,20 @@ static void i2c_hid_get_input(struct i2c_hid *ihid) return; } - if (ihid->quirks & I2C_HID_QUIRK_BOGUS_IRQ && ret_size == 0xffff) { - dev_warn_once(&ihid->client->dev, "%s: IRQ triggered but " - "there's no data\n", __func__); + if ((ihid->quirks & I2C_HID_QUIRK_BOGUS_IRQ) && ret_size == 0xffff) { + dev_warn_once(&ihid->client->dev, + "%s: IRQ triggered but there's no data\n", + __func__); return; } - if ((ret_size > size) || (ret_size < 2)) { + if (ret_size > size || ret_size < sizeof(__le16)) { if (ihid->quirks & I2C_HID_QUIRK_BAD_INPUT_SIZE) { - ihid->inbuf[0] = size & 0xff; - ihid->inbuf[1] = size >> 8; + *(__le16 *)ihid->inbuf = cpu_to_le16(size); ret_size = size; } else { - dev_err(&ihid->client->dev, "%s: incomplete report (%d/%d)\n", + dev_err(&ihid->client->dev, + "%s: incomplete report (%d/%d)\n", __func__, size, ret_size); return; } @@ -557,8 +558,9 @@ static void i2c_hid_get_input(struct i2c_hid *ihid) if (test_bit(I2C_HID_STARTED, &ihid->flags)) { pm_wakeup_event(&ihid->client->dev, 0); - hid_input_report(ihid->hid, HID_INPUT_REPORT, ihid->inbuf + 2, - ret_size - 2, 1); + hid_input_report(ihid->hid, HID_INPUT_REPORT, + ihid->inbuf + sizeof(__le16), + ret_size - sizeof(__le16), 1); } return; From 551117c52237e7d92bc050df961b6a63e86c5bcd Mon Sep 17 00:00:00 2001 From: Dmitry Torokhov Date: Mon, 17 Jan 2022 23:26:27 -0800 Subject: [PATCH 044/137] HID: i2c-hid: no longer need raw access to HID descriptor structure We can stop defining a union for HID descriptor data as we now only access individual members of it by names and using proper types instead of accessing by offset from the beginning of the data structure. Signed-off-by: Dmitry Torokhov Tested-by: Benjamin Tissoires Signed-off-by: Jiri Kosina --- drivers/hid/i2c-hid/i2c-hid-core.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/drivers/hid/i2c-hid/i2c-hid-core.c b/drivers/hid/i2c-hid/i2c-hid-core.c index a32502db15e4..f312d2afd319 100644 --- a/drivers/hid/i2c-hid/i2c-hid-core.c +++ b/drivers/hid/i2c-hid/i2c-hid-core.c @@ -98,10 +98,7 @@ struct i2c_hid_desc { struct i2c_hid { struct i2c_client *client; /* i2c client */ struct hid_device *hid; /* pointer to corresponding HID dev */ - union { - __u8 hdesc_buffer[sizeof(struct i2c_hid_desc)]; - struct i2c_hid_desc hdesc; /* the HID Descriptor */ - }; + struct i2c_hid_desc hdesc; /* the HID Descriptor */ __le16 wHIDDescRegister; /* location of the i2c * register of the HID * descriptor. */ @@ -923,7 +920,7 @@ static int i2c_hid_fetch_hid_descriptor(struct i2c_hid *ihid) "weird size of HID descriptor (%u)\n", dsize); return -ENODEV; } - i2c_hid_dbg(ihid, "HID Descriptor: %*ph\n", dsize, ihid->hdesc_buffer); + i2c_hid_dbg(ihid, "HID Descriptor: %*ph\n", dsize, &ihid->hdesc); return 0; } From 1c4d6cd4cb48ae5d097c610a5ca4bd15e9f41bfb Mon Sep 17 00:00:00 2001 From: Dmitry Torokhov Date: Mon, 17 Jan 2022 23:26:28 -0800 Subject: [PATCH 045/137] HID: i2c-hid: note that I2C xfer buffers are DMA-safe All I2C communications in the driver use driver-private buffers that are DMA-safe, so mark them as such. Signed-off-by: Dmitry Torokhov Tested-by: Benjamin Tissoires Signed-off-by: Jiri Kosina --- drivers/hid/i2c-hid/i2c-hid-core.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/hid/i2c-hid/i2c-hid-core.c b/drivers/hid/i2c-hid/i2c-hid-core.c index f312d2afd319..91b2fa0d33c7 100644 --- a/drivers/hid/i2c-hid/i2c-hid-core.c +++ b/drivers/hid/i2c-hid/i2c-hid-core.c @@ -180,7 +180,7 @@ static int i2c_hid_xfer(struct i2c_hid *ihid, __func__, send_len, send_buf); msgs[n].addr = client->addr; - msgs[n].flags = client->flags & I2C_M_TEN; + msgs[n].flags = (client->flags & I2C_M_TEN) | I2C_M_DMA_SAFE; msgs[n].len = send_len; msgs[n].buf = send_buf; n++; @@ -188,7 +188,8 @@ static int i2c_hid_xfer(struct i2c_hid *ihid, if (recv_len) { msgs[n].addr = client->addr; - msgs[n].flags = (client->flags & I2C_M_TEN) | I2C_M_RD; + msgs[n].flags = (client->flags & I2C_M_TEN) | + I2C_M_RD | I2C_M_DMA_SAFE; msgs[n].len = recv_len; msgs[n].buf = recv_buf; n++; From 2546c60004309ede8e2d1d5341e0decd90e057bf Mon Sep 17 00:00:00 2001 From: "David E. Box" Date: Fri, 11 Feb 2022 17:32:50 -0800 Subject: [PATCH 046/137] platform/x86: Add Intel Software Defined Silicon driver Intel Software Defined Silicon (SDSi) is a post manufacturing mechanism for activating additional silicon features. Features are enabled through a license activation process. The SDSi driver provides a per socket, sysfs attribute interface for applications to perform 3 main provisioning functions: 1. Provision an Authentication Key Certificate (AKC), a key written to internal NVRAM that is used to authenticate a capability specific activation payload. 2. Provision a Capability Activation Payload (CAP), a token authenticated using the AKC and applied to the CPU configuration to activate a new feature. 3. Read the SDSi State Certificate, containing the CPU configuration state. The operations perform function specific mailbox commands that forward the requests to SDSi hardware to perform authentication of the payloads and enable the silicon configuration (to be made available after power cycling). The SDSi device itself is enumerated as an auxiliary device from the intel_vsec driver and as such has a build dependency on CONFIG_INTEL_VSEC. Link: https://github.com/intel/intel-sdsi Signed-off-by: David E. Box Reviewed-by: Mark Gross Link: https://lore.kernel.org/r/20220212013252.1293396-2-david.e.box@linux.intel.com Reviewed-by: Hans de Goede Signed-off-by: Hans de Goede --- .../ABI/testing/sysfs-driver-intel_sdsi | 77 +++ MAINTAINERS | 5 + drivers/platform/x86/intel/Kconfig | 12 + drivers/platform/x86/intel/Makefile | 2 + drivers/platform/x86/intel/sdsi.c | 574 ++++++++++++++++++ drivers/platform/x86/intel/vsec.c | 12 +- 6 files changed, 681 insertions(+), 1 deletion(-) create mode 100644 Documentation/ABI/testing/sysfs-driver-intel_sdsi create mode 100644 drivers/platform/x86/intel/sdsi.c diff --git a/Documentation/ABI/testing/sysfs-driver-intel_sdsi b/Documentation/ABI/testing/sysfs-driver-intel_sdsi new file mode 100644 index 000000000000..ab122125ff9a --- /dev/null +++ b/Documentation/ABI/testing/sysfs-driver-intel_sdsi @@ -0,0 +1,77 @@ +What: /sys/bus/auxiliary/devices/intel_vsec.sdsi.X +Date: Feb 2022 +KernelVersion: 5.18 +Contact: "David E. Box" +Description: + This directory contains interface files for accessing Intel + Software Defined Silicon (SDSi) features on a CPU. X + represents the socket instance (though not the socket ID). + The socket ID is determined by reading the registers file + and decoding it per the specification. + + Some files communicate with SDSi hardware through a mailbox. + Should the operation fail, one of the following error codes + may be returned: + + Error Code Cause + ---------- ----- + EIO General mailbox failure. Log may indicate cause. + EBUSY Mailbox is owned by another agent. + EPERM SDSI capability is not enabled in hardware. + EPROTO Failure in mailbox protocol detected by driver. + See log for details. + EOVERFLOW For provision commands, the size of the data + exceeds what may be written. + ESPIPE Seeking is not allowed. + ETIMEDOUT Failure to complete mailbox transaction in time. + +What: /sys/bus/auxiliary/devices/intel_vsec.sdsi.X/guid +Date: Feb 2022 +KernelVersion: 5.18 +Contact: "David E. Box" +Description: + (RO) The GUID for the registers file. The GUID identifies + the layout of the registers file in this directory. + Information about the register layouts for a particular GUID + is available at http://github.com/intel/intel-sdsi + +What: /sys/bus/auxiliary/devices/intel_vsec.sdsi.X/registers +Date: Feb 2022 +KernelVersion: 5.18 +Contact: "David E. Box" +Description: + (RO) Contains information needed by applications to provision + a CPU and monitor status information. The layout of this file + is determined by the GUID in this directory. Information about + the layout for a particular GUID is available at + http://github.com/intel/intel-sdsi + +What: /sys/bus/auxiliary/devices/intel_vsec.sdsi.X/provision_akc +Date: Feb 2022 +KernelVersion: 5.18 +Contact: "David E. Box" +Description: + (WO) Used to write an Authentication Key Certificate (AKC) to + the SDSi NVRAM for the CPU. The AKC is used to authenticate a + Capability Activation Payload. Mailbox command. + +What: /sys/bus/auxiliary/devices/intel_vsec.sdsi.X/provision_cap +Date: Feb 2022 +KernelVersion: 5.18 +Contact: "David E. Box" +Description: + (WO) Used to write a Capability Activation Payload (CAP) to the + SDSi NVRAM for the CPU. CAPs are used to activate a given CPU + feature. A CAP is validated by SDSi hardware using a previously + provisioned AKC file. Upon successful authentication, the CPU + configuration is updated. A cold reboot is required to fully + activate the feature. Mailbox command. + +What: /sys/bus/auxiliary/devices/intel_vsec.sdsi.X/state_certificate +Date: Feb 2022 +KernelVersion: 5.18 +Contact: "David E. Box" +Description: + (RO) Used to read back the current State Certificate for the CPU + from SDSi hardware. The State Certificate contains information + about the current licenses on the CPU. Mailbox command. diff --git a/MAINTAINERS b/MAINTAINERS index f51ff0a45c8c..c812f955556b 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -9867,6 +9867,11 @@ S: Maintained F: arch/x86/include/asm/intel_scu_ipc.h F: drivers/platform/x86/intel_scu_* +INTEL SDSI DRIVER +M: David E. Box +S: Supported +F: drivers/platform/x86/intel/sdsi.c + INTEL SKYLAKE INT3472 ACPI DEVICE DRIVER M: Daniel Scally S: Maintained diff --git a/drivers/platform/x86/intel/Kconfig b/drivers/platform/x86/intel/Kconfig index 2d9b49eed33d..205ebb513b58 100644 --- a/drivers/platform/x86/intel/Kconfig +++ b/drivers/platform/x86/intel/Kconfig @@ -153,6 +153,18 @@ config INTEL_RST firmware will copy the memory contents back to RAM and resume the OS as usual. +config INTEL_SDSI + tristate "Intel Software Defined Silicon Driver" + depends on INTEL_VSEC + depends on X86_64 + help + This driver enables access to the Intel Software Defined Silicon + interface used to provision silicon features with an authentication + certificate and capability license. + + To compile this driver as a module, choose M here: the module will + be called intel_sdsi. + config INTEL_SMARTCONNECT tristate "Intel Smart Connect disabling driver" depends on ACPI diff --git a/drivers/platform/x86/intel/Makefile b/drivers/platform/x86/intel/Makefile index 053edc457ccb..1a5cdba4c2d2 100644 --- a/drivers/platform/x86/intel/Makefile +++ b/drivers/platform/x86/intel/Makefile @@ -25,6 +25,8 @@ intel_int0002_vgpio-y := int0002_vgpio.o obj-$(CONFIG_INTEL_INT0002_VGPIO) += intel_int0002_vgpio.o intel_oaktrail-y := oaktrail.o obj-$(CONFIG_INTEL_OAKTRAIL) += intel_oaktrail.o +intel_sdsi-y := sdsi.o +obj-$(CONFIG_INTEL_SDSI) += intel_sdsi.o intel_vsec-y := vsec.o obj-$(CONFIG_INTEL_VSEC) += intel_vsec.o diff --git a/drivers/platform/x86/intel/sdsi.c b/drivers/platform/x86/intel/sdsi.c new file mode 100644 index 000000000000..11d14cc0ff0a --- /dev/null +++ b/drivers/platform/x86/intel/sdsi.c @@ -0,0 +1,574 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Intel Software Defined Silicon driver + * + * Copyright (c) 2022, Intel Corporation. + * All Rights Reserved. + * + * Author: "David E. Box" + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "vsec.h" + +#define ACCESS_TYPE_BARID 2 +#define ACCESS_TYPE_LOCAL 3 + +#define SDSI_MIN_SIZE_DWORDS 276 +#define SDSI_SIZE_CONTROL 8 +#define SDSI_SIZE_MAILBOX 1024 +#define SDSI_SIZE_REGS 72 +#define SDSI_SIZE_CMD sizeof(u64) + +/* + * Write messages are currently up to the size of the mailbox + * while read messages are up to 4 times the size of the + * mailbox, sent in packets + */ +#define SDSI_SIZE_WRITE_MSG SDSI_SIZE_MAILBOX +#define SDSI_SIZE_READ_MSG (SDSI_SIZE_MAILBOX * 4) + +#define SDSI_ENABLED_FEATURES_OFFSET 16 +#define SDSI_ENABLED BIT(3) +#define SDSI_SOCKET_ID_OFFSET 64 +#define SDSI_SOCKET_ID GENMASK(3, 0) + +#define SDSI_MBOX_CMD_SUCCESS 0x40 +#define SDSI_MBOX_CMD_TIMEOUT 0x80 + +#define MBOX_TIMEOUT_US 2000 +#define MBOX_TIMEOUT_ACQUIRE_US 1000 +#define MBOX_POLLING_PERIOD_US 100 +#define MBOX_MAX_PACKETS 4 + +#define MBOX_OWNER_NONE 0x00 +#define MBOX_OWNER_INBAND 0x01 + +#define CTRL_RUN_BUSY BIT(0) +#define CTRL_READ_WRITE BIT(1) +#define CTRL_SOM BIT(2) +#define CTRL_EOM BIT(3) +#define CTRL_OWNER GENMASK(5, 4) +#define CTRL_COMPLETE BIT(6) +#define CTRL_READY BIT(7) +#define CTRL_STATUS GENMASK(15, 8) +#define CTRL_PACKET_SIZE GENMASK(31, 16) +#define CTRL_MSG_SIZE GENMASK(63, 48) + +#define DISC_TABLE_SIZE 12 +#define DT_ACCESS_TYPE GENMASK(3, 0) +#define DT_SIZE GENMASK(27, 12) +#define DT_TBIR GENMASK(2, 0) +#define DT_OFFSET(v) ((v) & GENMASK(31, 3)) + +enum sdsi_command { + SDSI_CMD_PROVISION_AKC = 0x04, + SDSI_CMD_PROVISION_CAP = 0x08, + SDSI_CMD_READ_STATE = 0x10, +}; + +struct sdsi_mbox_info { + u64 *payload; + u64 *buffer; + int size; +}; + +struct disc_table { + u32 access_info; + u32 guid; + u32 offset; +}; + +struct sdsi_priv { + struct mutex mb_lock; /* Mailbox access lock */ + struct device *dev; + void __iomem *control_addr; + void __iomem *mbox_addr; + void __iomem *regs_addr; + u32 guid; + bool sdsi_enabled; +}; + +/* SDSi mailbox operations must be performed using 64bit mov instructions */ +static __always_inline void +sdsi_memcpy64_toio(u64 __iomem *to, const u64 *from, size_t count_bytes) +{ + size_t count = count_bytes / sizeof(*to); + int i; + + for (i = 0; i < count; i++) + writeq(from[i], &to[i]); +} + +static __always_inline void +sdsi_memcpy64_fromio(u64 *to, const u64 __iomem *from, size_t count_bytes) +{ + size_t count = count_bytes / sizeof(*to); + int i; + + for (i = 0; i < count; i++) + to[i] = readq(&from[i]); +} + +static inline void sdsi_complete_transaction(struct sdsi_priv *priv) +{ + u64 control = FIELD_PREP(CTRL_COMPLETE, 1); + + lockdep_assert_held(&priv->mb_lock); + writeq(control, priv->control_addr); +} + +static int sdsi_status_to_errno(u32 status) +{ + switch (status) { + case SDSI_MBOX_CMD_SUCCESS: + return 0; + case SDSI_MBOX_CMD_TIMEOUT: + return -ETIMEDOUT; + default: + return -EIO; + } +} + +static int sdsi_mbox_cmd_read(struct sdsi_priv *priv, struct sdsi_mbox_info *info, + size_t *data_size) +{ + struct device *dev = priv->dev; + u32 total, loop, eom, status, message_size; + u64 control; + int ret; + + lockdep_assert_held(&priv->mb_lock); + + /* Format and send the read command */ + control = FIELD_PREP(CTRL_EOM, 1) | + FIELD_PREP(CTRL_SOM, 1) | + FIELD_PREP(CTRL_RUN_BUSY, 1) | + FIELD_PREP(CTRL_PACKET_SIZE, info->size); + writeq(control, priv->control_addr); + + /* For reads, data sizes that are larger than the mailbox size are read in packets. */ + total = 0; + loop = 0; + do { + int offset = SDSI_SIZE_MAILBOX * loop; + void __iomem *addr = priv->mbox_addr + offset; + u64 *buf = info->buffer + offset / SDSI_SIZE_CMD; + u32 packet_size; + + /* Poll on ready bit */ + ret = readq_poll_timeout(priv->control_addr, control, control & CTRL_READY, + MBOX_POLLING_PERIOD_US, MBOX_TIMEOUT_US); + if (ret) + break; + + eom = FIELD_GET(CTRL_EOM, control); + status = FIELD_GET(CTRL_STATUS, control); + packet_size = FIELD_GET(CTRL_PACKET_SIZE, control); + message_size = FIELD_GET(CTRL_MSG_SIZE, control); + + ret = sdsi_status_to_errno(status); + if (ret) + break; + + /* Only the last packet can be less than the mailbox size. */ + if (!eom && packet_size != SDSI_SIZE_MAILBOX) { + dev_err(dev, "Invalid packet size\n"); + ret = -EPROTO; + break; + } + + if (packet_size > SDSI_SIZE_MAILBOX) { + dev_err(dev, "Packet size too large\n"); + ret = -EPROTO; + break; + } + + sdsi_memcpy64_fromio(buf, addr, round_up(packet_size, SDSI_SIZE_CMD)); + + total += packet_size; + + sdsi_complete_transaction(priv); + } while (!eom && ++loop < MBOX_MAX_PACKETS); + + if (ret) { + sdsi_complete_transaction(priv); + return ret; + } + + if (!eom) { + dev_err(dev, "Exceeded read attempts\n"); + return -EPROTO; + } + + /* Message size check is only valid for multi-packet transfers */ + if (loop && total != message_size) + dev_warn(dev, "Read count %u differs from expected count %u\n", + total, message_size); + + *data_size = total; + + return 0; +} + +static int sdsi_mbox_cmd_write(struct sdsi_priv *priv, struct sdsi_mbox_info *info) +{ + u64 control; + u32 status; + int ret; + + lockdep_assert_held(&priv->mb_lock); + + /* Write rest of the payload */ + sdsi_memcpy64_toio(priv->mbox_addr + SDSI_SIZE_CMD, info->payload + 1, + info->size - SDSI_SIZE_CMD); + + /* Format and send the write command */ + control = FIELD_PREP(CTRL_EOM, 1) | + FIELD_PREP(CTRL_SOM, 1) | + FIELD_PREP(CTRL_RUN_BUSY, 1) | + FIELD_PREP(CTRL_READ_WRITE, 1) | + FIELD_PREP(CTRL_PACKET_SIZE, info->size); + writeq(control, priv->control_addr); + + /* Poll on run_busy bit */ + ret = readq_poll_timeout(priv->control_addr, control, !(control & CTRL_RUN_BUSY), + MBOX_POLLING_PERIOD_US, MBOX_TIMEOUT_US); + + if (ret) + goto release_mbox; + + status = FIELD_GET(CTRL_STATUS, control); + ret = sdsi_status_to_errno(status); + +release_mbox: + sdsi_complete_transaction(priv); + + return ret; +} + +static int sdsi_mbox_acquire(struct sdsi_priv *priv, struct sdsi_mbox_info *info) +{ + u64 control; + u32 owner; + int ret; + + lockdep_assert_held(&priv->mb_lock); + + /* Check mailbox is available */ + control = readq(priv->control_addr); + owner = FIELD_GET(CTRL_OWNER, control); + if (owner != MBOX_OWNER_NONE) + return -EBUSY; + + /* Write first qword of payload */ + writeq(info->payload[0], priv->mbox_addr); + + /* Check for ownership */ + ret = readq_poll_timeout(priv->control_addr, control, + FIELD_GET(CTRL_OWNER, control) & MBOX_OWNER_INBAND, + MBOX_POLLING_PERIOD_US, MBOX_TIMEOUT_ACQUIRE_US); + + return ret; +} + +static int sdsi_mbox_write(struct sdsi_priv *priv, struct sdsi_mbox_info *info) +{ + int ret; + + lockdep_assert_held(&priv->mb_lock); + + ret = sdsi_mbox_acquire(priv, info); + if (ret) + return ret; + + return sdsi_mbox_cmd_write(priv, info); +} + +static int sdsi_mbox_read(struct sdsi_priv *priv, struct sdsi_mbox_info *info, size_t *data_size) +{ + int ret; + + lockdep_assert_held(&priv->mb_lock); + + ret = sdsi_mbox_acquire(priv, info); + if (ret) + return ret; + + return sdsi_mbox_cmd_read(priv, info, data_size); +} + +static ssize_t sdsi_provision(struct sdsi_priv *priv, char *buf, size_t count, + enum sdsi_command command) +{ + struct sdsi_mbox_info info; + int ret; + + if (!priv->sdsi_enabled) + return -EPERM; + + if (count > (SDSI_SIZE_WRITE_MSG - SDSI_SIZE_CMD)) + return -EOVERFLOW; + + /* Qword aligned message + command qword */ + info.size = round_up(count, SDSI_SIZE_CMD) + SDSI_SIZE_CMD; + + info.payload = kzalloc(info.size, GFP_KERNEL); + if (!info.payload) + return -ENOMEM; + + /* Copy message to payload buffer */ + memcpy(info.payload, buf, count); + + /* Command is last qword of payload buffer */ + info.payload[(info.size - SDSI_SIZE_CMD) / SDSI_SIZE_CMD] = command; + + ret = mutex_lock_interruptible(&priv->mb_lock); + if (ret) + goto free_payload; + ret = sdsi_mbox_write(priv, &info); + mutex_unlock(&priv->mb_lock); + +free_payload: + kfree(info.payload); + + if (ret) + return ret; + + return count; +} + +static ssize_t provision_akc_write(struct file *filp, struct kobject *kobj, + struct bin_attribute *attr, char *buf, loff_t off, + size_t count) +{ + struct device *dev = kobj_to_dev(kobj); + struct sdsi_priv *priv = dev_get_drvdata(dev); + + if (off) + return -ESPIPE; + + return sdsi_provision(priv, buf, count, SDSI_CMD_PROVISION_AKC); +} +static BIN_ATTR_WO(provision_akc, SDSI_SIZE_WRITE_MSG); + +static ssize_t provision_cap_write(struct file *filp, struct kobject *kobj, + struct bin_attribute *attr, char *buf, loff_t off, + size_t count) +{ + struct device *dev = kobj_to_dev(kobj); + struct sdsi_priv *priv = dev_get_drvdata(dev); + + if (off) + return -ESPIPE; + + return sdsi_provision(priv, buf, count, SDSI_CMD_PROVISION_CAP); +} +static BIN_ATTR_WO(provision_cap, SDSI_SIZE_WRITE_MSG); + +static long state_certificate_read(struct file *filp, struct kobject *kobj, + struct bin_attribute *attr, char *buf, loff_t off, + size_t count) +{ + struct device *dev = kobj_to_dev(kobj); + struct sdsi_priv *priv = dev_get_drvdata(dev); + u64 command = SDSI_CMD_READ_STATE; + struct sdsi_mbox_info info; + size_t size; + int ret; + + if (!priv->sdsi_enabled) + return -EPERM; + + if (off) + return 0; + + /* Buffer for return data */ + info.buffer = kmalloc(SDSI_SIZE_READ_MSG, GFP_KERNEL); + if (!info.buffer) + return -ENOMEM; + + info.payload = &command; + info.size = sizeof(command); + + ret = mutex_lock_interruptible(&priv->mb_lock); + if (ret) + goto free_buffer; + ret = sdsi_mbox_read(priv, &info, &size); + mutex_unlock(&priv->mb_lock); + if (ret < 0) + goto free_buffer; + + if (size > count) + size = count; + + memcpy(buf, info.buffer, size); + +free_buffer: + kfree(info.buffer); + + if (ret) + return ret; + + return size; +} +static BIN_ATTR(state_certificate, 0400, state_certificate_read, NULL, SDSI_SIZE_READ_MSG); + +static ssize_t registers_read(struct file *filp, struct kobject *kobj, + struct bin_attribute *attr, char *buf, loff_t off, + size_t count) +{ + struct device *dev = kobj_to_dev(kobj); + struct sdsi_priv *priv = dev_get_drvdata(dev); + void __iomem *addr = priv->regs_addr; + + memcpy_fromio(buf, addr + off, count); + + return count; +} +static BIN_ATTR(registers, 0400, registers_read, NULL, SDSI_SIZE_REGS); + +static struct bin_attribute *sdsi_bin_attrs[] = { + &bin_attr_registers, + &bin_attr_state_certificate, + &bin_attr_provision_akc, + &bin_attr_provision_cap, + NULL +}; + +static ssize_t guid_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct sdsi_priv *priv = dev_get_drvdata(dev); + + return sysfs_emit(buf, "0x%x\n", priv->guid); +} +static DEVICE_ATTR_RO(guid); + +static struct attribute *sdsi_attrs[] = { + &dev_attr_guid.attr, + NULL +}; + +static const struct attribute_group sdsi_group = { + .attrs = sdsi_attrs, + .bin_attrs = sdsi_bin_attrs, +}; +__ATTRIBUTE_GROUPS(sdsi); + +static int sdsi_map_mbox_registers(struct sdsi_priv *priv, struct pci_dev *parent, + struct disc_table *disc_table, struct resource *disc_res) +{ + u32 access_type = FIELD_GET(DT_ACCESS_TYPE, disc_table->access_info); + u32 size = FIELD_GET(DT_SIZE, disc_table->access_info); + u32 tbir = FIELD_GET(DT_TBIR, disc_table->offset); + u32 offset = DT_OFFSET(disc_table->offset); + u32 features_offset; + struct resource res = {}; + + /* Starting location of SDSi MMIO region based on access type */ + switch (access_type) { + case ACCESS_TYPE_LOCAL: + if (tbir) { + dev_err(priv->dev, "Unsupported BAR index %u for access type %u\n", + tbir, access_type); + return -EINVAL; + } + + /* + * For access_type LOCAL, the base address is as follows: + * base address = end of discovery region + base offset + 1 + */ + res.start = disc_res->end + offset + 1; + break; + + case ACCESS_TYPE_BARID: + res.start = pci_resource_start(parent, tbir) + offset; + break; + + default: + dev_err(priv->dev, "Unrecognized access_type %u\n", access_type); + return -EINVAL; + } + + res.end = res.start + size * sizeof(u32) - 1; + res.flags = IORESOURCE_MEM; + + priv->control_addr = devm_ioremap_resource(priv->dev, &res); + if (IS_ERR(priv->control_addr)) + return PTR_ERR(priv->control_addr); + + priv->mbox_addr = priv->control_addr + SDSI_SIZE_CONTROL; + priv->regs_addr = priv->mbox_addr + SDSI_SIZE_MAILBOX; + + features_offset = readq(priv->regs_addr + SDSI_ENABLED_FEATURES_OFFSET); + priv->sdsi_enabled = !!(features_offset & SDSI_ENABLED); + + return 0; +} + +static int sdsi_probe(struct auxiliary_device *auxdev, const struct auxiliary_device_id *id) +{ + struct intel_vsec_device *intel_cap_dev = auxdev_to_ivdev(auxdev); + struct disc_table disc_table; + struct resource *disc_res; + void __iomem *disc_addr; + struct sdsi_priv *priv; + int ret; + + priv = devm_kzalloc(&auxdev->dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->dev = &auxdev->dev; + mutex_init(&priv->mb_lock); + auxiliary_set_drvdata(auxdev, priv); + + /* Get the SDSi discovery table */ + disc_res = &intel_cap_dev->resource[0]; + disc_addr = devm_ioremap_resource(&auxdev->dev, disc_res); + if (IS_ERR(disc_addr)) + return PTR_ERR(disc_addr); + + memcpy_fromio(&disc_table, disc_addr, DISC_TABLE_SIZE); + + priv->guid = disc_table.guid; + + /* Map the SDSi mailbox registers */ + ret = sdsi_map_mbox_registers(priv, intel_cap_dev->pcidev, &disc_table, disc_res); + if (ret) + return ret; + + return 0; +} + +static const struct auxiliary_device_id sdsi_aux_id_table[] = { + { .name = "intel_vsec.sdsi" }, + {} +}; +MODULE_DEVICE_TABLE(auxiliary, sdsi_aux_id_table); + +static struct auxiliary_driver sdsi_aux_driver = { + .driver = { + .dev_groups = sdsi_groups, + }, + .id_table = sdsi_aux_id_table, + .probe = sdsi_probe, + /* No remove. All resources are handled under devm */ +}; +module_auxiliary_driver(sdsi_aux_driver); + +MODULE_AUTHOR("David E. Box "); +MODULE_DESCRIPTION("Intel Software Defined Silicon driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/platform/x86/intel/vsec.c b/drivers/platform/x86/intel/vsec.c index c3bdd75ed690..bed436bf181f 100644 --- a/drivers/platform/x86/intel/vsec.c +++ b/drivers/platform/x86/intel/vsec.c @@ -32,6 +32,7 @@ #define TABLE_OFFSET_SHIFT 3 static DEFINE_IDA(intel_vsec_ida); +static DEFINE_IDA(intel_vsec_sdsi_ida); /** * struct intel_vsec_header - Common fields of Intel VSEC and DVSEC registers. @@ -63,12 +64,14 @@ enum intel_vsec_id { VSEC_ID_TELEMETRY = 2, VSEC_ID_WATCHER = 3, VSEC_ID_CRASHLOG = 4, + VSEC_ID_SDSI = 65, }; static enum intel_vsec_id intel_vsec_allow_list[] = { VSEC_ID_TELEMETRY, VSEC_ID_WATCHER, VSEC_ID_CRASHLOG, + VSEC_ID_SDSI, }; static const char *intel_vsec_name(enum intel_vsec_id id) @@ -83,6 +86,9 @@ static const char *intel_vsec_name(enum intel_vsec_id id) case VSEC_ID_CRASHLOG: return "crashlog"; + case VSEC_ID_SDSI: + return "sdsi"; + default: return NULL; } @@ -211,7 +217,11 @@ static int intel_vsec_add_dev(struct pci_dev *pdev, struct intel_vsec_header *he intel_vsec_dev->resource = res; intel_vsec_dev->num_resources = header->num_entries; intel_vsec_dev->quirks = quirks; - intel_vsec_dev->ida = &intel_vsec_ida; + + if (header->id == VSEC_ID_SDSI) + intel_vsec_dev->ida = &intel_vsec_sdsi_ida; + else + intel_vsec_dev->ida = &intel_vsec_ida; return intel_vsec_add_aux(pdev, intel_vsec_dev, intel_vsec_name(header->id)); } From e28747da771cbda2dac0e8d9487e875feb1b8fed Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Wed, 9 Feb 2022 13:31:15 +0200 Subject: [PATCH 047/137] gpiolib: sysfs: Move sysfs_emit() calls outside of the mutex lock In a few places we perform sysfs_emit() operations under mutex that do not require any locking. Move them outside of the mutex locks. Signed-off-by: Andy Shevchenko Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpiolib-sysfs.c | 29 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/drivers/gpio/gpiolib-sysfs.c b/drivers/gpio/gpiolib-sysfs.c index fc9ec6aa2fc2..7fe38191f17c 100644 --- a/drivers/gpio/gpiolib-sysfs.c +++ b/drivers/gpio/gpiolib-sysfs.c @@ -61,17 +61,16 @@ static ssize_t direction_show(struct device *dev, { struct gpiod_data *data = dev_get_drvdata(dev); struct gpio_desc *desc = data->desc; - ssize_t status; + int value; mutex_lock(&data->mutex); gpiod_get_direction(desc); - status = sysfs_emit(buf, "%s\n", - test_bit(FLAG_IS_OUT, &desc->flags) ? "out" : "in"); + value = !!test_bit(FLAG_IS_OUT, &desc->flags); mutex_unlock(&data->mutex); - return status; + return sysfs_emit(buf, "%s\n", value ? "out" : "in"); } static ssize_t direction_store(struct device *dev, @@ -108,12 +107,13 @@ static ssize_t value_show(struct device *dev, mutex_lock(&data->mutex); status = gpiod_get_value_cansleep(desc); - if (status >= 0) - status = sysfs_emit(buf, "%zd\n", status); mutex_unlock(&data->mutex); - return status; + if (status < 0) + return status; + + return sysfs_emit(buf, "%zd\n", status); } static ssize_t value_store(struct device *dev, @@ -238,7 +238,6 @@ static ssize_t edge_show(struct device *dev, struct device_attribute *attr, char *buf) { struct gpiod_data *data = dev_get_drvdata(dev); - ssize_t status = 0; int i; mutex_lock(&data->mutex); @@ -247,12 +246,13 @@ static ssize_t edge_show(struct device *dev, if (data->irq_flags == trigger_types[i].flags) break; } - if (i < ARRAY_SIZE(trigger_types)) - status = sysfs_emit(buf, "%s\n", trigger_types[i].name); mutex_unlock(&data->mutex); - return status; + if (i >= ARRAY_SIZE(trigger_types)) + return 0; + + return sysfs_emit(buf, "%s\n", trigger_types[i].name); } static ssize_t edge_store(struct device *dev, @@ -324,16 +324,15 @@ static ssize_t active_low_show(struct device *dev, { struct gpiod_data *data = dev_get_drvdata(dev); struct gpio_desc *desc = data->desc; - ssize_t status; + int value; mutex_lock(&data->mutex); - status = sysfs_emit(buf, "%d\n", - !!test_bit(FLAG_ACTIVE_LOW, &desc->flags)); + value = !!test_bit(FLAG_ACTIVE_LOW, &desc->flags); mutex_unlock(&data->mutex); - return status; + return sysfs_emit(buf, "%d\n", value); } static ssize_t active_low_store(struct device *dev, From 6b3c1791ae2fde75311b414f38d50e0125374a4e Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Wed, 9 Feb 2022 13:31:16 +0200 Subject: [PATCH 048/137] gpiolib: sysfs: Move kstrtox() calls outside of the mutex lock In a few places we perform kstrtox() operations under mutex that do not require any locking. Move them outside of the mutex locks. Signed-off-by: Andy Shevchenko Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpiolib-sysfs.c | 28 ++++++++++++---------------- 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/drivers/gpio/gpiolib-sysfs.c b/drivers/gpio/gpiolib-sysfs.c index 7fe38191f17c..5f26beefafbc 100644 --- a/drivers/gpio/gpiolib-sysfs.c +++ b/drivers/gpio/gpiolib-sysfs.c @@ -121,24 +121,18 @@ static ssize_t value_store(struct device *dev, { struct gpiod_data *data = dev_get_drvdata(dev); struct gpio_desc *desc = data->desc; - ssize_t status = 0; + ssize_t status; + long value; + + status = kstrtol(buf, 0, &value); mutex_lock(&data->mutex); if (!test_bit(FLAG_IS_OUT, &desc->flags)) { status = -EPERM; - } else { - long value; - - if (size <= 2 && isdigit(buf[0]) && - (size == 1 || buf[1] == '\n')) - value = buf[0] - '0'; - else - status = kstrtol(buf, 0, &value); - if (status == 0) { - gpiod_set_value_cansleep(desc, value); - status = size; - } + } else if (status == 0) { + gpiod_set_value_cansleep(desc, value); + status = size; } mutex_unlock(&data->mutex); @@ -342,11 +336,13 @@ static ssize_t active_low_store(struct device *dev, ssize_t status; long value; + status = kstrtol(buf, 0, &value); + if (status) + return status; + mutex_lock(&data->mutex); - status = kstrtol(buf, 0, &value); - if (status == 0) - status = gpio_sysfs_set_active_low(dev, value); + status = gpio_sysfs_set_active_low(dev, value); mutex_unlock(&data->mutex); From 667630edb5bacd05ee423cbbb53f236d0122ea34 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Wed, 9 Feb 2022 13:31:17 +0200 Subject: [PATCH 049/137] gpiolib: sysfs: Simplify edge handling in the code Instead of keeping specific data structure for IRQ trigger types, switch to array of trigger names and use index as a type. The code is in maintenance mode and that array is not going to grow. Signed-off-by: Andy Shevchenko Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpiolib-sysfs.c | 40 +++++++++++++----------------------- 1 file changed, 14 insertions(+), 26 deletions(-) diff --git a/drivers/gpio/gpiolib-sysfs.c b/drivers/gpio/gpiolib-sysfs.c index 5f26beefafbc..a34dc75a5293 100644 --- a/drivers/gpio/gpiolib-sysfs.c +++ b/drivers/gpio/gpiolib-sysfs.c @@ -13,6 +13,7 @@ #include "gpiolib.h" #include "gpiolib-sysfs.h" +#define GPIO_IRQF_TRIGGER_NONE 0 #define GPIO_IRQF_TRIGGER_FALLING BIT(0) #define GPIO_IRQF_TRIGGER_RISING BIT(1) #define GPIO_IRQF_TRIGGER_BOTH (GPIO_IRQF_TRIGGER_FALLING | \ @@ -218,54 +219,41 @@ static void gpio_sysfs_free_irq(struct device *dev) sysfs_put(data->value_kn); } -static const struct { - const char *name; - unsigned char flags; -} trigger_types[] = { - { "none", 0 }, - { "falling", GPIO_IRQF_TRIGGER_FALLING }, - { "rising", GPIO_IRQF_TRIGGER_RISING }, - { "both", GPIO_IRQF_TRIGGER_BOTH }, +static const char * const trigger_names[] = { + [GPIO_IRQF_TRIGGER_NONE] = "none", + [GPIO_IRQF_TRIGGER_FALLING] = "falling", + [GPIO_IRQF_TRIGGER_RISING] = "rising", + [GPIO_IRQF_TRIGGER_BOTH] = "both", }; static ssize_t edge_show(struct device *dev, struct device_attribute *attr, char *buf) { struct gpiod_data *data = dev_get_drvdata(dev); - int i; + int flags; mutex_lock(&data->mutex); - for (i = 0; i < ARRAY_SIZE(trigger_types); i++) { - if (data->irq_flags == trigger_types[i].flags) - break; - } + flags = data->irq_flags; mutex_unlock(&data->mutex); - if (i >= ARRAY_SIZE(trigger_types)) + if (flags >= ARRAY_SIZE(trigger_names)) return 0; - return sysfs_emit(buf, "%s\n", trigger_types[i].name); + return sysfs_emit(buf, "%s\n", trigger_names[flags]); } static ssize_t edge_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t size) { struct gpiod_data *data = dev_get_drvdata(dev); - unsigned char flags; ssize_t status = size; - int i; + int flags; - for (i = 0; i < ARRAY_SIZE(trigger_types); i++) { - if (sysfs_streq(trigger_types[i].name, buf)) - break; - } - - if (i == ARRAY_SIZE(trigger_types)) - return -EINVAL; - - flags = trigger_types[i].flags; + flags = sysfs_match_string(trigger_names, buf); + if (flags < 0) + return flags; mutex_lock(&data->mutex); From 269ecc0c894c6db7ea13ae076c01bd24e87b15e6 Mon Sep 17 00:00:00 2001 From: Yang Li Date: Wed, 16 Feb 2022 09:50:42 +0800 Subject: [PATCH 050/137] HID: i2c-hid: remove unneeded semicolon Eliminate the following coccicheck warning: ./drivers/hid/i2c-hid/i2c-hid-core.c:357:56-57: Unneeded semicolon Reported-by: Abaci Robot Signed-off-by: Yang Li Signed-off-by: Jiri Kosina --- drivers/hid/i2c-hid/i2c-hid-core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hid/i2c-hid/i2c-hid-core.c b/drivers/hid/i2c-hid/i2c-hid-core.c index 91b2fa0d33c7..c078f09a2318 100644 --- a/drivers/hid/i2c-hid/i2c-hid-core.c +++ b/drivers/hid/i2c-hid/i2c-hid-core.c @@ -354,7 +354,7 @@ static int i2c_hid_set_or_send_report(struct i2c_hid *ihid, * With simple "send report" all data goes into the output * register. */ - *(__le16 *)ihid->cmdbuf = ihid->hdesc.wOutputRegister;; + *(__le16 *)ihid->cmdbuf = ihid->hdesc.wOutputRegister; length += sizeof(__le16); } From 1324c5ac76bf7dccdac0fb128d0a675e1ee21b39 Mon Sep 17 00:00:00 2001 From: Nikolai Kondrashov Date: Thu, 10 Feb 2022 20:04:31 +0100 Subject: [PATCH 051/137] HID: uclogic: Support Huion tilt reporting MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add support for Huion v2 protocol tilt reporting. Describe reports as angles in degrees, which is not exactly true, but there doesn't seem to be a straightforward, consistent conversion possible, and what's reported would have to be enough. Signed-off-by: Nikolai Kondrashov Signed-off-by: José Expósito Signed-off-by: Jiri Kosina --- drivers/hid/hid-uclogic-core.c | 3 +++ drivers/hid/hid-uclogic-params.c | 1 + drivers/hid/hid-uclogic-params.h | 8 ++++++++ drivers/hid/hid-uclogic-rdesc.c | 12 +++++++++++- 4 files changed, 23 insertions(+), 1 deletion(-) diff --git a/drivers/hid/hid-uclogic-core.c b/drivers/hid/hid-uclogic-core.c index d8ab0139e5cd..823704bc02ef 100644 --- a/drivers/hid/hid-uclogic-core.c +++ b/drivers/hid/hid-uclogic-core.c @@ -304,6 +304,9 @@ static int uclogic_raw_event(struct hid_device *hdev, mod_timer(&drvdata->inrange_timer, jiffies + msecs_to_jiffies(100)); } + /* If we report tilt and Y direction is flipped */ + if (size >= 12 && params->pen.tilt_y_flipped) + data[11] = -data[11]; } /* Tweak frame control reports, if necessary */ diff --git a/drivers/hid/hid-uclogic-params.c b/drivers/hid/hid-uclogic-params.c index 3e70f969fb84..30e0f69ed5af 100644 --- a/drivers/hid/hid-uclogic-params.c +++ b/drivers/hid/hid-uclogic-params.c @@ -369,6 +369,7 @@ static int uclogic_params_pen_init_v2(struct uclogic_params_pen *pen, pen->id = UCLOGIC_RDESC_PEN_V2_ID; pen->inrange = UCLOGIC_PARAMS_PEN_INRANGE_NONE; pen->fragmented_hires = true; + pen->tilt_y_flipped = true; found = true; finish: *pfound = found; diff --git a/drivers/hid/hid-uclogic-params.h b/drivers/hid/hid-uclogic-params.h index ba48b1c7a0e5..e8381bb77bd0 100644 --- a/drivers/hid/hid-uclogic-params.h +++ b/drivers/hid/hid-uclogic-params.h @@ -62,6 +62,12 @@ struct uclogic_params_pen { * Only valid if "id" is not zero. */ bool fragmented_hires; + /* + * True if the pen reports tilt in bytes at offset 10 (X) and 11 (Y), + * and the Y tilt direction is flipped. + * Only valid if "id" is not zero. + */ + bool tilt_y_flipped; }; /* @@ -171,6 +177,7 @@ extern int uclogic_params_init(struct uclogic_params *params, ".pen.id = %u\n" \ ".pen.inrange = %s\n" \ ".pen.fragmented_hires = %s\n" \ + ".pen.tilt_y_flipped = %s\n" \ ".frame.desc_ptr = %p\n" \ ".frame.desc_size = %u\n" \ ".frame.id = %u\n" \ @@ -189,6 +196,7 @@ extern int uclogic_params_init(struct uclogic_params *params, (_params)->pen.id, \ uclogic_params_pen_inrange_to_str((_params)->pen.inrange), \ ((_params)->pen.fragmented_hires ? "true" : "false"), \ + ((_params)->pen.tilt_y_flipped ? "true" : "false"), \ (_params)->frame.desc_ptr, \ (_params)->frame.desc_size, \ (_params)->frame.id, \ diff --git a/drivers/hid/hid-uclogic-rdesc.c b/drivers/hid/hid-uclogic-rdesc.c index 6dd6dcd09c8b..2607cbdf8ff2 100644 --- a/drivers/hid/hid-uclogic-rdesc.c +++ b/drivers/hid/hid-uclogic-rdesc.c @@ -633,7 +633,17 @@ const __u8 uclogic_rdesc_pen_v2_template_arr[] = { 0x27, UCLOGIC_RDESC_PEN_PH(PRESSURE_LM), /* Logical Maximum (PLACEHOLDER), */ 0x81, 0x02, /* Input (Variable), */ - 0x81, 0x03, /* Input (Constant, Variable), */ + 0x54, /* Unit Exponent (0), */ + 0x65, 0x14, /* Unit (Degrees), */ + 0x35, 0xC4, /* Physical Minimum (-60), */ + 0x45, 0x3C, /* Physical Maximum (60), */ + 0x15, 0xC4, /* Logical Minimum (-60), */ + 0x25, 0x3C, /* Logical Maximum (60), */ + 0x75, 0x08, /* Report Size (8), */ + 0x95, 0x02, /* Report Count (2), */ + 0x09, 0x3D, /* Usage (X Tilt), */ + 0x09, 0x3E, /* Usage (Y Tilt), */ + 0x81, 0x02, /* Input (Variable), */ 0xC0, /* End Collection, */ 0xC0 /* End Collection */ }; From 85e860715e2ccf8c99fb9ad747d6f01d0ed92076 Mon Sep 17 00:00:00 2001 From: Nikolai Kondrashov Date: Thu, 10 Feb 2022 20:04:32 +0100 Subject: [PATCH 052/137] HID: uclogic: Rename Huion HS64 PID to Huion Tablet 2 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Since the 006d Huion PID is used by multiple tablets (at least HS64 and HS610), rename its constant to a more general "USB_DEVICE_ID_HUION_TABLET2". Signed-off-by: Nikolai Kondrashov Signed-off-by: José Expósito Signed-off-by: Jiri Kosina --- drivers/hid/hid-ids.h | 2 +- drivers/hid/hid-uclogic-core.c | 2 +- drivers/hid/hid-uclogic-params.c | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h index 78bd3ddda442..cef51e93e220 100644 --- a/drivers/hid/hid-ids.h +++ b/drivers/hid/hid-ids.h @@ -606,7 +606,7 @@ #define USB_VENDOR_ID_HUION 0x256c #define USB_DEVICE_ID_HUION_TABLET 0x006e -#define USB_DEVICE_ID_HUION_HS64 0x006d +#define USB_DEVICE_ID_HUION_TABLET2 0x006d #define USB_VENDOR_ID_IBM 0x04b3 #define USB_DEVICE_ID_IBM_SCROLLPOINT_III 0x3100 diff --git a/drivers/hid/hid-uclogic-core.c b/drivers/hid/hid-uclogic-core.c index 823704bc02ef..850c660ec2ab 100644 --- a/drivers/hid/hid-uclogic-core.c +++ b/drivers/hid/hid-uclogic-core.c @@ -376,7 +376,7 @@ static const struct hid_device_id uclogic_devices[] = { { HID_USB_DEVICE(USB_VENDOR_ID_HUION, USB_DEVICE_ID_HUION_TABLET) }, { HID_USB_DEVICE(USB_VENDOR_ID_HUION, - USB_DEVICE_ID_HUION_HS64) }, + USB_DEVICE_ID_HUION_TABLET2) }, { HID_USB_DEVICE(USB_VENDOR_ID_TRUST, USB_DEVICE_ID_TRUST_PANORA_TABLET) }, { HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC, diff --git a/drivers/hid/hid-uclogic-params.c b/drivers/hid/hid-uclogic-params.c index 30e0f69ed5af..98910d8dae92 100644 --- a/drivers/hid/hid-uclogic-params.c +++ b/drivers/hid/hid-uclogic-params.c @@ -993,7 +993,7 @@ int uclogic_params_init(struct uclogic_params *params, case VID_PID(USB_VENDOR_ID_HUION, USB_DEVICE_ID_HUION_TABLET): case VID_PID(USB_VENDOR_ID_HUION, - USB_DEVICE_ID_HUION_HS64): + USB_DEVICE_ID_HUION_TABLET2): case VID_PID(USB_VENDOR_ID_UCLOGIC, USB_DEVICE_ID_HUION_TABLET): case VID_PID(USB_VENDOR_ID_UCLOGIC, From 5591403cd67c28f3c49d76d7f43170de1b950d57 Mon Sep 17 00:00:00 2001 From: Nikolai Kondrashov Date: Thu, 10 Feb 2022 20:04:33 +0100 Subject: [PATCH 053/137] HID: uclogic: Support Huion 13th frame button MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Support reporting 13th frame button for Huion tablets. This supports reporting the button in the center of the dial for Huion HS610. Signed-off-by: Nikolai Kondrashov Signed-off-by: José Expósito Signed-off-by: Jiri Kosina --- drivers/hid/hid-uclogic-rdesc.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/hid/hid-uclogic-rdesc.c b/drivers/hid/hid-uclogic-rdesc.c index 2607cbdf8ff2..ec16355d200b 100644 --- a/drivers/hid/hid-uclogic-rdesc.c +++ b/drivers/hid/hid-uclogic-rdesc.c @@ -689,8 +689,8 @@ const size_t uclogic_rdesc_pen_v2_template_size = 0xA0, /* Collection (Physical), */ \ 0x05, 0x09, /* Usage Page (Button), */ \ 0x19, 0x01, /* Usage Minimum (01h), */ \ - 0x29, 0x02, /* Usage Maximum (02h), */ \ - 0x95, 0x02, /* Report Count (2), */ \ + 0x29, 0x03, /* Usage Maximum (03h), */ \ + 0x95, 0x03, /* Report Count (3), */ \ 0x81, 0x02, /* Input (Variable), */ \ 0x95, _padding, /* Report Count (_padding), */ \ 0x81, 0x01, /* Input (Constant), */ \ @@ -699,14 +699,14 @@ const size_t uclogic_rdesc_pen_v2_template_size = /* Fixed report descriptor for (tweaked) v1 buttonpad reports */ const __u8 uclogic_rdesc_buttonpad_v1_arr[] = { - UCLOGIC_RDESC_BUTTONPAD_BYTES(20) + UCLOGIC_RDESC_BUTTONPAD_BYTES(19) }; const size_t uclogic_rdesc_buttonpad_v1_size = sizeof(uclogic_rdesc_buttonpad_v1_arr); /* Fixed report descriptor for (tweaked) v2 buttonpad reports */ const __u8 uclogic_rdesc_buttonpad_v2_arr[] = { - UCLOGIC_RDESC_BUTTONPAD_BYTES(52) + UCLOGIC_RDESC_BUTTONPAD_BYTES(51) }; const size_t uclogic_rdesc_buttonpad_v2_size = sizeof(uclogic_rdesc_buttonpad_v2_arr); From 7e418667f2ed035445f8631b451e41d4ec13533c Mon Sep 17 00:00:00 2001 From: Nikolai Kondrashov Date: Thu, 10 Feb 2022 20:04:34 +0100 Subject: [PATCH 054/137] HID: uclogic: Split pen and frame raw event handling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In order to avoid ending up with a big uclogic_raw_event function, split it in two smaller functions: uclogic_raw_event_pen for the pen events and uclogic_raw_event_frame for the pad events. Signed-off-by: Nikolai Kondrashov Signed-off-by: José Expósito Signed-off-by: Jiri Kosina --- drivers/hid/hid-uclogic-core.c | 195 ++++++++++++++++++++------------- 1 file changed, 120 insertions(+), 75 deletions(-) diff --git a/drivers/hid/hid-uclogic-core.c b/drivers/hid/hid-uclogic-core.c index 850c660ec2ab..9187fd835a46 100644 --- a/drivers/hid/hid-uclogic-core.c +++ b/drivers/hid/hid-uclogic-core.c @@ -246,6 +246,123 @@ static int uclogic_resume(struct hid_device *hdev) } #endif +/** + * uclogic_raw_event_pen - handle raw pen events (pen HID reports). + * + * @drvdata: Driver data. + * @data: Report data buffer, can be modified. + * @size: Report data size, bytes. + * + * Returns: + * Negative value on error (stops event delivery), zero for success. + */ +static int uclogic_raw_event_pen(struct uclogic_drvdata *drvdata, + u8 *data, int size) +{ + struct uclogic_params *params = &drvdata->params; + + WARN_ON(drvdata == NULL); + WARN_ON(data == NULL && size != 0); + + /* If in-range reports are inverted */ + if (params->pen.inrange == + UCLOGIC_PARAMS_PEN_INRANGE_INVERTED) { + /* Invert the in-range bit */ + data[1] ^= 0x40; + } + /* + * If report contains fragmented high-resolution pen + * coordinates + */ + if (size >= 10 && params->pen.fragmented_hires) { + u8 pressure_low_byte; + u8 pressure_high_byte; + + /* Lift pressure bytes */ + pressure_low_byte = data[6]; + pressure_high_byte = data[7]; + /* + * Move Y coord to make space for high-order X + * coord byte + */ + data[6] = data[5]; + data[5] = data[4]; + /* Move high-order X coord byte */ + data[4] = data[8]; + /* Move high-order Y coord byte */ + data[7] = data[9]; + /* Place pressure bytes */ + data[8] = pressure_low_byte; + data[9] = pressure_high_byte; + } + /* If we need to emulate in-range detection */ + if (params->pen.inrange == UCLOGIC_PARAMS_PEN_INRANGE_NONE) { + /* Set in-range bit */ + data[1] |= 0x40; + /* (Re-)start in-range timeout */ + mod_timer(&drvdata->inrange_timer, + jiffies + msecs_to_jiffies(100)); + } + /* If we report tilt and Y direction is flipped */ + if (size >= 12 && params->pen.tilt_y_flipped) + data[11] = -data[11]; + + return 0; +} + +/** + * uclogic_raw_event_frame - handle raw frame events (frame HID reports). + * + * @drvdata: Driver data. + * @data: Report data buffer, can be modified. + * @size: Report data size, bytes. + * + * Returns: + * Negative value on error (stops event delivery), zero for success. + */ +static int uclogic_raw_event_frame(struct uclogic_drvdata *drvdata, + u8 *data, int size) +{ + struct uclogic_params *params = &drvdata->params; + + WARN_ON(drvdata == NULL); + WARN_ON(data == NULL && size != 0); + + /* If need to, and can, set pad device ID for Wacom drivers */ + if (params->frame.dev_id_byte > 0 && + params->frame.dev_id_byte < size) { + data[params->frame.dev_id_byte] = 0xf; + } + /* If need to, and can, read rotary encoder state change */ + if (params->frame.re_lsb > 0 && + params->frame.re_lsb / 8 < size) { + unsigned int byte = params->frame.re_lsb / 8; + unsigned int bit = params->frame.re_lsb % 8; + + u8 change; + u8 prev_state = drvdata->re_state; + /* Read Gray-coded state */ + u8 state = (data[byte] >> bit) & 0x3; + /* Encode state change into 2-bit signed integer */ + if ((prev_state == 1 && state == 0) || + (prev_state == 2 && state == 3)) { + change = 1; + } else if ((prev_state == 2 && state == 0) || + (prev_state == 1 && state == 3)) { + change = 3; + } else { + change = 0; + } + /* Write change */ + data[byte] = (data[byte] & ~((u8)3 << bit)) | + (change << bit); + /* Remember state */ + drvdata->re_state = state; + } + + return 0; +} + static int uclogic_raw_event(struct hid_device *hdev, struct hid_report *report, u8 *data, int size) @@ -265,85 +382,13 @@ static int uclogic_raw_event(struct hid_device *hdev, data[0] = params->frame.id; return 0; } - /* If in-range reports are inverted */ - if (params->pen.inrange == - UCLOGIC_PARAMS_PEN_INRANGE_INVERTED) { - /* Invert the in-range bit */ - data[1] ^= 0x40; - } - /* - * If report contains fragmented high-resolution pen - * coordinates - */ - if (size >= 10 && params->pen.fragmented_hires) { - u8 pressure_low_byte; - u8 pressure_high_byte; - - /* Lift pressure bytes */ - pressure_low_byte = data[6]; - pressure_high_byte = data[7]; - /* - * Move Y coord to make space for high-order X - * coord byte - */ - data[6] = data[5]; - data[5] = data[4]; - /* Move high-order X coord byte */ - data[4] = data[8]; - /* Move high-order Y coord byte */ - data[7] = data[9]; - /* Place pressure bytes */ - data[8] = pressure_low_byte; - data[9] = pressure_high_byte; - } - /* If we need to emulate in-range detection */ - if (params->pen.inrange == UCLOGIC_PARAMS_PEN_INRANGE_NONE) { - /* Set in-range bit */ - data[1] |= 0x40; - /* (Re-)start in-range timeout */ - mod_timer(&drvdata->inrange_timer, - jiffies + msecs_to_jiffies(100)); - } - /* If we report tilt and Y direction is flipped */ - if (size >= 12 && params->pen.tilt_y_flipped) - data[11] = -data[11]; + return uclogic_raw_event_pen(drvdata, data, size); } /* Tweak frame control reports, if necessary */ if ((report->type == HID_INPUT_REPORT) && - (report->id == params->frame.id)) { - /* If need to, and can, set pad device ID for Wacom drivers */ - if (params->frame.dev_id_byte > 0 && - params->frame.dev_id_byte < size) { - data[params->frame.dev_id_byte] = 0xf; - } - /* If need to, and can, read rotary encoder state change */ - if (params->frame.re_lsb > 0 && - params->frame.re_lsb / 8 < size) { - unsigned int byte = params->frame.re_lsb / 8; - unsigned int bit = params->frame.re_lsb % 8; - - u8 change; - u8 prev_state = drvdata->re_state; - /* Read Gray-coded state */ - u8 state = (data[byte] >> bit) & 0x3; - /* Encode state change into 2-bit signed integer */ - if ((prev_state == 1 && state == 0) || - (prev_state == 2 && state == 3)) { - change = 1; - } else if ((prev_state == 2 && state == 0) || - (prev_state == 1 && state == 3)) { - change = 3; - } else { - change = 0; - } - /* Write change */ - data[byte] = (data[byte] & ~((u8)3 << bit)) | - (change << bit); - /* Remember state */ - drvdata->re_state = state; - } - } + (report->id == params->frame.id)) + return uclogic_raw_event_frame(drvdata, data, size); return 0; } From 7f12dd246906de2504b157700a15f49935dc9598 Mon Sep 17 00:00:00 2001 From: Nikolai Kondrashov Date: Thu, 10 Feb 2022 20:04:35 +0100 Subject: [PATCH 055/137] HID: uclogic: Access pen/frame params directly in raw_event handling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Simplify the raw event handling code by accessing the uclogic_params_pen/uclogic_params_frame structs directly. Signed-off-by: Nikolai Kondrashov Signed-off-by: José Expósito Signed-off-by: Jiri Kosina --- drivers/hid/hid-uclogic-core.c | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/drivers/hid/hid-uclogic-core.c b/drivers/hid/hid-uclogic-core.c index 9187fd835a46..56b76d9b46af 100644 --- a/drivers/hid/hid-uclogic-core.c +++ b/drivers/hid/hid-uclogic-core.c @@ -259,13 +259,13 @@ static int uclogic_resume(struct hid_device *hdev) static int uclogic_raw_event_pen(struct uclogic_drvdata *drvdata, u8 *data, int size) { - struct uclogic_params *params = &drvdata->params; + struct uclogic_params_pen *pen = &drvdata->params.pen; WARN_ON(drvdata == NULL); WARN_ON(data == NULL && size != 0); /* If in-range reports are inverted */ - if (params->pen.inrange == + if (pen->inrange == UCLOGIC_PARAMS_PEN_INRANGE_INVERTED) { /* Invert the in-range bit */ data[1] ^= 0x40; @@ -274,7 +274,7 @@ static int uclogic_raw_event_pen(struct uclogic_drvdata *drvdata, * If report contains fragmented high-resolution pen * coordinates */ - if (size >= 10 && params->pen.fragmented_hires) { + if (size >= 10 && pen->fragmented_hires) { u8 pressure_low_byte; u8 pressure_high_byte; @@ -296,7 +296,7 @@ static int uclogic_raw_event_pen(struct uclogic_drvdata *drvdata, data[9] = pressure_high_byte; } /* If we need to emulate in-range detection */ - if (params->pen.inrange == UCLOGIC_PARAMS_PEN_INRANGE_NONE) { + if (pen->inrange == UCLOGIC_PARAMS_PEN_INRANGE_NONE) { /* Set in-range bit */ data[1] |= 0x40; /* (Re-)start in-range timeout */ @@ -304,7 +304,7 @@ static int uclogic_raw_event_pen(struct uclogic_drvdata *drvdata, jiffies + msecs_to_jiffies(100)); } /* If we report tilt and Y direction is flipped */ - if (size >= 12 && params->pen.tilt_y_flipped) + if (size >= 12 && pen->tilt_y_flipped) data[11] = -data[11]; return 0; @@ -323,21 +323,19 @@ static int uclogic_raw_event_pen(struct uclogic_drvdata *drvdata, static int uclogic_raw_event_frame(struct uclogic_drvdata *drvdata, u8 *data, int size) { - struct uclogic_params *params = &drvdata->params; + struct uclogic_params_frame *frame = &drvdata->params.frame; WARN_ON(drvdata == NULL); WARN_ON(data == NULL && size != 0); /* If need to, and can, set pad device ID for Wacom drivers */ - if (params->frame.dev_id_byte > 0 && - params->frame.dev_id_byte < size) { - data[params->frame.dev_id_byte] = 0xf; + if (frame->dev_id_byte > 0 && frame->dev_id_byte < size) { + data[frame->dev_id_byte] = 0xf; } /* If need to, and can, read rotary encoder state change */ - if (params->frame.re_lsb > 0 && - params->frame.re_lsb / 8 < size) { - unsigned int byte = params->frame.re_lsb / 8; - unsigned int bit = params->frame.re_lsb % 8; + if (frame->re_lsb > 0 && frame->re_lsb / 8 < size) { + unsigned int byte = frame->re_lsb / 8; + unsigned int bit = frame->re_lsb % 8; u8 change; u8 prev_state = drvdata->re_state; From fea53b9f159cd039e86aebde5cbbfd37765257fb Mon Sep 17 00:00:00 2001 From: Nikolai Kondrashov Date: Thu, 10 Feb 2022 20:04:36 +0100 Subject: [PATCH 056/137] HID: uclogic: Skip non-input raw events earlier MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Early return when a non-input raw event is received to simplify the logic present in uclogic_raw_event. Signed-off-by: Nikolai Kondrashov Signed-off-by: José Expósito Signed-off-by: Jiri Kosina --- drivers/hid/hid-uclogic-core.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/drivers/hid/hid-uclogic-core.c b/drivers/hid/hid-uclogic-core.c index 56b76d9b46af..8aac3f7a9cda 100644 --- a/drivers/hid/hid-uclogic-core.c +++ b/drivers/hid/hid-uclogic-core.c @@ -368,9 +368,12 @@ static int uclogic_raw_event(struct hid_device *hdev, struct uclogic_drvdata *drvdata = hid_get_drvdata(hdev); struct uclogic_params *params = &drvdata->params; + /* Do not handle anything but input reports */ + if (report->type != HID_INPUT_REPORT) + return 0; + /* Tweak pen reports, if necessary */ if (!params->pen_unused && - (report->type == HID_INPUT_REPORT) && (report->id == params->pen.id) && (size >= 2)) { /* If it's the "virtual" frame controls report */ @@ -384,8 +387,7 @@ static int uclogic_raw_event(struct hid_device *hdev, } /* Tweak frame control reports, if necessary */ - if ((report->type == HID_INPUT_REPORT) && - (report->id == params->frame.id)) + if (report->id == params->frame.id) return uclogic_raw_event_frame(drvdata, data, size); return 0; From 044fa8162dc10686fcffa5501d4ca3947df047ca Mon Sep 17 00:00:00 2001 From: Nikolai Kondrashov Date: Thu, 10 Feb 2022 20:04:37 +0100 Subject: [PATCH 057/137] HID: uclogic: Handle virtual frame reports MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Explicitly change report ID to virtual ID and handle virtual frame reports as real for consistency. Signed-off-by: Nikolai Kondrashov Signed-off-by: José Expósito Signed-off-by: Jiri Kosina --- drivers/hid/hid-uclogic-core.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/drivers/hid/hid-uclogic-core.c b/drivers/hid/hid-uclogic-core.c index 8aac3f7a9cda..73d79d149869 100644 --- a/drivers/hid/hid-uclogic-core.c +++ b/drivers/hid/hid-uclogic-core.c @@ -365,6 +365,7 @@ static int uclogic_raw_event(struct hid_device *hdev, struct hid_report *report, u8 *data, int size) { + unsigned int report_id = report->id; struct uclogic_drvdata *drvdata = hid_get_drvdata(hdev); struct uclogic_params *params = &drvdata->params; @@ -374,20 +375,20 @@ static int uclogic_raw_event(struct hid_device *hdev, /* Tweak pen reports, if necessary */ if (!params->pen_unused && - (report->id == params->pen.id) && + (report_id == params->pen.id) && (size >= 2)) { /* If it's the "virtual" frame controls report */ if (params->frame.id != 0 && data[1] & params->pen_frame_flag) { /* Change to virtual frame controls report ID */ - data[0] = params->frame.id; - return 0; + report_id = data[0] = params->frame.id; + } else { + return uclogic_raw_event_pen(drvdata, data, size); } - return uclogic_raw_event_pen(drvdata, data, size); } /* Tweak frame control reports, if necessary */ - if (report->id == params->frame.id) + if (report_id == params->frame.id) return uclogic_raw_event_frame(drvdata, data, size); return 0; From f1d1b3a9b41125d8a98b6ec94713715af332ffb1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Exp=C3=B3sito?= Date: Tue, 8 Feb 2022 19:37:02 +0100 Subject: [PATCH 058/137] HID: apple: Refactor key translation setup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The code used to map the apple_key_translation structs is duplicated. Extract it to a common function. Refactor, no functional changes. Signed-off-by: José Expósito Signed-off-by: Jiri Kosina --- drivers/hid/hid-apple.c | 36 ++++++++++++++++-------------------- 1 file changed, 16 insertions(+), 20 deletions(-) diff --git a/drivers/hid/hid-apple.c b/drivers/hid/hid-apple.c index 7dc89dc6b0f0..5d2778b6dbcb 100644 --- a/drivers/hid/hid-apple.c +++ b/drivers/hid/hid-apple.c @@ -202,6 +202,15 @@ static const struct apple_key_translation swapped_fn_leftctrl_keys[] = { { } }; +static inline void apple_setup_key_translation(struct input_dev *input, + const struct apple_key_translation *table) +{ + const struct apple_key_translation *trans; + + for (trans = table; trans->from; trans++) + set_bit(trans->to, input->keybit); +} + static const struct apple_key_translation *apple_find_translation( const struct apple_key_translation *table, u16 from) { @@ -452,30 +461,17 @@ static __u8 *apple_report_fixup(struct hid_device *hdev, __u8 *rdesc, static void apple_setup_input(struct input_dev *input) { - const struct apple_key_translation *trans; - set_bit(KEY_NUMLOCK, input->keybit); /* Enable all needed keys */ - for (trans = apple_fn_keys; trans->from; trans++) - set_bit(trans->to, input->keybit); + apple_setup_key_translation(input, apple_fn_keys); + apple_setup_key_translation(input, powerbook_fn_keys); + apple_setup_key_translation(input, powerbook_numlock_keys); + apple_setup_key_translation(input, apple_iso_keyboard); + apple_setup_key_translation(input, apple2021_fn_keys); - for (trans = powerbook_fn_keys; trans->from; trans++) - set_bit(trans->to, input->keybit); - - for (trans = powerbook_numlock_keys; trans->from; trans++) - set_bit(trans->to, input->keybit); - - for (trans = apple_iso_keyboard; trans->from; trans++) - set_bit(trans->to, input->keybit); - - for (trans = apple2021_fn_keys; trans->from; trans++) - set_bit(trans->to, input->keybit); - - if (swap_fn_leftctrl) { - for (trans = swapped_fn_leftctrl_keys; trans->from; trans++) - set_bit(trans->to, input->keybit); - } + if (swap_fn_leftctrl) + apple_setup_key_translation(input, swapped_fn_leftctrl_keys); } static int apple_input_mapping(struct hid_device *hdev, struct hid_input *hi, From 0fea6fe7d5ef1b5fa5f78048d4729f85181c04ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Exp=C3=B3sito?= Date: Tue, 8 Feb 2022 19:37:03 +0100 Subject: [PATCH 059/137] HID: apple: Magic Keyboard first generation FN key mapping MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The function key mapping for the Magic Keyboard first generation (2007, 2009 and 2011 aluminum wireless models) was not present and the default one was used instead. This caused two main issues: - The F5 and F6 keys were sending KEY_KBDILLUMDOWN and KEY_KBDILLUMUP; however, the keyboard is not backlited. - The keyboard has the APPLE_NUMLOCK_EMULATION quirk with F6 set as the KEY_NUMLOCK key by "powerbook_numlock_keys". However, because F6 was mapped to KEY_KBDILLUMUP by the default mapping it was not possible to switch the numlock status. This means that, if numlock was enabled on session startup, it was not possible to disable it without connecting another keyboard. Add a custom translation table for the device leaving F5 unassigned and using F6 as the KEY_NUMLOCK key. Signed-off-by: José Expósito Signed-off-by: Jiri Kosina --- drivers/hid/hid-apple.c | 38 +++++++++++++++++++++++++++++++++++--- 1 file changed, 35 insertions(+), 3 deletions(-) diff --git a/drivers/hid/hid-apple.c b/drivers/hid/hid-apple.c index 5d2778b6dbcb..8097765f9df6 100644 --- a/drivers/hid/hid-apple.c +++ b/drivers/hid/hid-apple.c @@ -76,6 +76,27 @@ struct apple_key_translation { u8 flags; }; +static const struct apple_key_translation magic_keyboard_alu_fn_keys[] = { + { KEY_BACKSPACE, KEY_DELETE }, + { KEY_ENTER, KEY_INSERT }, + { KEY_F1, KEY_BRIGHTNESSDOWN, APPLE_FLAG_FKEY }, + { KEY_F2, KEY_BRIGHTNESSUP, APPLE_FLAG_FKEY }, + { KEY_F3, KEY_SCALE, APPLE_FLAG_FKEY }, + { KEY_F4, KEY_DASHBOARD, APPLE_FLAG_FKEY }, + { KEY_F6, KEY_NUMLOCK, APPLE_FLAG_FKEY }, + { KEY_F7, KEY_PREVIOUSSONG, APPLE_FLAG_FKEY }, + { KEY_F8, KEY_PLAYPAUSE, APPLE_FLAG_FKEY }, + { KEY_F9, KEY_NEXTSONG, APPLE_FLAG_FKEY }, + { KEY_F10, KEY_MUTE, APPLE_FLAG_FKEY }, + { KEY_F11, KEY_VOLUMEDOWN, APPLE_FLAG_FKEY }, + { KEY_F12, KEY_VOLUMEUP, APPLE_FLAG_FKEY }, + { KEY_UP, KEY_PAGEUP }, + { KEY_DOWN, KEY_PAGEDOWN }, + { KEY_LEFT, KEY_HOME }, + { KEY_RIGHT, KEY_END }, + { } +}; + static const struct apple_key_translation apple2021_fn_keys[] = { { KEY_BACKSPACE, KEY_DELETE }, { KEY_ENTER, KEY_INSERT }, @@ -251,9 +272,19 @@ static int hidinput_apple_event(struct hid_device *hid, struct input_dev *input, } if (fnmode) { - if (hid->product == USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_2021 || - hid->product == USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_FINGERPRINT_2021 || - hid->product == USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_NUMPAD_2021) + if (hid->product == USB_DEVICE_ID_APPLE_ALU_WIRELESS_ANSI || + hid->product == USB_DEVICE_ID_APPLE_ALU_WIRELESS_ISO || + hid->product == USB_DEVICE_ID_APPLE_ALU_WIRELESS_JIS || + hid->product == USB_DEVICE_ID_APPLE_ALU_WIRELESS_2009_ANSI || + hid->product == USB_DEVICE_ID_APPLE_ALU_WIRELESS_2009_ISO || + hid->product == USB_DEVICE_ID_APPLE_ALU_WIRELESS_2009_JIS || + hid->product == USB_DEVICE_ID_APPLE_ALU_WIRELESS_2011_ANSI || + hid->product == USB_DEVICE_ID_APPLE_ALU_WIRELESS_2011_ISO || + hid->product == USB_DEVICE_ID_APPLE_ALU_WIRELESS_2011_JIS) + table = magic_keyboard_alu_fn_keys; + else if (hid->product == USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_2021 || + hid->product == USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_FINGERPRINT_2021 || + hid->product == USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_NUMPAD_2021) table = apple2021_fn_keys; else if (hid->product >= USB_DEVICE_ID_APPLE_WELLSPRING4_ANSI && hid->product <= USB_DEVICE_ID_APPLE_WELLSPRING4A_JIS) @@ -468,6 +499,7 @@ static void apple_setup_input(struct input_dev *input) apple_setup_key_translation(input, powerbook_fn_keys); apple_setup_key_translation(input, powerbook_numlock_keys); apple_setup_key_translation(input, apple_iso_keyboard); + apple_setup_key_translation(input, magic_keyboard_alu_fn_keys); apple_setup_key_translation(input, apple2021_fn_keys); if (swap_fn_leftctrl) From 250b369ed2380ba9accda31931e63ae351ab9f6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Exp=C3=B3sito?= Date: Tue, 8 Feb 2022 19:37:04 +0100 Subject: [PATCH 060/137] HID: apple: Magic Keyboard 2015 FN key mapping MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Magic Keyboard 2015 function key mapping was not present and the default mapping was used. While this worked for most keys, the F5 and F6 keys were sending KEY_KBDILLUMDOWN and KEY_KBDILLUMUP; however, the keyboard is not backlited. Add a custom translation table for the keyboard leaving F5 and F6 unassigned to mimic the default behavior on macOS. Signed-off-by: José Expósito Signed-off-by: Jiri Kosina --- drivers/hid/hid-apple.c | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/drivers/hid/hid-apple.c b/drivers/hid/hid-apple.c index 8097765f9df6..2ce404b46adf 100644 --- a/drivers/hid/hid-apple.c +++ b/drivers/hid/hid-apple.c @@ -97,6 +97,26 @@ static const struct apple_key_translation magic_keyboard_alu_fn_keys[] = { { } }; +static const struct apple_key_translation magic_keyboard_2015_fn_keys[] = { + { KEY_BACKSPACE, KEY_DELETE }, + { KEY_ENTER, KEY_INSERT }, + { KEY_F1, KEY_BRIGHTNESSDOWN, APPLE_FLAG_FKEY }, + { KEY_F2, KEY_BRIGHTNESSUP, APPLE_FLAG_FKEY }, + { KEY_F3, KEY_SCALE, APPLE_FLAG_FKEY }, + { KEY_F4, KEY_DASHBOARD, APPLE_FLAG_FKEY }, + { KEY_F7, KEY_PREVIOUSSONG, APPLE_FLAG_FKEY }, + { KEY_F8, KEY_PLAYPAUSE, APPLE_FLAG_FKEY }, + { KEY_F9, KEY_NEXTSONG, APPLE_FLAG_FKEY }, + { KEY_F10, KEY_MUTE, APPLE_FLAG_FKEY }, + { KEY_F11, KEY_VOLUMEDOWN, APPLE_FLAG_FKEY }, + { KEY_F12, KEY_VOLUMEUP, APPLE_FLAG_FKEY }, + { KEY_UP, KEY_PAGEUP }, + { KEY_DOWN, KEY_PAGEDOWN }, + { KEY_LEFT, KEY_HOME }, + { KEY_RIGHT, KEY_END }, + { } +}; + static const struct apple_key_translation apple2021_fn_keys[] = { { KEY_BACKSPACE, KEY_DELETE }, { KEY_ENTER, KEY_INSERT }, @@ -282,6 +302,9 @@ static int hidinput_apple_event(struct hid_device *hid, struct input_dev *input, hid->product == USB_DEVICE_ID_APPLE_ALU_WIRELESS_2011_ISO || hid->product == USB_DEVICE_ID_APPLE_ALU_WIRELESS_2011_JIS) table = magic_keyboard_alu_fn_keys; + else if (hid->product == USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_2015 || + hid->product == USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_NUMPAD_2015) + table = magic_keyboard_2015_fn_keys; else if (hid->product == USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_2021 || hid->product == USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_FINGERPRINT_2021 || hid->product == USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_NUMPAD_2021) @@ -500,6 +523,7 @@ static void apple_setup_input(struct input_dev *input) apple_setup_key_translation(input, powerbook_numlock_keys); apple_setup_key_translation(input, apple_iso_keyboard); apple_setup_key_translation(input, magic_keyboard_alu_fn_keys); + apple_setup_key_translation(input, magic_keyboard_2015_fn_keys); apple_setup_key_translation(input, apple2021_fn_keys); if (swap_fn_leftctrl) From 8ae5c16c9d421d43f32f66d2308031f1bd3f9336 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Exp=C3=B3sito?= Date: Tue, 8 Feb 2022 19:50:09 +0100 Subject: [PATCH 061/137] HID: apple: Report Magic Keyboard 2021 battery over USB MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Like the Apple Magic Keyboard 2015, when connected over USB, the 2021 version registers 2 different interfaces. One of them is used to report the battery level. However, unlike when connected over Bluetooth, the battery level is not reported automatically and it is required to fetch it manually. Add the APPLE_RDESC_BATTERY quirk to fix the battery report descriptor and manually fetch the battery level. Tested with the ANSI, ISO and JIS variants of the keyboard. Signed-off-by: José Expósito Signed-off-by: Jiri Kosina --- drivers/hid/hid-apple.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/hid/hid-apple.c b/drivers/hid/hid-apple.c index 2ce404b46adf..3ed7a6998492 100644 --- a/drivers/hid/hid-apple.c +++ b/drivers/hid/hid-apple.c @@ -800,7 +800,7 @@ static const struct hid_device_id apple_devices[] = { { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_GEYSER1_TP_ONLY), .driver_data = APPLE_NUMLOCK_EMULATION | APPLE_HAS_FN }, { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_2021), - .driver_data = APPLE_HAS_FN | APPLE_ISO_TILDE_QUIRK }, + .driver_data = APPLE_HAS_FN | APPLE_ISO_TILDE_QUIRK | APPLE_RDESC_BATTERY }, { HID_BLUETOOTH_DEVICE(BT_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_2021), .driver_data = APPLE_HAS_FN | APPLE_ISO_TILDE_QUIRK }, { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_FINGERPRINT_2021), From cbfcfbfc384890a062a5d0cc4792df094a6cc7a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Exp=C3=B3sito?= Date: Tue, 8 Feb 2022 19:55:30 +0100 Subject: [PATCH 062/137] HID: apple: Report Magic Keyboard 2021 with fingerprint reader battery over USB MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Like the Apple Magic Keyboard 2015, when connected over USB, the 2021 version with fingerprint reader registers 2 different interfaces. One of them is used to report the battery level. However, unlike when connected over Bluetooth, the battery level is not reported automatically and it is required to fetch it manually. Add the APPLE_RDESC_BATTERY quirk to fix the battery report descriptor and manually fetch the battery level. Tested with the ANSI variant of the keyboard with and without numpad. Signed-off-by: José Expósito Signed-off-by: Jiri Kosina --- drivers/hid/hid-apple.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/hid/hid-apple.c b/drivers/hid/hid-apple.c index 3ed7a6998492..b3b33813d16c 100644 --- a/drivers/hid/hid-apple.c +++ b/drivers/hid/hid-apple.c @@ -804,11 +804,11 @@ static const struct hid_device_id apple_devices[] = { { HID_BLUETOOTH_DEVICE(BT_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_2021), .driver_data = APPLE_HAS_FN | APPLE_ISO_TILDE_QUIRK }, { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_FINGERPRINT_2021), - .driver_data = APPLE_HAS_FN | APPLE_ISO_TILDE_QUIRK }, + .driver_data = APPLE_HAS_FN | APPLE_ISO_TILDE_QUIRK | APPLE_RDESC_BATTERY }, { HID_BLUETOOTH_DEVICE(BT_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_FINGERPRINT_2021), .driver_data = APPLE_HAS_FN | APPLE_ISO_TILDE_QUIRK }, { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_NUMPAD_2021), - .driver_data = APPLE_HAS_FN | APPLE_ISO_TILDE_QUIRK }, + .driver_data = APPLE_HAS_FN | APPLE_ISO_TILDE_QUIRK | APPLE_RDESC_BATTERY }, { HID_BLUETOOTH_DEVICE(BT_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_NUMPAD_2021), .driver_data = APPLE_HAS_FN | APPLE_ISO_TILDE_QUIRK }, From 9018eacbe623b2c3535da37035e5f22d3d70b6ce Mon Sep 17 00:00:00 2001 From: Paul Pawlowski Date: Thu, 3 Feb 2022 12:21:13 +0000 Subject: [PATCH 063/137] HID: apple: Add support for keyboard backlight on certain T2 Macs. This patch introduces the requisite plumbing for supporting keyboard backlight on T2-attached, USB exposed models. The quirk mechanism was used to reuse the existing hid-apple driver. Signed-off-by: Paul Pawlowski Signed-off-by: Aun-Ali Zaidi Signed-off-by: Aditya Garg Signed-off-by: Jiri Kosina --- drivers/hid/hid-apple.c | 125 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 125 insertions(+) diff --git a/drivers/hid/hid-apple.c b/drivers/hid/hid-apple.c index b3b33813d16c..8998d005b1db 100644 --- a/drivers/hid/hid-apple.c +++ b/drivers/hid/hid-apple.c @@ -7,6 +7,7 @@ * Copyright (c) 2005 Michael Haboustak for Concept2, Inc * Copyright (c) 2006-2007 Jiri Kosina * Copyright (c) 2008 Jiri Slaby + * Copyright (c) 2019 Paul Pawlowski */ /* @@ -33,6 +34,7 @@ /* BIT(7) reserved, was: APPLE_IGNORE_HIDINPUT */ #define APPLE_NUMLOCK_EMULATION BIT(8) #define APPLE_RDESC_BATTERY BIT(9) +#define APPLE_BACKLIGHT_CTL BIT(10) #define APPLE_FLAG_FKEY 0x01 @@ -61,6 +63,12 @@ MODULE_PARM_DESC(swap_fn_leftctrl, "Swap the Fn and left Control keys. " "(For people who want to keep PC keyboard muscle memory. " "[0] = as-is, Mac layout, 1 = swapped, PC layout)"); +struct apple_sc_backlight { + struct led_classdev cdev; + struct hid_device *hdev; + unsigned short backlight_off, backlight_on_min, backlight_on_max; +}; + struct apple_sc { struct hid_device *hdev; unsigned long quirks; @@ -68,6 +76,7 @@ struct apple_sc { unsigned int fn_found; DECLARE_BITMAP(pressed_numlock, KEY_CNT); struct timer_list battery_timer; + struct apple_sc_backlight *backlight; }; struct apple_key_translation { @@ -117,6 +126,20 @@ static const struct apple_key_translation magic_keyboard_2015_fn_keys[] = { { } }; +struct apple_backlight_config_report { + u8 report_id; + u8 version; + u16 backlight_off, backlight_on_min, backlight_on_max; +}; + +struct apple_backlight_set_report { + u8 report_id; + u8 version; + u16 backlight; + u16 rate; +}; + + static const struct apple_key_translation apple2021_fn_keys[] = { { KEY_BACKSPACE, KEY_DELETE }, { KEY_ENTER, KEY_INSERT }, @@ -582,6 +605,105 @@ static int apple_input_configured(struct hid_device *hdev, return 0; } +static bool apple_backlight_check_support(struct hid_device *hdev) +{ + int i; + unsigned int hid; + struct hid_report *report; + + list_for_each_entry(report, &hdev->report_enum[HID_INPUT_REPORT].report_list, list) { + for (i = 0; i < report->maxfield; i++) { + hid = report->field[i]->usage->hid; + if ((hid & HID_USAGE_PAGE) == HID_UP_MSVENDOR && (hid & HID_USAGE) == 0xf) + return true; + } + } + + return false; +} + +static int apple_backlight_set(struct hid_device *hdev, u16 value, u16 rate) +{ + int ret = 0; + struct apple_backlight_set_report *rep; + + rep = kmalloc(sizeof(*rep), GFP_KERNEL); + if (rep == NULL) + return -ENOMEM; + + rep->report_id = 0xB0; + rep->version = 1; + rep->backlight = value; + rep->rate = rate; + + ret = hid_hw_raw_request(hdev, 0xB0u, (u8 *) rep, sizeof(*rep), + HID_OUTPUT_REPORT, HID_REQ_SET_REPORT); + + kfree(rep); + return ret; +} + +static int apple_backlight_led_set(struct led_classdev *led_cdev, + enum led_brightness brightness) +{ + struct apple_sc_backlight *backlight = container_of(led_cdev, + struct apple_sc_backlight, cdev); + + return apple_backlight_set(backlight->hdev, brightness, 0); +} + +static int apple_backlight_init(struct hid_device *hdev) +{ + int ret; + struct apple_sc *asc = hid_get_drvdata(hdev); + struct apple_backlight_config_report *rep; + + if (!apple_backlight_check_support(hdev)) + return -EINVAL; + + rep = kmalloc(0x200, GFP_KERNEL); + if (rep == NULL) + return -ENOMEM; + + ret = hid_hw_raw_request(hdev, 0xBFu, (u8 *) rep, sizeof(*rep), + HID_FEATURE_REPORT, HID_REQ_GET_REPORT); + if (ret < 0) { + hid_err(hdev, "backlight request failed: %d\n", ret); + goto cleanup_and_exit; + } + if (ret < 8 || rep->version != 1) { + hid_err(hdev, "backlight config struct: bad version %i\n", rep->version); + ret = -EINVAL; + goto cleanup_and_exit; + } + + hid_dbg(hdev, "backlight config: off=%u, on_min=%u, on_max=%u\n", + rep->backlight_off, rep->backlight_on_min, rep->backlight_on_max); + + asc->backlight = devm_kzalloc(&hdev->dev, sizeof(*asc->backlight), GFP_KERNEL); + if (!asc->backlight) { + ret = -ENOMEM; + goto cleanup_and_exit; + } + + asc->backlight->hdev = hdev; + asc->backlight->cdev.name = "apple::kbd_backlight"; + asc->backlight->cdev.max_brightness = rep->backlight_on_max; + asc->backlight->cdev.brightness_set_blocking = apple_backlight_led_set; + + ret = apple_backlight_set(hdev, 0, 0); + if (ret < 0) { + hid_err(hdev, "backlight set request failed: %d\n", ret); + goto cleanup_and_exit; + } + + ret = devm_led_classdev_register(&hdev->dev, &asc->backlight->cdev); + +cleanup_and_exit: + kfree(rep); + return ret; +} + static int apple_probe(struct hid_device *hdev, const struct hid_device_id *id) { @@ -617,6 +739,9 @@ static int apple_probe(struct hid_device *hdev, jiffies + msecs_to_jiffies(APPLE_BATTERY_TIMEOUT_MS)); apple_fetch_battery(hdev); + if (quirks & APPLE_BACKLIGHT_CTL) + apple_backlight_init(hdev); + return 0; } From 42f6a2d300233ae3da48f90f3463695b89c21ea0 Mon Sep 17 00:00:00 2001 From: Aun-Ali Zaidi Date: Thu, 3 Feb 2022 12:22:09 +0000 Subject: [PATCH 064/137] HID: apple: Add necessary IDs and configuration for T2 Macs. This patch adds the necessary IDs and configuration for Macs with the T2 Security chip. Signed-off-by: Aun-Ali Zaidi Signed-off-by: Aditya Garg Signed-off-by: Jiri Kosina --- drivers/hid/hid-apple.c | 16 ++++++++++++++++ drivers/hid/hid-ids.h | 8 ++++++++ drivers/hid/hid-quirks.c | 16 ++++++++++++++++ 3 files changed, 40 insertions(+) diff --git a/drivers/hid/hid-apple.c b/drivers/hid/hid-apple.c index 8998d005b1db..bf48293aeb35 100644 --- a/drivers/hid/hid-apple.c +++ b/drivers/hid/hid-apple.c @@ -913,6 +913,22 @@ static const struct hid_device_id apple_devices[] = { .driver_data = APPLE_HAS_FN | APPLE_ISO_TILDE_QUIRK }, { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING9_JIS), .driver_data = APPLE_HAS_FN | APPLE_RDESC_JIS }, + { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRINGT2_J140K), + .driver_data = APPLE_HAS_FN | APPLE_BACKLIGHT_CTL }, + { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRINGT2_J132), + .driver_data = APPLE_HAS_FN | APPLE_BACKLIGHT_CTL }, + { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRINGT2_J680), + .driver_data = APPLE_HAS_FN | APPLE_BACKLIGHT_CTL }, + { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRINGT2_J213), + .driver_data = APPLE_HAS_FN | APPLE_BACKLIGHT_CTL }, + { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRINGT2_J214K), + .driver_data = APPLE_HAS_FN }, + { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRINGT2_J223), + .driver_data = APPLE_HAS_FN }, + { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRINGT2_J230K), + .driver_data = APPLE_HAS_FN }, + { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRINGT2_J152F), + .driver_data = APPLE_HAS_FN }, { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_ALU_WIRELESS_2009_ANSI), .driver_data = APPLE_NUMLOCK_EMULATION | APPLE_HAS_FN }, { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_ALU_WIRELESS_2009_ISO), diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h index 78bd3ddda442..6c2a36b16ed2 100644 --- a/drivers/hid/hid-ids.h +++ b/drivers/hid/hid-ids.h @@ -167,6 +167,14 @@ #define USB_DEVICE_ID_APPLE_WELLSPRING9_ANSI 0x0272 #define USB_DEVICE_ID_APPLE_WELLSPRING9_ISO 0x0273 #define USB_DEVICE_ID_APPLE_WELLSPRING9_JIS 0x0274 +#define USB_DEVICE_ID_APPLE_WELLSPRINGT2_J140K 0x027a +#define USB_DEVICE_ID_APPLE_WELLSPRINGT2_J132 0x027b +#define USB_DEVICE_ID_APPLE_WELLSPRINGT2_J680 0x027c +#define USB_DEVICE_ID_APPLE_WELLSPRINGT2_J213 0x027d +#define USB_DEVICE_ID_APPLE_WELLSPRINGT2_J214K 0x027e +#define USB_DEVICE_ID_APPLE_WELLSPRINGT2_J223 0x027f +#define USB_DEVICE_ID_APPLE_WELLSPRINGT2_J230K 0x0280 +#define USB_DEVICE_ID_APPLE_WELLSPRINGT2_J152F 0x0340 #define USB_DEVICE_ID_APPLE_FOUNTAIN_TP_ONLY 0x030a #define USB_DEVICE_ID_APPLE_GEYSER1_TP_ONLY 0x030b #define USB_DEVICE_ID_APPLE_IRCONTROL 0x8240 diff --git a/drivers/hid/hid-quirks.c b/drivers/hid/hid-quirks.c index c066ba901867..dc67717d2dab 100644 --- a/drivers/hid/hid-quirks.c +++ b/drivers/hid/hid-quirks.c @@ -295,6 +295,14 @@ static const struct hid_device_id hid_have_special_driver[] = { { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING9_ANSI) }, { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING9_ISO) }, { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING9_JIS) }, + { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRINGT2_J140K) }, + { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRINGT2_J132) }, + { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRINGT2_J680) }, + { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRINGT2_J213) }, + { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRINGT2_J214K) }, + { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRINGT2_J223) }, + { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRINGT2_J230K) }, + { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRINGT2_J152F) }, { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_ALU_WIRELESS_2009_ANSI) }, { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_ALU_WIRELESS_2009_ISO) }, { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_ALU_WIRELESS_2009_JIS) }, @@ -930,6 +938,14 @@ static const struct hid_device_id hid_mouse_ignore_list[] = { { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING9_ANSI) }, { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING9_ISO) }, { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRING9_JIS) }, + { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRINGT2_J140K) }, + { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRINGT2_J132) }, + { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRINGT2_J680) }, + { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRINGT2_J213) }, + { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRINGT2_J214K) }, + { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRINGT2_J223) }, + { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRINGT2_J230K) }, + { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_WELLSPRINGT2_J152F) }, { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_FOUNTAIN_TP_ONLY) }, { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_GEYSER1_TP_ONLY) }, { } From c5f09b1b45cbb90147846f82ec0489789c99667e Mon Sep 17 00:00:00 2001 From: Aditya Garg Date: Thu, 3 Feb 2022 12:23:02 +0000 Subject: [PATCH 065/137] HID: apple: Add fn mapping for MacBook Pros with Touch Bar This patch adds the Fn mapping for keyboards on certain T2 Macs. [jkosina@suse.cz: rebase on top of apple_setup_input() refactoring] Signed-off-by: Aditya Garg Signed-off-by: Jiri Kosina --- drivers/hid/hid-apple.c | 58 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/drivers/hid/hid-apple.c b/drivers/hid/hid-apple.c index bf48293aeb35..0cf35caee9fa 100644 --- a/drivers/hid/hid-apple.c +++ b/drivers/hid/hid-apple.c @@ -183,6 +183,51 @@ static const struct apple_key_translation macbookair_fn_keys[] = { { } }; +static const struct apple_key_translation macbookpro_no_esc_fn_keys[] = { + { KEY_BACKSPACE, KEY_DELETE }, + { KEY_ENTER, KEY_INSERT }, + { KEY_GRAVE, KEY_ESC }, + { KEY_1, KEY_F1 }, + { KEY_2, KEY_F2 }, + { KEY_3, KEY_F3 }, + { KEY_4, KEY_F4 }, + { KEY_5, KEY_F5 }, + { KEY_6, KEY_F6 }, + { KEY_7, KEY_F7 }, + { KEY_8, KEY_F8 }, + { KEY_9, KEY_F9 }, + { KEY_0, KEY_F10 }, + { KEY_MINUS, KEY_F11 }, + { KEY_EQUAL, KEY_F12 }, + { KEY_UP, KEY_PAGEUP }, + { KEY_DOWN, KEY_PAGEDOWN }, + { KEY_LEFT, KEY_HOME }, + { KEY_RIGHT, KEY_END }, + { } +}; + +static const struct apple_key_translation macbookpro_dedicated_esc_fn_keys[] = { + { KEY_BACKSPACE, KEY_DELETE }, + { KEY_ENTER, KEY_INSERT }, + { KEY_1, KEY_F1 }, + { KEY_2, KEY_F2 }, + { KEY_3, KEY_F3 }, + { KEY_4, KEY_F4 }, + { KEY_5, KEY_F5 }, + { KEY_6, KEY_F6 }, + { KEY_7, KEY_F7 }, + { KEY_8, KEY_F8 }, + { KEY_9, KEY_F9 }, + { KEY_0, KEY_F10 }, + { KEY_MINUS, KEY_F11 }, + { KEY_EQUAL, KEY_F12 }, + { KEY_UP, KEY_PAGEUP }, + { KEY_DOWN, KEY_PAGEDOWN }, + { KEY_LEFT, KEY_HOME }, + { KEY_RIGHT, KEY_END }, + { } +}; + static const struct apple_key_translation apple_fn_keys[] = { { KEY_BACKSPACE, KEY_DELETE }, { KEY_ENTER, KEY_INSERT }, @@ -332,6 +377,17 @@ static int hidinput_apple_event(struct hid_device *hid, struct input_dev *input, hid->product == USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_FINGERPRINT_2021 || hid->product == USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_NUMPAD_2021) table = apple2021_fn_keys; + else if (hid->product == USB_DEVICE_ID_APPLE_WELLSPRINGT2_J132 || + hid->product == USB_DEVICE_ID_APPLE_WELLSPRINGT2_J680 || + hid->product == USB_DEVICE_ID_APPLE_WELLSPRINGT2_J213) + table = macbookpro_no_esc_fn_keys; + else if (hid->product == USB_DEVICE_ID_APPLE_WELLSPRINGT2_J214K || + hid->product == USB_DEVICE_ID_APPLE_WELLSPRINGT2_J223 || + hid->product == USB_DEVICE_ID_APPLE_WELLSPRINGT2_J152F) + table = macbookpro_dedicated_esc_fn_keys; + else if (hid->product == USB_DEVICE_ID_APPLE_WELLSPRINGT2_J140K || + hid->product == USB_DEVICE_ID_APPLE_WELLSPRINGT2_J230K) + table = apple_fn_keys; else if (hid->product >= USB_DEVICE_ID_APPLE_WELLSPRING4_ANSI && hid->product <= USB_DEVICE_ID_APPLE_WELLSPRING4A_JIS) table = macbookair_fn_keys; @@ -548,6 +604,8 @@ static void apple_setup_input(struct input_dev *input) apple_setup_key_translation(input, magic_keyboard_alu_fn_keys); apple_setup_key_translation(input, magic_keyboard_2015_fn_keys); apple_setup_key_translation(input, apple2021_fn_keys); + apple_setup_key_translation(input, macbookpro_no_esc_fn_keys); + apple_setup_key_translation(input, macbookpro_dedicated_esc_fn_keys); if (swap_fn_leftctrl) apple_setup_key_translation(input, swapped_fn_leftctrl_keys); From 047b6188b66e42513a2b0d36244f03d06f882e59 Mon Sep 17 00:00:00 2001 From: Jelle van der Waa Date: Sun, 16 Jan 2022 16:34:25 +0100 Subject: [PATCH 066/137] HID: Add driver for Razer Blackwidow keyboards Add a driver to enable the macro keys (M1 - M5) by default, these are mapped to XF86Tools and XF86Launch5 - XF86Launch8. The driver remaps them by default to macro keys with an option to retain the old mapping which users most likely already use as there are many scripts to enable the macro keys available on Github and other websites. Signed-off-by: Jelle van der Waa Signed-off-by: Jiri Kosina --- drivers/hid/Kconfig | 7 +++ drivers/hid/Makefile | 1 + drivers/hid/hid-ids.h | 3 + drivers/hid/hid-razer.c | 125 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 136 insertions(+) create mode 100644 drivers/hid/hid-razer.c diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig index f5544157576c..66a5200ce83a 100644 --- a/drivers/hid/Kconfig +++ b/drivers/hid/Kconfig @@ -929,6 +929,13 @@ config PLAYSTATION_FF Say Y here if you would like to enable force feedback support for PlayStation game controllers. +config HID_RAZER + tristate "Razer non-fully HID-compliant devices" + depends on HID + help + Support for Razer devices that are not fully compliant with the + HID standard. + config HID_PRIMAX tristate "Primax non-fully HID-compliant devices" depends on HID diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile index 6d3e630e81af..501731380f1f 100644 --- a/drivers/hid/Makefile +++ b/drivers/hid/Makefile @@ -99,6 +99,7 @@ hid-picolcd-$(CONFIG_DEBUG_FS) += hid-picolcd_debugfs.o obj-$(CONFIG_HID_PLANTRONICS) += hid-plantronics.o obj-$(CONFIG_HID_PLAYSTATION) += hid-playstation.o obj-$(CONFIG_HID_PRIMAX) += hid-primax.o +obj-$(CONFIG_HID_RAZER) += hid-razer.o obj-$(CONFIG_HID_REDRAGON) += hid-redragon.o obj-$(CONFIG_HID_RETRODE) += hid-retrode.o obj-$(CONFIG_HID_ROCCAT) += hid-roccat.o hid-roccat-common.o \ diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h index 78bd3ddda442..43d0021ba0ef 100644 --- a/drivers/hid/hid-ids.h +++ b/drivers/hid/hid-ids.h @@ -1030,6 +1030,9 @@ #define I2C_PRODUCT_ID_RAYDIUM_3118 0x3118 #define USB_VENDOR_ID_RAZER 0x1532 +#define USB_DEVICE_ID_RAZER_BLACKWIDOW_ULTIMATE 0x010D +#define USB_DEVICE_ID_RAZER_BLACKWIDOW 0x010e +#define USB_DEVICE_ID_RAZER_BLACKWIDOW_CLASSIC 0x011b #define USB_DEVICE_ID_RAZER_BLADE_14 0x011D #define USB_VENDOR_ID_REALTEK 0x0bda diff --git a/drivers/hid/hid-razer.c b/drivers/hid/hid-razer.c new file mode 100644 index 000000000000..740df148b0ef --- /dev/null +++ b/drivers/hid/hid-razer.c @@ -0,0 +1,125 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * HID driver for gaming keys on Razer Blackwidow gaming keyboards + * Macro Key Keycodes: M1 = 191, M2 = 192, M3 = 193, M4 = 194, M5 = 195 + * + * Copyright (c) 2021 Jelle van der Waa + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "hid-ids.h" + +#define map_key_clear(c) hid_map_usage_clear(hi, usage, bit, max, EV_KEY, (c)) + +#define RAZER_BLACKWIDOW_TRANSFER_BUF_SIZE 91 + +static bool macro_key_remapping = 1; +module_param(macro_key_remapping, bool, 0644); +MODULE_PARM_DESC(macro_key_remapping, " on (Y) off (N)"); + + +static unsigned char blackwidow_init[RAZER_BLACKWIDOW_TRANSFER_BUF_SIZE] = { + 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x04, + 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x04, 0x00 +}; + +static int razer_input_mapping(struct hid_device *hdev, + struct hid_input *hi, struct hid_field *field, + struct hid_usage *usage, unsigned long **bit, int *max) +{ + + if (!macro_key_remapping) + return 0; + + if ((usage->hid & HID_UP_KEYBOARD) != HID_UP_KEYBOARD) + return 0; + + switch (usage->hid & ~HID_UP_KEYBOARD) { + case 0x68: + map_key_clear(KEY_MACRO1); + return 1; + case 0x69: + map_key_clear(KEY_MACRO2); + return 1; + case 0x6a: + map_key_clear(KEY_MACRO3); + return 1; + case 0x6b: + map_key_clear(KEY_MACRO4); + return 1; + case 0x6c: + map_key_clear(KEY_MACRO5); + return 1; + } + + return 0; +} + +static int razer_probe(struct hid_device *hdev, const struct hid_device_id *id) +{ + char *buf; + int ret = 0; + + ret = hid_parse(hdev); + if (ret) + return ret; + + /* + * Only send the enable macro keys command for the third device + * identified as mouse input. + */ + if (hdev->type == HID_TYPE_USBMOUSE) { + buf = kmemdup(blackwidow_init, RAZER_BLACKWIDOW_TRANSFER_BUF_SIZE, GFP_KERNEL); + if (buf == NULL) + return -ENOMEM; + + ret = hid_hw_raw_request(hdev, 0, buf, RAZER_BLACKWIDOW_TRANSFER_BUF_SIZE, + HID_FEATURE_REPORT, HID_REQ_SET_REPORT); + if (ret != RAZER_BLACKWIDOW_TRANSFER_BUF_SIZE) + hid_err(hdev, "failed to enable macro keys: %d\n", ret); + + kfree(buf); + } + + return hid_hw_start(hdev, HID_CONNECT_DEFAULT); +} + +static const struct hid_device_id razer_devices[] = { + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER, + USB_DEVICE_ID_RAZER_BLACKWIDOW) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER, + USB_DEVICE_ID_RAZER_BLACKWIDOW_CLASSIC) }, + { HID_USB_DEVICE(USB_VENDOR_ID_RAZER, + USB_DEVICE_ID_RAZER_BLACKWIDOW_ULTIMATE) }, + { } +}; +MODULE_DEVICE_TABLE(hid, razer_devices); + +static struct hid_driver razer_driver = { + .name = "razer", + .id_table = razer_devices, + .input_mapping = razer_input_mapping, + .probe = razer_probe, +}; +module_hid_driver(razer_driver); + +MODULE_AUTHOR("Jelle van der Waa "); +MODULE_LICENSE("GPL"); From ce2645c458b5c83b0872ea9e39d2c3293445353a Mon Sep 17 00:00:00 2001 From: Srinivas Pandruvada Date: Thu, 3 Feb 2022 16:03:03 -0800 Subject: [PATCH 067/137] platform/x86/intel/uncore-freq: Move to uncore-frequency folder Move the current driver from platform/x86/intel/uncore-frequency.c to platform/x86/intel/uncore-frequency/uncore-frequency.c. No functional changes are expected. Signed-off-by: Srinivas Pandruvada Acked-by: Rafael J. Wysocki Link: https://lore.kernel.org/r/20220204000306.2517447-2-srinivas.pandruvada@linux.intel.com Signed-off-by: Hans de Goede --- MAINTAINERS | 2 +- drivers/platform/x86/intel/Kconfig | 14 ++----------- drivers/platform/x86/intel/Makefile | 4 ++-- .../x86/intel/uncore-frequency/Kconfig | 21 +++++++++++++++++++ .../x86/intel/uncore-frequency/Makefile | 7 +++++++ .../{ => uncore-frequency}/uncore-frequency.c | 0 6 files changed, 33 insertions(+), 15 deletions(-) create mode 100644 drivers/platform/x86/intel/uncore-frequency/Kconfig create mode 100644 drivers/platform/x86/intel/uncore-frequency/Makefile rename drivers/platform/x86/intel/{ => uncore-frequency}/uncore-frequency.c (100%) diff --git a/MAINTAINERS b/MAINTAINERS index c812f955556b..136f817428cf 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -9908,7 +9908,7 @@ INTEL UNCORE FREQUENCY CONTROL M: Srinivas Pandruvada L: platform-driver-x86@vger.kernel.org S: Maintained -F: drivers/platform/x86/intel/uncore-frequency.c +F: drivers/platform/x86/intel/uncore-frequency/ INTEL VENDOR SPECIFIC EXTENDED CAPABILITIES DRIVER M: David E. Box diff --git a/drivers/platform/x86/intel/Kconfig b/drivers/platform/x86/intel/Kconfig index 205ebb513b58..1f01a8a23c57 100644 --- a/drivers/platform/x86/intel/Kconfig +++ b/drivers/platform/x86/intel/Kconfig @@ -11,6 +11,8 @@ source "drivers/platform/x86/intel/pmt/Kconfig" source "drivers/platform/x86/intel/speed_select_if/Kconfig" source "drivers/platform/x86/intel/telemetry/Kconfig" source "drivers/platform/x86/intel/wmi/Kconfig" +source "drivers/platform/x86/intel/uncore-frequency/Kconfig" + config INTEL_HID_EVENT tristate "Intel HID Event" @@ -190,18 +192,6 @@ config INTEL_TURBO_MAX_3 This driver is only required when the system is not using Hardware P-States (HWP). In HWP mode, priority can be read from ACPI tables. -config INTEL_UNCORE_FREQ_CONTROL - tristate "Intel Uncore frequency control driver" - depends on X86_64 - help - This driver allows control of Uncore frequency limits on - supported server platforms. - - Uncore frequency controls RING/LLC (last-level cache) clocks. - - To compile this driver as a module, choose M here: the module - will be called intel-uncore-frequency. - config INTEL_VSEC tristate "Intel Vendor Specific Extended Capabilities Driver" depends on PCI diff --git a/drivers/platform/x86/intel/Makefile b/drivers/platform/x86/intel/Makefile index 1a5cdba4c2d2..c61bc3e97121 100644 --- a/drivers/platform/x86/intel/Makefile +++ b/drivers/platform/x86/intel/Makefile @@ -12,6 +12,8 @@ obj-$(CONFIG_INTEL_PMT_CLASS) += pmt/ obj-$(CONFIG_INTEL_SPEED_SELECT_INTERFACE) += speed_select_if/ obj-$(CONFIG_INTEL_TELEMETRY) += telemetry/ obj-$(CONFIG_INTEL_WMI) += wmi/ +obj-$(CONFIG_INTEL_UNCORE_FREQ_CONTROL) += uncore-frequency/ + # Intel input drivers intel-hid-y := hid.o @@ -51,5 +53,3 @@ intel-smartconnect-y := smartconnect.o obj-$(CONFIG_INTEL_SMARTCONNECT) += intel-smartconnect.o intel_turbo_max_3-y := turbo_max_3.o obj-$(CONFIG_INTEL_TURBO_MAX_3) += intel_turbo_max_3.o -intel-uncore-frequency-y := uncore-frequency.o -obj-$(CONFIG_INTEL_UNCORE_FREQ_CONTROL) += intel-uncore-frequency.o diff --git a/drivers/platform/x86/intel/uncore-frequency/Kconfig b/drivers/platform/x86/intel/uncore-frequency/Kconfig new file mode 100644 index 000000000000..21b209124916 --- /dev/null +++ b/drivers/platform/x86/intel/uncore-frequency/Kconfig @@ -0,0 +1,21 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Uncore Frquency control drivers +# + +menu "Intel Uncore Frequency Control" + depends on X86_64 || COMPILE_TEST + +config INTEL_UNCORE_FREQ_CONTROL + tristate "Intel Uncore frequency control driver" + depends on X86_64 + help + This driver allows control of Uncore frequency limits on + supported server platforms. + + Uncore frequency controls RING/LLC (last-level cache) clocks. + + To compile this driver as a module, choose M here: the module + will be called intel-uncore-frequency. + +endmenu diff --git a/drivers/platform/x86/intel/uncore-frequency/Makefile b/drivers/platform/x86/intel/uncore-frequency/Makefile new file mode 100644 index 000000000000..e22186a480e2 --- /dev/null +++ b/drivers/platform/x86/intel/uncore-frequency/Makefile @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for linux/drivers/platform/x86/intel/uncore-frequency +# + +obj-$(CONFIG_INTEL_UNCORE_FREQ_CONTROL) += intel-uncore-frequency.o +intel-uncore-frequency-y := uncore-frequency.o diff --git a/drivers/platform/x86/intel/uncore-frequency.c b/drivers/platform/x86/intel/uncore-frequency/uncore-frequency.c similarity index 100% rename from drivers/platform/x86/intel/uncore-frequency.c rename to drivers/platform/x86/intel/uncore-frequency/uncore-frequency.c From ae7b2ce578513adf0a77f0127e27ee4447d50443 Mon Sep 17 00:00:00 2001 From: Srinivas Pandruvada Date: Thu, 3 Feb 2022 16:03:04 -0800 Subject: [PATCH 068/137] platform/x86/intel/uncore-freq: Use sysfs API to create attributes Use of sysfs API is always preferable over using kobject calls to create attributes. Remove usage of kobject_init_and_add() and use sysfs_create_group(). To create relationship between sysfs attribute and uncore instance use device_attribute*, which is defined per uncore instance. To create uniform locking for both read and write attributes take lock in the sysfs callbacks, not in the actual functions where the MSRs are read or updated. No functional changes are expected. Signed-off-by: Srinivas Pandruvada Acked-by: Rafael J. Wysocki Link: https://lore.kernel.org/r/20220204000306.2517447-3-srinivas.pandruvada@linux.intel.com Signed-off-by: Hans de Goede --- .../intel/uncore-frequency/uncore-frequency.c | 225 +++++++++--------- 1 file changed, 113 insertions(+), 112 deletions(-) diff --git a/drivers/platform/x86/intel/uncore-frequency/uncore-frequency.c b/drivers/platform/x86/intel/uncore-frequency/uncore-frequency.c index 4cd8254f2e40..35b00608a81d 100644 --- a/drivers/platform/x86/intel/uncore-frequency/uncore-frequency.c +++ b/drivers/platform/x86/intel/uncore-frequency/uncore-frequency.c @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-2.0 /* * Intel Uncore Frequency Setting - * Copyright (c) 2019, Intel Corporation. + * Copyright (c) 2022, Intel Corporation. * All rights reserved. * * Provide interface to set MSR 620 at a granularity of per die. On CPU online, @@ -32,21 +32,36 @@ * @initial_max_freq_khz: Sampled maximum uncore frequency at driver init * @control_cpu: Designated CPU for a die to read/write * @valid: Mark the data valid/invalid + * @package_id: Package id for this instance + * @die_id: Die id for this instance + * @name: Sysfs entry name for this instance + * @uncore_attr_group: Attribute group storage + * @max_freq_khz_dev_attr: Storage for device attribute max_freq_khz + * @mix_freq_khz_dev_attr: Storage for device attribute min_freq_khz + * @initial_max_freq_khz_dev_attr: Storage for device attribute initial_max_freq_khz + * @initial_min_freq_khz_dev_attr: Storage for device attribute initial_min_freq_khz + * @uncore_attrs: Attribute storage for group creation * * This structure is used to encapsulate all data related to uncore sysfs * settings for a die/package. */ struct uncore_data { - struct kobject kobj; - struct completion kobj_unregister; u64 stored_uncore_data; u32 initial_min_freq_khz; u32 initial_max_freq_khz; int control_cpu; bool valid; -}; + int package_id; + int die_id; + char name[32]; -#define to_uncore_data(a) container_of(a, struct uncore_data, kobj) + struct attribute_group uncore_attr_group; + struct device_attribute max_freq_khz_dev_attr; + struct device_attribute min_freq_khz_dev_attr; + struct device_attribute initial_max_freq_khz_dev_attr; + struct device_attribute initial_min_freq_khz_dev_attr; + struct attribute *uncore_attrs[5]; +}; /* Max instances for uncore data, one for each die */ static int uncore_max_entries __read_mostly; @@ -61,36 +76,6 @@ static enum cpuhp_state uncore_hp_state __read_mostly; /* Mutex to control all mutual exclusions */ static DEFINE_MUTEX(uncore_lock); -struct uncore_attr { - struct attribute attr; - ssize_t (*show)(struct kobject *kobj, - struct attribute *attr, char *buf); - ssize_t (*store)(struct kobject *kobj, - struct attribute *attr, const char *c, ssize_t count); -}; - -#define define_one_uncore_ro(_name) \ -static struct uncore_attr _name = \ -__ATTR(_name, 0444, show_##_name, NULL) - -#define define_one_uncore_rw(_name) \ -static struct uncore_attr _name = \ -__ATTR(_name, 0644, show_##_name, store_##_name) - -#define show_uncore_data(member_name) \ - static ssize_t show_##member_name(struct kobject *kobj, \ - struct attribute *attr, \ - char *buf) \ - { \ - struct uncore_data *data = to_uncore_data(kobj); \ - return scnprintf(buf, PAGE_SIZE, "%u\n", \ - data->member_name); \ - } \ - define_one_uncore_ro(member_name) - -show_uncore_data(initial_min_freq_khz); -show_uncore_data(initial_max_freq_khz); - /* Common function to read MSR 0x620 and read min/max */ static int uncore_read_ratio(struct uncore_data *data, unsigned int *min, unsigned int *max) @@ -118,22 +103,16 @@ static int uncore_write_ratio(struct uncore_data *data, unsigned int input, int ret; u64 cap; - mutex_lock(&uncore_lock); - - if (data->control_cpu < 0) { - ret = -ENXIO; - goto finish_write; - } + if (data->control_cpu < 0) + return -ENXIO; input /= UNCORE_FREQ_KHZ_MULTIPLIER; - if (!input || input > 0x7F) { - ret = -EINVAL; - goto finish_write; - } + if (!input || input > 0x7F) + return -EINVAL; ret = rdmsrl_on_cpu(data->control_cpu, MSR_UNCORE_RATIO_LIMIT, &cap); if (ret) - goto finish_write; + return ret; if (set_max) { cap &= ~0x7F; @@ -145,37 +124,16 @@ static int uncore_write_ratio(struct uncore_data *data, unsigned int input, ret = wrmsrl_on_cpu(data->control_cpu, MSR_UNCORE_RATIO_LIMIT, cap); if (ret) - goto finish_write; + return ret; data->stored_uncore_data = cap; -finish_write: - mutex_unlock(&uncore_lock); - - return ret; + return 0; } -static ssize_t store_min_max_freq_khz(struct kobject *kobj, - struct attribute *attr, - const char *buf, ssize_t count, - int min_max) -{ - struct uncore_data *data = to_uncore_data(kobj); - unsigned int input; - - if (kstrtouint(buf, 10, &input)) - return -EINVAL; - - uncore_write_ratio(data, input, min_max); - - return count; -} - -static ssize_t show_min_max_freq_khz(struct kobject *kobj, - struct attribute *attr, +static ssize_t show_min_max_freq_khz(struct uncore_data *data, char *buf, int min_max) { - struct uncore_data *data = to_uncore_data(kobj); unsigned int min, max; int ret; @@ -191,22 +149,40 @@ static ssize_t show_min_max_freq_khz(struct kobject *kobj, return sprintf(buf, "%u\n", min); } +static ssize_t store_min_max_freq_khz(struct uncore_data *data, + const char *buf, ssize_t count, + int min_max) +{ + unsigned int input; + + if (kstrtouint(buf, 10, &input)) + return -EINVAL; + + mutex_lock(&uncore_lock); + uncore_write_ratio(data, input, min_max); + mutex_unlock(&uncore_lock); + + return count; +} + #define store_uncore_min_max(name, min_max) \ - static ssize_t store_##name(struct kobject *kobj, \ - struct attribute *attr, \ - const char *buf, ssize_t count) \ - { \ + static ssize_t store_##name(struct device *dev, \ + struct device_attribute *attr, \ + const char *buf, size_t count) \ + { \ + struct uncore_data *data = container_of(attr, struct uncore_data, name##_dev_attr);\ \ - return store_min_max_freq_khz(kobj, attr, buf, count, \ - min_max); \ + return store_min_max_freq_khz(data, buf, count, \ + min_max); \ } #define show_uncore_min_max(name, min_max) \ - static ssize_t show_##name(struct kobject *kobj, \ - struct attribute *attr, char *buf) \ + static ssize_t show_##name(struct device *dev, \ + struct device_attribute *attr, char *buf)\ { \ + struct uncore_data *data = container_of(attr, struct uncore_data, name##_dev_attr);\ \ - return show_min_max_freq_khz(kobj, attr, buf, min_max); \ + return show_min_max_freq_khz(data, buf, min_max); \ } store_uncore_min_max(min_freq_khz, 0); @@ -215,30 +191,64 @@ store_uncore_min_max(max_freq_khz, 1); show_uncore_min_max(min_freq_khz, 0); show_uncore_min_max(max_freq_khz, 1); -define_one_uncore_rw(min_freq_khz); -define_one_uncore_rw(max_freq_khz); +#define show_uncore_data(member_name) \ + static ssize_t show_##member_name(struct device *dev, \ + struct device_attribute *attr, char *buf)\ + { \ + struct uncore_data *data = container_of(attr, struct uncore_data,\ + member_name##_dev_attr);\ + \ + return scnprintf(buf, PAGE_SIZE, "%u\n", \ + data->member_name); \ + } \ -static struct attribute *uncore_attrs[] = { - &initial_min_freq_khz.attr, - &initial_max_freq_khz.attr, - &max_freq_khz.attr, - &min_freq_khz.attr, - NULL -}; -ATTRIBUTE_GROUPS(uncore); +show_uncore_data(initial_min_freq_khz); +show_uncore_data(initial_max_freq_khz); -static void uncore_sysfs_entry_release(struct kobject *kobj) +#define init_attribute_rw(_name) \ + do { \ + sysfs_attr_init(&data->_name##_dev_attr.attr); \ + data->_name##_dev_attr.show = show_##_name; \ + data->_name##_dev_attr.store = store_##_name; \ + data->_name##_dev_attr.attr.name = #_name; \ + data->_name##_dev_attr.attr.mode = 0644; \ + } while (0) + +#define init_attribute_ro(_name) \ + do { \ + sysfs_attr_init(&data->_name##_dev_attr.attr); \ + data->_name##_dev_attr.show = show_##_name; \ + data->_name##_dev_attr.store = NULL; \ + data->_name##_dev_attr.attr.name = #_name; \ + data->_name##_dev_attr.attr.mode = 0444; \ + } while (0) + +static int create_attr_group(struct uncore_data *data, char *name) { - struct uncore_data *data = to_uncore_data(kobj); + int ret, index = 0; - complete(&data->kobj_unregister); + init_attribute_rw(max_freq_khz); + init_attribute_rw(min_freq_khz); + init_attribute_ro(initial_min_freq_khz); + init_attribute_ro(initial_max_freq_khz); + + data->uncore_attrs[index++] = &data->max_freq_khz_dev_attr.attr; + data->uncore_attrs[index++] = &data->min_freq_khz_dev_attr.attr; + data->uncore_attrs[index++] = &data->initial_min_freq_khz_dev_attr.attr; + data->uncore_attrs[index++] = &data->initial_max_freq_khz_dev_attr.attr; + data->uncore_attrs[index] = NULL; + + data->uncore_attr_group.name = name; + data->uncore_attr_group.attrs = data->uncore_attrs; + ret = sysfs_create_group(uncore_root_kobj, &data->uncore_attr_group); + + return ret; } -static struct kobj_type uncore_ktype = { - .release = uncore_sysfs_entry_release, - .sysfs_ops = &kobj_sysfs_ops, - .default_groups = uncore_groups, -}; +static void delete_attr_group(struct uncore_data *data, char *name) +{ + sysfs_remove_group(uncore_root_kobj, &data->uncore_attr_group); +} /* Caller provides protection */ static struct uncore_data *uncore_get_instance(unsigned int cpu) @@ -266,21 +276,17 @@ static void uncore_add_die_entry(int cpu) /* control cpu changed */ data->control_cpu = cpu; } else { - char str[64]; int ret; memset(data, 0, sizeof(*data)); - sprintf(str, "package_%02d_die_%02d", + sprintf(data->name, "package_%02d_die_%02d", topology_physical_package_id(cpu), topology_die_id(cpu)); uncore_read_ratio(data, &data->initial_min_freq_khz, &data->initial_max_freq_khz); - init_completion(&data->kobj_unregister); - - ret = kobject_init_and_add(&data->kobj, &uncore_ktype, - uncore_root_kobj, str); + ret = create_attr_group(data, data->name); if (!ret) { data->control_cpu = cpu; data->valid = true; @@ -296,8 +302,11 @@ static void uncore_remove_die_entry(int cpu) mutex_lock(&uncore_lock); data = uncore_get_instance(cpu); - if (data) + if (data) { + delete_attr_group(data, data->name); data->control_cpu = -1; + data->valid = false; + } mutex_unlock(&uncore_lock); } @@ -433,16 +442,8 @@ module_init(intel_uncore_init) static void __exit intel_uncore_exit(void) { - int i; - unregister_pm_notifier(&uncore_pm_nb); cpuhp_remove_state(uncore_hp_state); - for (i = 0; i < uncore_max_entries; ++i) { - if (uncore_instances[i].valid) { - kobject_put(&uncore_instances[i].kobj); - wait_for_completion(&uncore_instances[i].kobj_unregister); - } - } kobject_put(uncore_root_kobj); kfree(uncore_instances); } From 414eef27283a2133a0998beb8e3e35f84eaac961 Mon Sep 17 00:00:00 2001 From: Srinivas Pandruvada Date: Thu, 3 Feb 2022 16:03:05 -0800 Subject: [PATCH 069/137] platform/x86/intel/uncore-freq: Display uncore current frequency Add a new sysfs attribute "current_freq_khz" to display current uncore frequency. This value is read from MSR 0x621. Root user permission is required to read uncore current frequency. Signed-off-by: Srinivas Pandruvada Acked-by: Rafael J. Wysocki Link: https://lore.kernel.org/r/20220204000306.2517447-4-srinivas.pandruvada@linux.intel.com Signed-off-by: Hans de Goede --- .../intel/uncore-frequency/uncore-frequency.c | 71 ++++++++++++++++--- 1 file changed, 62 insertions(+), 9 deletions(-) diff --git a/drivers/platform/x86/intel/uncore-frequency/uncore-frequency.c b/drivers/platform/x86/intel/uncore-frequency/uncore-frequency.c index 35b00608a81d..f5e980163911 100644 --- a/drivers/platform/x86/intel/uncore-frequency/uncore-frequency.c +++ b/drivers/platform/x86/intel/uncore-frequency/uncore-frequency.c @@ -22,6 +22,7 @@ #include #define MSR_UNCORE_RATIO_LIMIT 0x620 +#define MSR_UNCORE_PERF_STATUS 0x621 #define UNCORE_FREQ_KHZ_MULTIPLIER 100000 /** @@ -40,6 +41,7 @@ * @mix_freq_khz_dev_attr: Storage for device attribute min_freq_khz * @initial_max_freq_khz_dev_attr: Storage for device attribute initial_max_freq_khz * @initial_min_freq_khz_dev_attr: Storage for device attribute initial_min_freq_khz + * @current_freq_khz_dev_attr: Storage for device attribute current_freq_khz * @uncore_attrs: Attribute storage for group creation * * This structure is used to encapsulate all data related to uncore sysfs @@ -60,7 +62,8 @@ struct uncore_data { struct device_attribute min_freq_khz_dev_attr; struct device_attribute initial_max_freq_khz_dev_attr; struct device_attribute initial_min_freq_khz_dev_attr; - struct attribute *uncore_attrs[5]; + struct device_attribute current_freq_khz_dev_attr; + struct attribute *uncore_attrs[6]; }; /* Max instances for uncore data, one for each die */ @@ -131,22 +134,32 @@ static int uncore_write_ratio(struct uncore_data *data, unsigned int input, return 0; } -static ssize_t show_min_max_freq_khz(struct uncore_data *data, - char *buf, int min_max) +static int uncore_read_freq(struct uncore_data *data, unsigned int *freq) { - unsigned int min, max; + u64 ratio; + int ret; + + ret = rdmsrl_on_cpu(data->control_cpu, MSR_UNCORE_PERF_STATUS, &ratio); + if (ret) + return ret; + + *freq = (ratio & 0x7F) * UNCORE_FREQ_KHZ_MULTIPLIER; + + return 0; +} + +static ssize_t show_perf_status_freq_khz(struct uncore_data *data, char *buf) +{ + unsigned int freq; int ret; mutex_lock(&uncore_lock); - ret = uncore_read_ratio(data, &min, &max); + ret = uncore_read_freq(data, &freq); mutex_unlock(&uncore_lock); if (ret) return ret; - if (min_max) - return sprintf(buf, "%u\n", max); - - return sprintf(buf, "%u\n", min); + return sprintf(buf, "%u\n", freq); } static ssize_t store_min_max_freq_khz(struct uncore_data *data, @@ -165,6 +178,24 @@ static ssize_t store_min_max_freq_khz(struct uncore_data *data, return count; } +static ssize_t show_min_max_freq_khz(struct uncore_data *data, + char *buf, int min_max) +{ + unsigned int min, max; + int ret; + + mutex_lock(&uncore_lock); + ret = uncore_read_ratio(data, &min, &max); + mutex_unlock(&uncore_lock); + if (ret) + return ret; + + if (min_max) + return sprintf(buf, "%u\n", max); + + return sprintf(buf, "%u\n", min); +} + #define store_uncore_min_max(name, min_max) \ static ssize_t store_##name(struct device *dev, \ struct device_attribute *attr, \ @@ -185,12 +216,23 @@ static ssize_t store_min_max_freq_khz(struct uncore_data *data, return show_min_max_freq_khz(data, buf, min_max); \ } +#define show_uncore_perf_status(name) \ + static ssize_t show_##name(struct device *dev, \ + struct device_attribute *attr, char *buf)\ + { \ + struct uncore_data *data = container_of(attr, struct uncore_data, name##_dev_attr);\ + \ + return show_perf_status_freq_khz(data, buf); \ + } + store_uncore_min_max(min_freq_khz, 0); store_uncore_min_max(max_freq_khz, 1); show_uncore_min_max(min_freq_khz, 0); show_uncore_min_max(max_freq_khz, 1); +show_uncore_perf_status(current_freq_khz); + #define show_uncore_data(member_name) \ static ssize_t show_##member_name(struct device *dev, \ struct device_attribute *attr, char *buf)\ @@ -223,6 +265,15 @@ show_uncore_data(initial_max_freq_khz); data->_name##_dev_attr.attr.mode = 0444; \ } while (0) +#define init_attribute_root_ro(_name) \ + do { \ + sysfs_attr_init(&data->_name##_dev_attr.attr); \ + data->_name##_dev_attr.show = show_##_name; \ + data->_name##_dev_attr.store = NULL; \ + data->_name##_dev_attr.attr.name = #_name; \ + data->_name##_dev_attr.attr.mode = 0400; \ + } while (0) + static int create_attr_group(struct uncore_data *data, char *name) { int ret, index = 0; @@ -231,11 +282,13 @@ static int create_attr_group(struct uncore_data *data, char *name) init_attribute_rw(min_freq_khz); init_attribute_ro(initial_min_freq_khz); init_attribute_ro(initial_max_freq_khz); + init_attribute_root_ro(current_freq_khz); data->uncore_attrs[index++] = &data->max_freq_khz_dev_attr.attr; data->uncore_attrs[index++] = &data->min_freq_khz_dev_attr.attr; data->uncore_attrs[index++] = &data->initial_min_freq_khz_dev_attr.attr; data->uncore_attrs[index++] = &data->initial_max_freq_khz_dev_attr.attr; + data->uncore_attrs[index++] = &data->current_freq_khz_dev_attr.attr; data->uncore_attrs[index] = NULL; data->uncore_attr_group.name = name; From dbce412a7733bb7a763d99db413da22da72e3736 Mon Sep 17 00:00:00 2001 From: Srinivas Pandruvada Date: Thu, 3 Feb 2022 16:03:06 -0800 Subject: [PATCH 070/137] platform/x86/intel-uncore-freq: Split common and enumeration part Split the current driver in two parts: - Common part: All the commom function other than enumeration function. - Enumeration/HW specific part: The current enumeration using CPU model is left in the old module. This uses service of common driver to register sysfs objects. Also provide callbacks for MSR access related to uncore. - Add MODULE_DEVICE_TABLE to uncore-frequency.c No functional changes are expected. Signed-off-by: Srinivas Pandruvada Acked-by: Rafael J. Wysocki Link: https://lore.kernel.org/r/20220204000306.2517447-5-srinivas.pandruvada@linux.intel.com Signed-off-by: Hans de Goede --- .../x86/intel/uncore-frequency/Makefile | 2 + .../uncore-frequency-common.c | 252 +++++++++++++ .../uncore-frequency-common.h | 62 ++++ .../intel/uncore-frequency/uncore-frequency.c | 332 +++--------------- 4 files changed, 365 insertions(+), 283 deletions(-) create mode 100644 drivers/platform/x86/intel/uncore-frequency/uncore-frequency-common.c create mode 100644 drivers/platform/x86/intel/uncore-frequency/uncore-frequency-common.h diff --git a/drivers/platform/x86/intel/uncore-frequency/Makefile b/drivers/platform/x86/intel/uncore-frequency/Makefile index e22186a480e2..e0f7968e8285 100644 --- a/drivers/platform/x86/intel/uncore-frequency/Makefile +++ b/drivers/platform/x86/intel/uncore-frequency/Makefile @@ -5,3 +5,5 @@ obj-$(CONFIG_INTEL_UNCORE_FREQ_CONTROL) += intel-uncore-frequency.o intel-uncore-frequency-y := uncore-frequency.o +obj-$(CONFIG_INTEL_UNCORE_FREQ_CONTROL) += intel-uncore-frequency-common.o +intel-uncore-frequency-common-y := uncore-frequency-common.o diff --git a/drivers/platform/x86/intel/uncore-frequency/uncore-frequency-common.c b/drivers/platform/x86/intel/uncore-frequency/uncore-frequency-common.c new file mode 100644 index 000000000000..e4d5a7960234 --- /dev/null +++ b/drivers/platform/x86/intel/uncore-frequency/uncore-frequency-common.c @@ -0,0 +1,252 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Intel Uncore Frequency Control: Common code implementation + * Copyright (c) 2022, Intel Corporation. + * All rights reserved. + * + */ +#include +#include +#include "uncore-frequency-common.h" + +/* Mutex to control all mutual exclusions */ +static DEFINE_MUTEX(uncore_lock); +/* Root of the all uncore sysfs kobjs */ +static struct kobject *uncore_root_kobj; +/* uncore instance count */ +static int uncore_instance_count; + +/* callbacks for actual HW read/write */ +static int (*uncore_read)(struct uncore_data *data, unsigned int *min, unsigned int *max); +static int (*uncore_write)(struct uncore_data *data, unsigned int input, unsigned int min_max); +static int (*uncore_read_freq)(struct uncore_data *data, unsigned int *freq); + +static ssize_t show_min_max_freq_khz(struct uncore_data *data, + char *buf, int min_max) +{ + unsigned int min, max; + int ret; + + mutex_lock(&uncore_lock); + ret = uncore_read(data, &min, &max); + mutex_unlock(&uncore_lock); + if (ret) + return ret; + + if (min_max) + return sprintf(buf, "%u\n", max); + + return sprintf(buf, "%u\n", min); +} + +static ssize_t store_min_max_freq_khz(struct uncore_data *data, + const char *buf, ssize_t count, + int min_max) +{ + unsigned int input; + + if (kstrtouint(buf, 10, &input)) + return -EINVAL; + + mutex_lock(&uncore_lock); + uncore_write(data, input, min_max); + mutex_unlock(&uncore_lock); + + return count; +} + +static ssize_t show_perf_status_freq_khz(struct uncore_data *data, char *buf) +{ + unsigned int freq; + int ret; + + mutex_lock(&uncore_lock); + ret = uncore_read_freq(data, &freq); + mutex_unlock(&uncore_lock); + if (ret) + return ret; + + return sprintf(buf, "%u\n", freq); +} + +#define store_uncore_min_max(name, min_max) \ + static ssize_t store_##name(struct device *dev, \ + struct device_attribute *attr, \ + const char *buf, size_t count) \ + { \ + struct uncore_data *data = container_of(attr, struct uncore_data, name##_dev_attr);\ + \ + return store_min_max_freq_khz(data, buf, count, \ + min_max); \ + } + +#define show_uncore_min_max(name, min_max) \ + static ssize_t show_##name(struct device *dev, \ + struct device_attribute *attr, char *buf)\ + { \ + struct uncore_data *data = container_of(attr, struct uncore_data, name##_dev_attr);\ + \ + return show_min_max_freq_khz(data, buf, min_max); \ + } + +#define show_uncore_perf_status(name) \ + static ssize_t show_##name(struct device *dev, \ + struct device_attribute *attr, char *buf)\ + { \ + struct uncore_data *data = container_of(attr, struct uncore_data, name##_dev_attr);\ + \ + return show_perf_status_freq_khz(data, buf); \ + } + +store_uncore_min_max(min_freq_khz, 0); +store_uncore_min_max(max_freq_khz, 1); + +show_uncore_min_max(min_freq_khz, 0); +show_uncore_min_max(max_freq_khz, 1); + +show_uncore_perf_status(current_freq_khz); + +#define show_uncore_data(member_name) \ + static ssize_t show_##member_name(struct device *dev, \ + struct device_attribute *attr, char *buf)\ + { \ + struct uncore_data *data = container_of(attr, struct uncore_data,\ + member_name##_dev_attr);\ + \ + return scnprintf(buf, PAGE_SIZE, "%u\n", \ + data->member_name); \ + } \ + +show_uncore_data(initial_min_freq_khz); +show_uncore_data(initial_max_freq_khz); + +#define init_attribute_rw(_name) \ + do { \ + sysfs_attr_init(&data->_name##_dev_attr.attr); \ + data->_name##_dev_attr.show = show_##_name; \ + data->_name##_dev_attr.store = store_##_name; \ + data->_name##_dev_attr.attr.name = #_name; \ + data->_name##_dev_attr.attr.mode = 0644; \ + } while (0) + +#define init_attribute_ro(_name) \ + do { \ + sysfs_attr_init(&data->_name##_dev_attr.attr); \ + data->_name##_dev_attr.show = show_##_name; \ + data->_name##_dev_attr.store = NULL; \ + data->_name##_dev_attr.attr.name = #_name; \ + data->_name##_dev_attr.attr.mode = 0444; \ + } while (0) + +#define init_attribute_root_ro(_name) \ + do { \ + sysfs_attr_init(&data->_name##_dev_attr.attr); \ + data->_name##_dev_attr.show = show_##_name; \ + data->_name##_dev_attr.store = NULL; \ + data->_name##_dev_attr.attr.name = #_name; \ + data->_name##_dev_attr.attr.mode = 0400; \ + } while (0) + +static int create_attr_group(struct uncore_data *data, char *name) +{ + int ret, index = 0; + + init_attribute_rw(max_freq_khz); + init_attribute_rw(min_freq_khz); + init_attribute_ro(initial_min_freq_khz); + init_attribute_ro(initial_max_freq_khz); + init_attribute_root_ro(current_freq_khz); + + data->uncore_attrs[index++] = &data->max_freq_khz_dev_attr.attr; + data->uncore_attrs[index++] = &data->min_freq_khz_dev_attr.attr; + data->uncore_attrs[index++] = &data->initial_min_freq_khz_dev_attr.attr; + data->uncore_attrs[index++] = &data->initial_max_freq_khz_dev_attr.attr; + data->uncore_attrs[index++] = &data->current_freq_khz_dev_attr.attr; + data->uncore_attrs[index] = NULL; + + data->uncore_attr_group.name = name; + data->uncore_attr_group.attrs = data->uncore_attrs; + ret = sysfs_create_group(uncore_root_kobj, &data->uncore_attr_group); + + return ret; +} + +static void delete_attr_group(struct uncore_data *data, char *name) +{ + sysfs_remove_group(uncore_root_kobj, &data->uncore_attr_group); +} + +int uncore_freq_add_entry(struct uncore_data *data, int cpu) +{ + int ret = 0; + + mutex_lock(&uncore_lock); + if (data->valid) { + /* control cpu changed */ + data->control_cpu = cpu; + goto uncore_unlock; + } + + sprintf(data->name, "package_%02d_die_%02d", data->package_id, data->die_id); + + uncore_read(data, &data->initial_min_freq_khz, &data->initial_max_freq_khz); + + ret = create_attr_group(data, data->name); + if (!ret) { + data->control_cpu = cpu; + data->valid = true; + } + +uncore_unlock: + mutex_unlock(&uncore_lock); + + return ret; +} +EXPORT_SYMBOL_NS_GPL(uncore_freq_add_entry, INTEL_UNCORE_FREQUENCY); + +void uncore_freq_remove_die_entry(struct uncore_data *data) +{ + mutex_lock(&uncore_lock); + delete_attr_group(data, data->name); + data->control_cpu = -1; + data->valid = false; + mutex_unlock(&uncore_lock); +} +EXPORT_SYMBOL_NS_GPL(uncore_freq_remove_die_entry, INTEL_UNCORE_FREQUENCY); + +int uncore_freq_common_init(int (*read_control_freq)(struct uncore_data *data, unsigned int *min, unsigned int *max), + int (*write_control_freq)(struct uncore_data *data, unsigned int input, unsigned int set_max), + int (*read_freq)(struct uncore_data *data, unsigned int *freq)) +{ + mutex_lock(&uncore_lock); + + uncore_read = read_control_freq; + uncore_write = write_control_freq; + uncore_read_freq = read_freq; + + if (!uncore_root_kobj) + uncore_root_kobj = kobject_create_and_add("intel_uncore_frequency", + &cpu_subsys.dev_root->kobj); + if (uncore_root_kobj) + ++uncore_instance_count; + mutex_unlock(&uncore_lock); + + return (!!uncore_root_kobj); +} +EXPORT_SYMBOL_NS_GPL(uncore_freq_common_init, INTEL_UNCORE_FREQUENCY); + +void uncore_freq_common_exit(void) +{ + mutex_lock(&uncore_lock); + --uncore_instance_count; + if (!uncore_instance_count) { + kobject_put(uncore_root_kobj); + uncore_root_kobj = NULL; + } + mutex_unlock(&uncore_lock); +} +EXPORT_SYMBOL_NS_GPL(uncore_freq_common_exit, INTEL_UNCORE_FREQUENCY); + + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Intel Uncore Frequency Common Module"); diff --git a/drivers/platform/x86/intel/uncore-frequency/uncore-frequency-common.h b/drivers/platform/x86/intel/uncore-frequency/uncore-frequency-common.h new file mode 100644 index 000000000000..f5dcfa2fb285 --- /dev/null +++ b/drivers/platform/x86/intel/uncore-frequency/uncore-frequency-common.h @@ -0,0 +1,62 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Intel Uncore Frequency Control: Common defines and prototypes + * Copyright (c) 2022, Intel Corporation. + * All rights reserved. + * + */ + +#ifndef __INTEL_UNCORE_FREQ_COMMON_H +#define __INTEL_UNCORE_FREQ_COMMON_H + +#include + +/** + * struct uncore_data - Encapsulate all uncore data + * @stored_uncore_data: Last user changed MSR 620 value, which will be restored + * on system resume. + * @initial_min_freq_khz: Sampled minimum uncore frequency at driver init + * @initial_max_freq_khz: Sampled maximum uncore frequency at driver init + * @control_cpu: Designated CPU for a die to read/write + * @valid: Mark the data valid/invalid + * @package_id: Package id for this instance + * @die_id: Die id for this instance + * @name: Sysfs entry name for this instance + * @uncore_attr_group: Attribute group storage + * @max_freq_khz_dev_attr: Storage for device attribute max_freq_khz + * @mix_freq_khz_dev_attr: Storage for device attribute min_freq_khz + * @initial_max_freq_khz_dev_attr: Storage for device attribute initial_max_freq_khz + * @initial_min_freq_khz_dev_attr: Storage for device attribute initial_min_freq_khz + * @current_freq_khz_dev_attr: Storage for device attribute current_freq_khz + * @uncore_attrs: Attribute storage for group creation + * + * This structure is used to encapsulate all data related to uncore sysfs + * settings for a die/package. + */ +struct uncore_data { + u64 stored_uncore_data; + u32 initial_min_freq_khz; + u32 initial_max_freq_khz; + int control_cpu; + bool valid; + int package_id; + int die_id; + char name[32]; + + struct attribute_group uncore_attr_group; + struct device_attribute max_freq_khz_dev_attr; + struct device_attribute min_freq_khz_dev_attr; + struct device_attribute initial_max_freq_khz_dev_attr; + struct device_attribute initial_min_freq_khz_dev_attr; + struct device_attribute current_freq_khz_dev_attr; + struct attribute *uncore_attrs[6]; +}; + +int uncore_freq_common_init(int (*read_control_freq)(struct uncore_data *data, unsigned int *min, unsigned int *max), + int (*write_control_freq)(struct uncore_data *data, unsigned int input, unsigned int min_max), + int (*uncore_read_freq)(struct uncore_data *data, unsigned int *freq)); +void uncore_freq_common_exit(void); +int uncore_freq_add_entry(struct uncore_data *data, int cpu); +void uncore_freq_remove_die_entry(struct uncore_data *data); + +#endif diff --git a/drivers/platform/x86/intel/uncore-frequency/uncore-frequency.c b/drivers/platform/x86/intel/uncore-frequency/uncore-frequency.c index f5e980163911..791af0e287e4 100644 --- a/drivers/platform/x86/intel/uncore-frequency/uncore-frequency.c +++ b/drivers/platform/x86/intel/uncore-frequency/uncore-frequency.c @@ -21,67 +21,23 @@ #include #include -#define MSR_UNCORE_RATIO_LIMIT 0x620 -#define MSR_UNCORE_PERF_STATUS 0x621 -#define UNCORE_FREQ_KHZ_MULTIPLIER 100000 - -/** - * struct uncore_data - Encapsulate all uncore data - * @stored_uncore_data: Last user changed MSR 620 value, which will be restored - * on system resume. - * @initial_min_freq_khz: Sampled minimum uncore frequency at driver init - * @initial_max_freq_khz: Sampled maximum uncore frequency at driver init - * @control_cpu: Designated CPU for a die to read/write - * @valid: Mark the data valid/invalid - * @package_id: Package id for this instance - * @die_id: Die id for this instance - * @name: Sysfs entry name for this instance - * @uncore_attr_group: Attribute group storage - * @max_freq_khz_dev_attr: Storage for device attribute max_freq_khz - * @mix_freq_khz_dev_attr: Storage for device attribute min_freq_khz - * @initial_max_freq_khz_dev_attr: Storage for device attribute initial_max_freq_khz - * @initial_min_freq_khz_dev_attr: Storage for device attribute initial_min_freq_khz - * @current_freq_khz_dev_attr: Storage for device attribute current_freq_khz - * @uncore_attrs: Attribute storage for group creation - * - * This structure is used to encapsulate all data related to uncore sysfs - * settings for a die/package. - */ -struct uncore_data { - u64 stored_uncore_data; - u32 initial_min_freq_khz; - u32 initial_max_freq_khz; - int control_cpu; - bool valid; - int package_id; - int die_id; - char name[32]; - - struct attribute_group uncore_attr_group; - struct device_attribute max_freq_khz_dev_attr; - struct device_attribute min_freq_khz_dev_attr; - struct device_attribute initial_max_freq_khz_dev_attr; - struct device_attribute initial_min_freq_khz_dev_attr; - struct device_attribute current_freq_khz_dev_attr; - struct attribute *uncore_attrs[6]; -}; +#include "uncore-frequency-common.h" /* Max instances for uncore data, one for each die */ static int uncore_max_entries __read_mostly; /* Storage for uncore data for all instances */ static struct uncore_data *uncore_instances; -/* Root of the all uncore sysfs kobjs */ -static struct kobject *uncore_root_kobj; /* Stores the CPU mask of the target CPUs to use during uncore read/write */ static cpumask_t uncore_cpu_mask; /* CPU online callback register instance */ static enum cpuhp_state uncore_hp_state __read_mostly; -/* Mutex to control all mutual exclusions */ -static DEFINE_MUTEX(uncore_lock); -/* Common function to read MSR 0x620 and read min/max */ -static int uncore_read_ratio(struct uncore_data *data, unsigned int *min, - unsigned int *max) +#define MSR_UNCORE_RATIO_LIMIT 0x620 +#define MSR_UNCORE_PERF_STATUS 0x621 +#define UNCORE_FREQ_KHZ_MULTIPLIER 100000 + +static int uncore_read_control_freq(struct uncore_data *data, unsigned int *min, + unsigned int *max) { u64 cap; int ret; @@ -99,25 +55,24 @@ static int uncore_read_ratio(struct uncore_data *data, unsigned int *min, return 0; } -/* Common function to set min/max ratios to be used by sysfs callbacks */ -static int uncore_write_ratio(struct uncore_data *data, unsigned int input, - int set_max) +static int uncore_write_control_freq(struct uncore_data *data, unsigned int input, + unsigned int min_max) { int ret; u64 cap; - if (data->control_cpu < 0) - return -ENXIO; - input /= UNCORE_FREQ_KHZ_MULTIPLIER; if (!input || input > 0x7F) return -EINVAL; + if (data->control_cpu < 0) + return -ENXIO; + ret = rdmsrl_on_cpu(data->control_cpu, MSR_UNCORE_RATIO_LIMIT, &cap); if (ret) return ret; - if (set_max) { + if (min_max) { cap &= ~0x7F; cap |= input; } else { @@ -139,6 +94,9 @@ static int uncore_read_freq(struct uncore_data *data, unsigned int *freq) u64 ratio; int ret; + if (data->control_cpu < 0) + return -ENXIO; + ret = rdmsrl_on_cpu(data->control_cpu, MSR_UNCORE_PERF_STATUS, &ratio); if (ret) return ret; @@ -148,161 +106,6 @@ static int uncore_read_freq(struct uncore_data *data, unsigned int *freq) return 0; } -static ssize_t show_perf_status_freq_khz(struct uncore_data *data, char *buf) -{ - unsigned int freq; - int ret; - - mutex_lock(&uncore_lock); - ret = uncore_read_freq(data, &freq); - mutex_unlock(&uncore_lock); - if (ret) - return ret; - - return sprintf(buf, "%u\n", freq); -} - -static ssize_t store_min_max_freq_khz(struct uncore_data *data, - const char *buf, ssize_t count, - int min_max) -{ - unsigned int input; - - if (kstrtouint(buf, 10, &input)) - return -EINVAL; - - mutex_lock(&uncore_lock); - uncore_write_ratio(data, input, min_max); - mutex_unlock(&uncore_lock); - - return count; -} - -static ssize_t show_min_max_freq_khz(struct uncore_data *data, - char *buf, int min_max) -{ - unsigned int min, max; - int ret; - - mutex_lock(&uncore_lock); - ret = uncore_read_ratio(data, &min, &max); - mutex_unlock(&uncore_lock); - if (ret) - return ret; - - if (min_max) - return sprintf(buf, "%u\n", max); - - return sprintf(buf, "%u\n", min); -} - -#define store_uncore_min_max(name, min_max) \ - static ssize_t store_##name(struct device *dev, \ - struct device_attribute *attr, \ - const char *buf, size_t count) \ - { \ - struct uncore_data *data = container_of(attr, struct uncore_data, name##_dev_attr);\ - \ - return store_min_max_freq_khz(data, buf, count, \ - min_max); \ - } - -#define show_uncore_min_max(name, min_max) \ - static ssize_t show_##name(struct device *dev, \ - struct device_attribute *attr, char *buf)\ - { \ - struct uncore_data *data = container_of(attr, struct uncore_data, name##_dev_attr);\ - \ - return show_min_max_freq_khz(data, buf, min_max); \ - } - -#define show_uncore_perf_status(name) \ - static ssize_t show_##name(struct device *dev, \ - struct device_attribute *attr, char *buf)\ - { \ - struct uncore_data *data = container_of(attr, struct uncore_data, name##_dev_attr);\ - \ - return show_perf_status_freq_khz(data, buf); \ - } - -store_uncore_min_max(min_freq_khz, 0); -store_uncore_min_max(max_freq_khz, 1); - -show_uncore_min_max(min_freq_khz, 0); -show_uncore_min_max(max_freq_khz, 1); - -show_uncore_perf_status(current_freq_khz); - -#define show_uncore_data(member_name) \ - static ssize_t show_##member_name(struct device *dev, \ - struct device_attribute *attr, char *buf)\ - { \ - struct uncore_data *data = container_of(attr, struct uncore_data,\ - member_name##_dev_attr);\ - \ - return scnprintf(buf, PAGE_SIZE, "%u\n", \ - data->member_name); \ - } \ - -show_uncore_data(initial_min_freq_khz); -show_uncore_data(initial_max_freq_khz); - -#define init_attribute_rw(_name) \ - do { \ - sysfs_attr_init(&data->_name##_dev_attr.attr); \ - data->_name##_dev_attr.show = show_##_name; \ - data->_name##_dev_attr.store = store_##_name; \ - data->_name##_dev_attr.attr.name = #_name; \ - data->_name##_dev_attr.attr.mode = 0644; \ - } while (0) - -#define init_attribute_ro(_name) \ - do { \ - sysfs_attr_init(&data->_name##_dev_attr.attr); \ - data->_name##_dev_attr.show = show_##_name; \ - data->_name##_dev_attr.store = NULL; \ - data->_name##_dev_attr.attr.name = #_name; \ - data->_name##_dev_attr.attr.mode = 0444; \ - } while (0) - -#define init_attribute_root_ro(_name) \ - do { \ - sysfs_attr_init(&data->_name##_dev_attr.attr); \ - data->_name##_dev_attr.show = show_##_name; \ - data->_name##_dev_attr.store = NULL; \ - data->_name##_dev_attr.attr.name = #_name; \ - data->_name##_dev_attr.attr.mode = 0400; \ - } while (0) - -static int create_attr_group(struct uncore_data *data, char *name) -{ - int ret, index = 0; - - init_attribute_rw(max_freq_khz); - init_attribute_rw(min_freq_khz); - init_attribute_ro(initial_min_freq_khz); - init_attribute_ro(initial_max_freq_khz); - init_attribute_root_ro(current_freq_khz); - - data->uncore_attrs[index++] = &data->max_freq_khz_dev_attr.attr; - data->uncore_attrs[index++] = &data->min_freq_khz_dev_attr.attr; - data->uncore_attrs[index++] = &data->initial_min_freq_khz_dev_attr.attr; - data->uncore_attrs[index++] = &data->initial_max_freq_khz_dev_attr.attr; - data->uncore_attrs[index++] = &data->current_freq_khz_dev_attr.attr; - data->uncore_attrs[index] = NULL; - - data->uncore_attr_group.name = name; - data->uncore_attr_group.attrs = data->uncore_attrs; - ret = sysfs_create_group(uncore_root_kobj, &data->uncore_attr_group); - - return ret; -} - -static void delete_attr_group(struct uncore_data *data, char *name) -{ - sysfs_remove_group(uncore_root_kobj, &data->uncore_attr_group); -} - /* Caller provides protection */ static struct uncore_data *uncore_get_instance(unsigned int cpu) { @@ -314,57 +117,9 @@ static struct uncore_data *uncore_get_instance(unsigned int cpu) return NULL; } -static void uncore_add_die_entry(int cpu) -{ - struct uncore_data *data; - - mutex_lock(&uncore_lock); - data = uncore_get_instance(cpu); - if (!data) { - mutex_unlock(&uncore_lock); - return; - } - - if (data->valid) { - /* control cpu changed */ - data->control_cpu = cpu; - } else { - int ret; - - memset(data, 0, sizeof(*data)); - sprintf(data->name, "package_%02d_die_%02d", - topology_physical_package_id(cpu), - topology_die_id(cpu)); - - uncore_read_ratio(data, &data->initial_min_freq_khz, - &data->initial_max_freq_khz); - - ret = create_attr_group(data, data->name); - if (!ret) { - data->control_cpu = cpu; - data->valid = true; - } - } - mutex_unlock(&uncore_lock); -} - -/* Last CPU in this die is offline, make control cpu invalid */ -static void uncore_remove_die_entry(int cpu) -{ - struct uncore_data *data; - - mutex_lock(&uncore_lock); - data = uncore_get_instance(cpu); - if (data) { - delete_attr_group(data, data->name); - data->control_cpu = -1; - data->valid = false; - } - mutex_unlock(&uncore_lock); -} - static int uncore_event_cpu_online(unsigned int cpu) { + struct uncore_data *data; int target; /* Check if there is an online cpu in the package for uncore MSR */ @@ -374,15 +129,26 @@ static int uncore_event_cpu_online(unsigned int cpu) /* Use this CPU on this die as a control CPU */ cpumask_set_cpu(cpu, &uncore_cpu_mask); - uncore_add_die_entry(cpu); - return 0; + data = uncore_get_instance(cpu); + if (!data) + return 0; + + data->package_id = topology_physical_package_id(cpu); + data->die_id = topology_die_id(cpu); + + return uncore_freq_add_entry(data, cpu); } static int uncore_event_cpu_offline(unsigned int cpu) { + struct uncore_data *data; int target; + data = uncore_get_instance(cpu); + if (!data) + return 0; + /* Check if existing cpu is used for uncore MSRs */ if (!cpumask_test_and_clear_cpu(cpu, &uncore_cpu_mask)) return 0; @@ -392,9 +158,9 @@ static int uncore_event_cpu_offline(unsigned int cpu) if (target < nr_cpu_ids) { cpumask_set_cpu(target, &uncore_cpu_mask); - uncore_add_die_entry(target); + uncore_freq_add_entry(data, target); } else { - uncore_remove_die_entry(cpu); + uncore_freq_remove_die_entry(data); } return 0; @@ -403,24 +169,20 @@ static int uncore_event_cpu_offline(unsigned int cpu) static int uncore_pm_notify(struct notifier_block *nb, unsigned long mode, void *_unused) { - int cpu; + int i; switch (mode) { case PM_POST_HIBERNATION: case PM_POST_RESTORE: case PM_POST_SUSPEND: - for_each_cpu(cpu, &uncore_cpu_mask) { - struct uncore_data *data; - int ret; + for (i = 0; i < uncore_max_entries; ++i) { + struct uncore_data *data = &uncore_instances[i]; - data = uncore_get_instance(cpu); if (!data || !data->valid || !data->stored_uncore_data) - continue; + return 0; - ret = wrmsrl_on_cpu(cpu, MSR_UNCORE_RATIO_LIMIT, - data->stored_uncore_data); - if (ret) - return ret; + wrmsrl_on_cpu(data->control_cpu, MSR_UNCORE_RATIO_LIMIT, + data->stored_uncore_data); } break; default: @@ -443,6 +205,7 @@ static const struct x86_cpu_id intel_uncore_cpu_ids[] = { X86_MATCH_INTEL_FAM6_MODEL(SAPPHIRERAPIDS_X, NULL), {} }; +MODULE_DEVICE_TABLE(x86cpu, intel_uncore_cpu_ids); static int __init intel_uncore_init(void) { @@ -460,12 +223,10 @@ static int __init intel_uncore_init(void) if (!uncore_instances) return -ENOMEM; - uncore_root_kobj = kobject_create_and_add("intel_uncore_frequency", - &cpu_subsys.dev_root->kobj); - if (!uncore_root_kobj) { - ret = -ENOMEM; + ret = uncore_freq_common_init(uncore_read_control_freq, uncore_write_control_freq, + uncore_read_freq); + if (!ret) goto err_free; - } ret = cpuhp_setup_state(CPUHP_AP_ONLINE_DYN, "platform/x86/uncore-freq:online", @@ -485,7 +246,7 @@ static int __init intel_uncore_init(void) err_rem_state: cpuhp_remove_state(uncore_hp_state); err_rem_kobj: - kobject_put(uncore_root_kobj); + uncore_freq_common_exit(); err_free: kfree(uncore_instances); @@ -495,12 +256,17 @@ module_init(intel_uncore_init) static void __exit intel_uncore_exit(void) { + int i; + unregister_pm_notifier(&uncore_pm_nb); cpuhp_remove_state(uncore_hp_state); - kobject_put(uncore_root_kobj); + for (i = 0; i < uncore_max_entries; ++i) + uncore_freq_remove_die_entry(&uncore_instances[i]); + uncore_freq_common_exit(); kfree(uncore_instances); } module_exit(intel_uncore_exit) +MODULE_IMPORT_NS(INTEL_UNCORE_FREQUENCY); MODULE_LICENSE("GPL v2"); MODULE_DESCRIPTION("Intel Uncore Frequency Limits Driver"); From ed9f4f961260de51ed76d903e86b35ee8ce8eeb3 Mon Sep 17 00:00:00 2001 From: Jiri Kosina Date: Thu, 17 Feb 2022 08:17:02 +0100 Subject: [PATCH 071/137] HID: apple: properly reflect LEDS dependency Since hid-apple driver now makes use of LEDS functionality, reflect this properly in Kconfig. Reported-by: kernel test robot Fixes: 9018eacbe623b ("HID: apple: Add support for keyboard backlight on certain T2 Macs.") Signed-off-by: Jiri Kosina --- drivers/hid/Kconfig | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig index f5544157576c..9c0e45f56d84 100644 --- a/drivers/hid/Kconfig +++ b/drivers/hid/Kconfig @@ -128,6 +128,8 @@ config HID_ACRUX_FF config HID_APPLE tristate "Apple {i,Power,Mac}Books" depends on HID + depends on LEDS_CLASS + depends on NEW_LEDS default !EXPERT help Support for some Apple devices which less or more break From 3d7d407dfb05b257e15cb0c6b056428a4a8c2e5d Mon Sep 17 00:00:00 2001 From: Sanket Goswami Date: Fri, 4 Feb 2022 17:55:27 +0530 Subject: [PATCH 072/137] platform/x86: amd-pmc: Add support for AMD Spill to DRAM STB feature Spill to DRAM functionality is a feature that allows STB (Smart Trace Buffer) to spill data from SRAM into DRAM on some future AMD ASICs. The size allocated for STB is more than the earlier SoC's which helps to collect more tracing and telemetry data. Co-developed-by: Shyam Sundar S K Signed-off-by: Shyam Sundar S K Signed-off-by: Sanket Goswami Reviewed-by: Mario Limonciello Link: https://lore.kernel.org/r/20220204122527.3873552-1-Sanket.Goswami@amd.com Reviewed-by: Hans de Goede Signed-off-by: Hans de Goede --- drivers/platform/x86/amd-pmc.c | 144 +++++++++++++++++++++++++++++---- 1 file changed, 130 insertions(+), 14 deletions(-) diff --git a/drivers/platform/x86/amd-pmc.c b/drivers/platform/x86/amd-pmc.c index 4c72ba68b315..69fdbb0d9f45 100644 --- a/drivers/platform/x86/amd-pmc.c +++ b/drivers/platform/x86/amd-pmc.c @@ -41,6 +41,16 @@ #define AMD_PMC_STB_PMI_0 0x03E30600 #define AMD_PMC_STB_PREDEF 0xC6000001 +/* STB S2D(Spill to DRAM) has different message port offset */ +#define STB_SPILL_TO_DRAM 0xBE +#define AMD_S2D_REGISTER_MESSAGE 0xA20 +#define AMD_S2D_REGISTER_RESPONSE 0xA80 +#define AMD_S2D_REGISTER_ARGUMENT 0xA88 + +/* STB Spill to DRAM Parameters */ +#define S2D_TELEMETRY_BYTES_MAX 0x100000 +#define S2D_TELEMETRY_DRAMBYTES_MAX 0x1000000 + /* Base address of SMU for mapping physical address to virtual address */ #define AMD_PMC_SMU_INDEX_ADDRESS 0xB8 #define AMD_PMC_SMU_INDEX_DATA 0xBC @@ -95,6 +105,12 @@ enum amd_pmc_def { MSG_OS_HINT_RN, }; +enum s2d_arg { + S2D_TELEMETRY_SIZE = 0x01, + S2D_PHYS_ADDR_LOW, + S2D_PHYS_ADDR_HIGH, +}; + struct amd_pmc_bit_map { const char *name; u32 bit_mask; @@ -119,7 +135,9 @@ static const struct amd_pmc_bit_map soc15_ip_blk[] = { struct amd_pmc_dev { void __iomem *regbase; void __iomem *smu_virt_addr; + void __iomem *stb_virt_addr; void __iomem *fch_virt_addr; + bool msg_port; u32 base_addr; u32 cpu_id; u32 active_ips; @@ -236,6 +254,44 @@ static const struct file_operations amd_pmc_stb_debugfs_fops = { .release = amd_pmc_stb_debugfs_release, }; +static int amd_pmc_stb_debugfs_open_v2(struct inode *inode, struct file *filp) +{ + struct amd_pmc_dev *dev = filp->f_inode->i_private; + u32 *buf; + + buf = kzalloc(S2D_TELEMETRY_BYTES_MAX, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + memcpy_fromio(buf, dev->stb_virt_addr, S2D_TELEMETRY_BYTES_MAX); + filp->private_data = buf; + + return 0; +} + +static ssize_t amd_pmc_stb_debugfs_read_v2(struct file *filp, char __user *buf, size_t size, + loff_t *pos) +{ + if (!filp->private_data) + return -EINVAL; + + return simple_read_from_buffer(buf, size, pos, filp->private_data, + S2D_TELEMETRY_BYTES_MAX); +} + +static int amd_pmc_stb_debugfs_release_v2(struct inode *inode, struct file *filp) +{ + kfree(filp->private_data); + return 0; +} + +static const struct file_operations amd_pmc_stb_debugfs_fops_v2 = { + .owner = THIS_MODULE, + .open = amd_pmc_stb_debugfs_open_v2, + .read = amd_pmc_stb_debugfs_read_v2, + .release = amd_pmc_stb_debugfs_release_v2, +}; + static int amd_pmc_idlemask_read(struct amd_pmc_dev *pdev, struct device *dev, struct seq_file *s) { @@ -350,9 +406,14 @@ static void amd_pmc_dbgfs_register(struct amd_pmc_dev *dev) debugfs_create_file("amd_pmc_idlemask", 0644, dev->dbgfs_dir, dev, &amd_pmc_idlemask_fops); /* Enable STB only when the module_param is set */ - if (enable_stb) - debugfs_create_file("stb_read", 0644, dev->dbgfs_dir, dev, - &amd_pmc_stb_debugfs_fops); + if (enable_stb) { + if (dev->cpu_id == AMD_CPU_ID_YC) + debugfs_create_file("stb_read", 0644, dev->dbgfs_dir, dev, + &amd_pmc_stb_debugfs_fops_v2); + else + debugfs_create_file("stb_read", 0644, dev->dbgfs_dir, dev, + &amd_pmc_stb_debugfs_fops); + } } #else static inline void amd_pmc_dbgfs_register(struct amd_pmc_dev *dev) @@ -392,26 +453,47 @@ static int amd_pmc_setup_smu_logging(struct amd_pmc_dev *dev) static void amd_pmc_dump_registers(struct amd_pmc_dev *dev) { - u32 value; + u32 value, message, argument, response; - value = amd_pmc_reg_read(dev, AMD_PMC_REGISTER_RESPONSE); + if (dev->msg_port) { + message = AMD_S2D_REGISTER_MESSAGE; + argument = AMD_S2D_REGISTER_ARGUMENT; + response = AMD_S2D_REGISTER_RESPONSE; + } else { + message = AMD_PMC_REGISTER_MESSAGE; + argument = AMD_PMC_REGISTER_ARGUMENT; + response = AMD_PMC_REGISTER_RESPONSE; + } + + value = amd_pmc_reg_read(dev, response); dev_dbg(dev->dev, "AMD_PMC_REGISTER_RESPONSE:%x\n", value); - value = amd_pmc_reg_read(dev, AMD_PMC_REGISTER_ARGUMENT); + value = amd_pmc_reg_read(dev, argument); dev_dbg(dev->dev, "AMD_PMC_REGISTER_ARGUMENT:%x\n", value); - value = amd_pmc_reg_read(dev, AMD_PMC_REGISTER_MESSAGE); + value = amd_pmc_reg_read(dev, message); dev_dbg(dev->dev, "AMD_PMC_REGISTER_MESSAGE:%x\n", value); } static int amd_pmc_send_cmd(struct amd_pmc_dev *dev, u32 arg, u32 *data, u8 msg, bool ret) { int rc; - u32 val; + u32 val, message, argument, response; mutex_lock(&dev->lock); + + if (dev->msg_port) { + message = AMD_S2D_REGISTER_MESSAGE; + argument = AMD_S2D_REGISTER_ARGUMENT; + response = AMD_S2D_REGISTER_RESPONSE; + } else { + message = AMD_PMC_REGISTER_MESSAGE; + argument = AMD_PMC_REGISTER_ARGUMENT; + response = AMD_PMC_REGISTER_RESPONSE; + } + /* Wait until we get a valid response */ - rc = readx_poll_timeout(ioread32, dev->regbase + AMD_PMC_REGISTER_RESPONSE, + rc = readx_poll_timeout(ioread32, dev->regbase + response, val, val != 0, PMC_MSG_DELAY_MIN_US, PMC_MSG_DELAY_MIN_US * RESPONSE_REGISTER_LOOP_MAX); if (rc) { @@ -420,16 +502,16 @@ static int amd_pmc_send_cmd(struct amd_pmc_dev *dev, u32 arg, u32 *data, u8 msg, } /* Write zero to response register */ - amd_pmc_reg_write(dev, AMD_PMC_REGISTER_RESPONSE, 0); + amd_pmc_reg_write(dev, response, 0); /* Write argument into response register */ - amd_pmc_reg_write(dev, AMD_PMC_REGISTER_ARGUMENT, arg); + amd_pmc_reg_write(dev, argument, arg); /* Write message ID to message ID register */ - amd_pmc_reg_write(dev, AMD_PMC_REGISTER_MESSAGE, msg); + amd_pmc_reg_write(dev, message, msg); /* Wait until we get a valid response */ - rc = readx_poll_timeout(ioread32, dev->regbase + AMD_PMC_REGISTER_RESPONSE, + rc = readx_poll_timeout(ioread32, dev->regbase + response, val, val != 0, PMC_MSG_DELAY_MIN_US, PMC_MSG_DELAY_MIN_US * RESPONSE_REGISTER_LOOP_MAX); if (rc) { @@ -442,7 +524,7 @@ static int amd_pmc_send_cmd(struct amd_pmc_dev *dev, u32 arg, u32 *data, u8 msg, if (ret) { /* PMFW may take longer time to return back the data */ usleep_range(DELAY_MIN_US, 10 * DELAY_MAX_US); - *data = amd_pmc_reg_read(dev, AMD_PMC_REGISTER_ARGUMENT); + *data = amd_pmc_reg_read(dev, argument); } break; case AMD_PMC_RESULT_CMD_REJECT_BUSY: @@ -601,6 +683,34 @@ static const struct pci_device_id pmc_pci_ids[] = { { } }; +static int amd_pmc_s2d_init(struct amd_pmc_dev *dev) +{ + u32 size, phys_addr_low, phys_addr_hi; + u64 stb_phys_addr; + + /* Spill to DRAM feature uses separate SMU message port */ + dev->msg_port = 1; + + amd_pmc_send_cmd(dev, S2D_TELEMETRY_SIZE, &size, STB_SPILL_TO_DRAM, 1); + if (size != S2D_TELEMETRY_BYTES_MAX) + return -EIO; + + /* Get STB DRAM address */ + amd_pmc_send_cmd(dev, S2D_PHYS_ADDR_LOW, &phys_addr_low, STB_SPILL_TO_DRAM, 1); + amd_pmc_send_cmd(dev, S2D_PHYS_ADDR_HIGH, &phys_addr_hi, STB_SPILL_TO_DRAM, 1); + + stb_phys_addr = ((u64)phys_addr_hi << 32 | phys_addr_low); + + /* Clear msg_port for other SMU operation */ + dev->msg_port = 0; + + dev->stb_virt_addr = devm_ioremap(dev->dev, stb_phys_addr, S2D_TELEMETRY_DRAMBYTES_MAX); + if (!dev->stb_virt_addr) + return -ENOMEM; + + return 0; +} + static int amd_pmc_write_stb(struct amd_pmc_dev *dev, u32 data) { int err; @@ -719,6 +829,12 @@ static int amd_pmc_probe(struct platform_device *pdev) if (err) dev_err(dev->dev, "SMU debugging info not supported on this platform\n"); + if (enable_stb && dev->cpu_id == AMD_CPU_ID_YC) { + err = amd_pmc_s2d_init(dev); + if (err) + return err; + } + amd_pmc_get_smu_version(dev); platform_set_drvdata(pdev, dev); amd_pmc_dbgfs_register(dev); From 42f8bcb37e2c65931799cdf61d086ed78456e798 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Jo=C5=84czyk?= Date: Sat, 12 Feb 2022 13:59:08 +0100 Subject: [PATCH 073/137] platform/dcdbas: move EXPORT_SYMBOL after function MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The declaration EXPORT_SYMBOL(dcdbas_smi_request); was placed after smi_request_store(), which made a false impression that dcdbas_smi_request() was not exported. Signed-off-by: Mateusz Jończyk Cc: Stuart Hayes Cc: Hans de Goede Cc: Mark Gross Link: https://lore.kernel.org/r/20220212125908.357588-1-mat.jonczyk@o2.pl Signed-off-by: Hans de Goede --- drivers/platform/x86/dell/dcdbas.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/platform/x86/dell/dcdbas.c b/drivers/platform/x86/dell/dcdbas.c index 5e63d6225048..db3633fafbd5 100644 --- a/drivers/platform/x86/dell/dcdbas.c +++ b/drivers/platform/x86/dell/dcdbas.c @@ -284,6 +284,7 @@ int dcdbas_smi_request(struct smi_cmd *smi_cmd) return ret; } +EXPORT_SYMBOL(dcdbas_smi_request); /** * smi_request_store: @@ -351,7 +352,6 @@ static ssize_t smi_request_store(struct device *dev, mutex_unlock(&smi_data_lock); return ret; } -EXPORT_SYMBOL(dcdbas_smi_request); /** * host_control_smi: generate host control SMI From 8ca97812c3c830573f965a07bbd84223e8c5f5bd Mon Sep 17 00:00:00 2001 From: Jue Wang Date: Thu, 17 Feb 2022 17:32:09 -0800 Subject: [PATCH 074/137] x86/mce: Work around an erratum on fast string copy instructions A rare kernel panic scenario can happen when the following conditions are met due to an erratum on fast string copy instructions: 1) An uncorrected error. 2) That error must be in first cache line of a page. 3) Kernel must execute page_copy from the page immediately before that page. The fast string copy instructions ("REP; MOVS*") could consume an uncorrectable memory error in the cache line _right after_ the desired region to copy and raise an MCE. Bit 0 of MSR_IA32_MISC_ENABLE can be cleared to disable fast string copy and will avoid such spurious machine checks. However, that is less preferable due to the permanent performance impact. Considering memory poison is rare, it's desirable to keep fast string copy enabled until an MCE is seen. Intel has confirmed the following: 1. The CPU erratum of fast string copy only applies to Skylake, Cascade Lake and Cooper Lake generations. Directly return from the MCE handler: 2. Will result in complete execution of the "REP; MOVS*" with no data loss or corruption. 3. Will not result in another MCE firing on the next poisoned cache line due to "REP; MOVS*". 4. Will resume execution from a correct point in code. 5. Will result in the same instruction that triggered the MCE firing a second MCE immediately for any other software recoverable data fetch errors. 6. Is not safe without disabling the fast string copy, as the next fast string copy of the same buffer on the same CPU would result in a PANIC MCE. This should mitigate the erratum completely with the only caveat that the fast string copy is disabled on the affected hyper thread thus performance degradation. This is still better than the OS crashing on MCEs raised on an irrelevant process due to "REP; MOVS*' accesses in a kernel context, e.g., copy_page. Tested: Injected errors on 1st cache line of 8 anonymous pages of process 'proc1' and observed MCE consumption from 'proc2' with no panic (directly returned). Without the fix, the host panicked within a few minutes on a random 'proc2' process due to kernel access from copy_page. [ bp: Fix comment style + touch ups, zap an unlikely(), improve the quirk function's readability. ] Signed-off-by: Jue Wang Signed-off-by: Borislav Petkov Reviewed-by: Tony Luck Link: https://lore.kernel.org/r/20220218013209.2436006-1-juew@google.com --- arch/x86/kernel/cpu/mce/core.c | 64 ++++++++++++++++++++++++++++++ arch/x86/kernel/cpu/mce/internal.h | 5 ++- 2 files changed, 68 insertions(+), 1 deletion(-) diff --git a/arch/x86/kernel/cpu/mce/core.c b/arch/x86/kernel/cpu/mce/core.c index 0e7147430ec0..3d766e6247bc 100644 --- a/arch/x86/kernel/cpu/mce/core.c +++ b/arch/x86/kernel/cpu/mce/core.c @@ -814,6 +814,59 @@ quirk_sandybridge_ifu(int bank, struct mce *m, struct pt_regs *regs) m->cs = regs->cs; } +/* + * Disable fast string copy and return from the MCE handler upon the first SRAR + * MCE on bank 1 due to a CPU erratum on Intel Skylake/Cascade Lake/Cooper Lake + * CPUs. + * The fast string copy instructions ("REP; MOVS*") could consume an + * uncorrectable memory error in the cache line _right after_ the desired region + * to copy and raise an MCE with RIP pointing to the instruction _after_ the + * "REP; MOVS*". + * This mitigation addresses the issue completely with the caveat of performance + * degradation on the CPU affected. This is still better than the OS crashing on + * MCEs raised on an irrelevant process due to "REP; MOVS*" accesses from a + * kernel context (e.g., copy_page). + * + * Returns true when fast string copy on CPU has been disabled. + */ +static noinstr bool quirk_skylake_repmov(void) +{ + u64 mcgstatus = mce_rdmsrl(MSR_IA32_MCG_STATUS); + u64 misc_enable = mce_rdmsrl(MSR_IA32_MISC_ENABLE); + u64 mc1_status; + + /* + * Apply the quirk only to local machine checks, i.e., no broadcast + * sync is needed. + */ + if (!(mcgstatus & MCG_STATUS_LMCES) || + !(misc_enable & MSR_IA32_MISC_ENABLE_FAST_STRING)) + return false; + + mc1_status = mce_rdmsrl(MSR_IA32_MCx_STATUS(1)); + + /* Check for a software-recoverable data fetch error. */ + if ((mc1_status & + (MCI_STATUS_VAL | MCI_STATUS_OVER | MCI_STATUS_UC | MCI_STATUS_EN | + MCI_STATUS_ADDRV | MCI_STATUS_MISCV | MCI_STATUS_PCC | + MCI_STATUS_AR | MCI_STATUS_S)) == + (MCI_STATUS_VAL | MCI_STATUS_UC | MCI_STATUS_EN | + MCI_STATUS_ADDRV | MCI_STATUS_MISCV | + MCI_STATUS_AR | MCI_STATUS_S)) { + misc_enable &= ~MSR_IA32_MISC_ENABLE_FAST_STRING; + mce_wrmsrl(MSR_IA32_MISC_ENABLE, misc_enable); + mce_wrmsrl(MSR_IA32_MCx_STATUS(1), 0); + + instrumentation_begin(); + pr_err_once("Erratum detected, disable fast string copy instructions.\n"); + instrumentation_end(); + + return true; + } + + return false; +} + /* * Do a quick check if any of the events requires a panic. * This decides if we keep the events around or clear them. @@ -1383,6 +1436,9 @@ noinstr void do_machine_check(struct pt_regs *regs) else if (unlikely(!mca_cfg.initialized)) return unexpected_machine_check(regs); + if (mce_flags.skx_repmov_quirk && quirk_skylake_repmov()) + goto clear; + /* * Establish sequential order between the CPUs entering the machine * check handler. @@ -1525,6 +1581,7 @@ noinstr void do_machine_check(struct pt_regs *regs) out: instrumentation_end(); +clear: mce_wrmsrl(MSR_IA32_MCG_STATUS, 0); } EXPORT_SYMBOL_GPL(do_machine_check); @@ -1838,6 +1895,13 @@ static int __mcheck_cpu_apply_quirks(struct cpuinfo_x86 *c) if (c->x86 == 6 && c->x86_model == 45) mce_flags.snb_ifu_quirk = 1; + + /* + * Skylake, Cascacde Lake and Cooper Lake require a quirk on + * rep movs. + */ + if (c->x86 == 6 && c->x86_model == INTEL_FAM6_SKYLAKE_X) + mce_flags.skx_repmov_quirk = 1; } if (c->x86_vendor == X86_VENDOR_ZHAOXIN) { diff --git a/arch/x86/kernel/cpu/mce/internal.h b/arch/x86/kernel/cpu/mce/internal.h index a04b61e27827..3efb5037c364 100644 --- a/arch/x86/kernel/cpu/mce/internal.h +++ b/arch/x86/kernel/cpu/mce/internal.h @@ -170,7 +170,10 @@ struct mce_vendor_flags { /* SandyBridge IFU quirk */ snb_ifu_quirk : 1, - __reserved_0 : 57; + /* Skylake, Cascade Lake, Cooper Lake REP;MOVS* quirk */ + skx_repmov_quirk : 1, + + __reserved_0 : 56; }; extern struct mce_vendor_flags mce_flags; From 4b1be2fe63b80fd3af591268f1698a495e7f29be Mon Sep 17 00:00:00 2001 From: Daniel Scally Date: Wed, 16 Feb 2022 22:53:02 +0000 Subject: [PATCH 075/137] platform/x86: int3472: Add terminator to gpiod_lookup_table Without the terminator, if a con_id is passed to gpio_find() that does not exist in the lookup table the function will not stop looping correctly, and eventually cause an oops. Fixes: 19d8d6e36b4b ("platform/x86: int3472: Pass tps68470_regulator_platform_data to the tps68470-regulator MFD-cell") Signed-off-by: Daniel Scally Link: https://lore.kernel.org/r/20220216225304.53911-5-djrscally@gmail.com Reviewed-by: Hans de Goede Signed-off-by: Hans de Goede --- drivers/platform/x86/intel/int3472/tps68470_board_data.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/platform/x86/intel/int3472/tps68470_board_data.c b/drivers/platform/x86/intel/int3472/tps68470_board_data.c index f93d437fd192..525f09a3b5ff 100644 --- a/drivers/platform/x86/intel/int3472/tps68470_board_data.c +++ b/drivers/platform/x86/intel/int3472/tps68470_board_data.c @@ -100,7 +100,8 @@ static struct gpiod_lookup_table surface_go_tps68470_gpios = { .dev_id = "i2c-INT347A:00", .table = { GPIO_LOOKUP("tps68470-gpio", 9, "reset", GPIO_ACTIVE_LOW), - GPIO_LOOKUP("tps68470-gpio", 7, "powerdown", GPIO_ACTIVE_LOW) + GPIO_LOOKUP("tps68470-gpio", 7, "powerdown", GPIO_ACTIVE_LOW), + { } } }; From 7f1b8e0d6360178e3527d4f14e6921c254a86035 Mon Sep 17 00:00:00 2001 From: Borislav Petkov Date: Mon, 20 Dec 2021 21:43:28 +0100 Subject: [PATCH 076/137] x86/mce: Remove the tolerance level control This is pretty much unused and not really useful. What is more, all relevant MCA hardware has recoverable machine checks support so there's no real need to tweak MCA tolerance levels in order to *maybe* extend machine lifetime. So rip it out. Signed-off-by: Borislav Petkov Link: https://lore.kernel.org/r/YcDq8PxvKtTENl/e@zn.tnic --- Documentation/ABI/removed/sysfs-mce | 37 ++++++++++++++++ Documentation/ABI/testing/sysfs-mce | 32 -------------- Documentation/vm/hwpoison.rst | 2 - Documentation/x86/x86_64/boot-options.rst | 9 +--- arch/x86/kernel/cpu/mce/core.c | 53 +++++++++-------------- arch/x86/kernel/cpu/mce/internal.h | 3 +- arch/x86/kernel/cpu/mce/severity.c | 21 ++++----- 7 files changed, 68 insertions(+), 89 deletions(-) create mode 100644 Documentation/ABI/removed/sysfs-mce diff --git a/Documentation/ABI/removed/sysfs-mce b/Documentation/ABI/removed/sysfs-mce new file mode 100644 index 000000000000..ef5dd2a80918 --- /dev/null +++ b/Documentation/ABI/removed/sysfs-mce @@ -0,0 +1,37 @@ +What: /sys/devices/system/machinecheck/machinecheckX/tolerant +Contact: Borislav Petkov +Date: Dec, 2021 +Description: + Unused and obsolete after the advent of recoverable machine + checks (see last sentence below) and those are present since + 2010 (Nehalem). + + Original description: + + The entries appear for each CPU, but they are truly shared + between all CPUs. + + Tolerance level. When a machine check exception occurs for a + non corrected machine check the kernel can take different + actions. + + Since machine check exceptions can happen any time it is + sometimes risky for the kernel to kill a process because it + defies normal kernel locking rules. The tolerance level + configures how hard the kernel tries to recover even at some + risk of deadlock. Higher tolerant values trade potentially + better uptime with the risk of a crash or even corruption + (for tolerant >= 3). + + == =========================================================== + 0 always panic on uncorrected errors, log corrected errors + 1 panic or SIGBUS on uncorrected errors, log corrected errors + 2 SIGBUS or log uncorrected errors, log corrected errors + 3 never panic or SIGBUS, log all errors (for testing only) + == =========================================================== + + Default: 1 + + Note this only makes a difference if the CPU allows recovery + from a machine check exception. Current x86 CPUs generally + do not. diff --git a/Documentation/ABI/testing/sysfs-mce b/Documentation/ABI/testing/sysfs-mce index c8cd989034b4..83172f50e27c 100644 --- a/Documentation/ABI/testing/sysfs-mce +++ b/Documentation/ABI/testing/sysfs-mce @@ -53,38 +53,6 @@ Description: (but some corrected errors might be still reported in other ways) -What: /sys/devices/system/machinecheck/machinecheckX/tolerant -Contact: Andi Kleen -Date: Feb, 2007 -Description: - The entries appear for each CPU, but they are truly shared - between all CPUs. - - Tolerance level. When a machine check exception occurs for a - non corrected machine check the kernel can take different - actions. - - Since machine check exceptions can happen any time it is - sometimes risky for the kernel to kill a process because it - defies normal kernel locking rules. The tolerance level - configures how hard the kernel tries to recover even at some - risk of deadlock. Higher tolerant values trade potentially - better uptime with the risk of a crash or even corruption - (for tolerant >= 3). - - == =========================================================== - 0 always panic on uncorrected errors, log corrected errors - 1 panic or SIGBUS on uncorrected errors, log corrected errors - 2 SIGBUS or log uncorrected errors, log corrected errors - 3 never panic or SIGBUS, log all errors (for testing only) - == =========================================================== - - Default: 1 - - Note this only makes a difference if the CPU allows recovery - from a machine check exception. Current x86 CPUs generally - do not. - What: /sys/devices/system/machinecheck/machinecheckX/trigger Contact: Andi Kleen Date: Feb, 2007 diff --git a/Documentation/vm/hwpoison.rst b/Documentation/vm/hwpoison.rst index 89b5f7a52077..c742de1769d1 100644 --- a/Documentation/vm/hwpoison.rst +++ b/Documentation/vm/hwpoison.rst @@ -60,8 +60,6 @@ There are two (actually three) modes memory failure recovery can be in: vm.memory_failure_recovery sysctl set to zero: All memory failures cause a panic. Do not attempt recovery. - (on x86 this can be also affected by the tolerant level of the - MCE subsystem) early kill (can be controlled globally and per process) diff --git a/Documentation/x86/x86_64/boot-options.rst b/Documentation/x86/x86_64/boot-options.rst index ccb7e86bf8d9..07aa0007f346 100644 --- a/Documentation/x86/x86_64/boot-options.rst +++ b/Documentation/x86/x86_64/boot-options.rst @@ -47,14 +47,7 @@ Please see Documentation/x86/x86_64/machinecheck.rst for sysfs runtime tunables. in a reboot. On Intel systems it is enabled by default. mce=nobootlog Disable boot machine check logging. - mce=tolerancelevel[,monarchtimeout] (number,number) - tolerance levels: - 0: always panic on uncorrected errors, log corrected errors - 1: panic or SIGBUS on uncorrected errors, log corrected errors - 2: SIGBUS or log uncorrected errors, log corrected errors - 3: never panic or SIGBUS, log all errors (for testing only) - Default is 1 - Can be also set using sysfs which is preferable. + mce=monarchtimeout (number) monarchtimeout: Sets the time in us to wait for other CPUs on machine checks. 0 to disable. diff --git a/arch/x86/kernel/cpu/mce/core.c b/arch/x86/kernel/cpu/mce/core.c index 3d766e6247bc..6c9b5dac38fd 100644 --- a/arch/x86/kernel/cpu/mce/core.c +++ b/arch/x86/kernel/cpu/mce/core.c @@ -86,14 +86,6 @@ struct mce_vendor_flags mce_flags __read_mostly; struct mca_config mca_cfg __read_mostly = { .bootlog = -1, - /* - * Tolerant levels: - * 0: always panic on uncorrected errors, log corrected errors - * 1: panic or SIGBUS on uncorrected errors, log corrected errors - * 2: SIGBUS or log uncorrected errors (if possible), log corr. errors - * 3: never panic or SIGBUS, log all errors (for testing only) - */ - .tolerant = 1, .monarch_timeout = -1 }; @@ -753,7 +745,7 @@ bool machine_check_poll(enum mcp_flags flags, mce_banks_t *b) goto clear_it; mce_read_aux(&m, i); - m.severity = mce_severity(&m, NULL, mca_cfg.tolerant, NULL, false); + m.severity = mce_severity(&m, NULL, NULL, false); /* * Don't get the IP here because it's unlikely to * have anything to do with the actual error location. @@ -887,7 +879,7 @@ static __always_inline int mce_no_way_out(struct mce *m, char **msg, unsigned lo quirk_sandybridge_ifu(i, m, regs); m->bank = i; - if (mce_severity(m, regs, mca_cfg.tolerant, &tmp, true) >= MCE_PANIC_SEVERITY) { + if (mce_severity(m, regs, &tmp, true) >= MCE_PANIC_SEVERITY) { mce_read_aux(m, i); *msg = tmp; return 1; @@ -935,12 +927,11 @@ static noinstr int mce_timed_out(u64 *t, const char *msg) if (!mca_cfg.monarch_timeout) goto out; if ((s64)*t < SPINUNIT) { - if (mca_cfg.tolerant <= 1) { - if (cpumask_and(&mce_missing_cpus, cpu_online_mask, &mce_missing_cpus)) - pr_emerg("CPUs not responding to MCE broadcast (may include false positives): %*pbl\n", - cpumask_pr_args(&mce_missing_cpus)); - mce_panic(msg, NULL, NULL); - } + if (cpumask_and(&mce_missing_cpus, cpu_online_mask, &mce_missing_cpus)) + pr_emerg("CPUs not responding to MCE broadcast (may include false positives): %*pbl\n", + cpumask_pr_args(&mce_missing_cpus)); + mce_panic(msg, NULL, NULL); + ret = 1; goto out; } @@ -1004,9 +995,9 @@ static void mce_reign(void) * This dumps all the mces in the log buffer and stops the * other CPUs. */ - if (m && global_worst >= MCE_PANIC_SEVERITY && mca_cfg.tolerant < 3) { + if (m && global_worst >= MCE_PANIC_SEVERITY) { /* call mce_severity() to get "msg" for panic */ - mce_severity(m, NULL, mca_cfg.tolerant, &msg, true); + mce_severity(m, NULL, &msg, true); mce_panic("Fatal machine check", m, msg); } @@ -1020,7 +1011,7 @@ static void mce_reign(void) * No machine check event found. Must be some external * source or one CPU is hung. Panic. */ - if (global_worst <= MCE_KEEP_SEVERITY && mca_cfg.tolerant < 3) + if (global_worst <= MCE_KEEP_SEVERITY) mce_panic("Fatal machine check from unknown source", NULL, NULL); /* @@ -1267,7 +1258,7 @@ __mc_scan_banks(struct mce *m, struct pt_regs *regs, struct mce *final, /* Set taint even when machine check was not enabled. */ taint++; - severity = mce_severity(m, regs, cfg->tolerant, NULL, true); + severity = mce_severity(m, regs, NULL, true); /* * When machine check was for corrected/deferred handler don't @@ -1425,7 +1416,6 @@ noinstr void do_machine_check(struct pt_regs *regs) int worst = 0, order, no_way_out, kill_current_task, lmce, taint = 0; DECLARE_BITMAP(valid_banks, MAX_NR_BANKS) = { 0 }; DECLARE_BITMAP(toclear, MAX_NR_BANKS) = { 0 }; - struct mca_config *cfg = &mca_cfg; struct mce m, *final; char *msg = NULL; @@ -1447,7 +1437,7 @@ noinstr void do_machine_check(struct pt_regs *regs) /* * If no_way_out gets set, there is no safe way to recover from this - * MCE. If mca_cfg.tolerant is cranked up, we'll try anyway. + * MCE. */ no_way_out = 0; @@ -1481,7 +1471,7 @@ noinstr void do_machine_check(struct pt_regs *regs) * severity is MCE_AR_SEVERITY we have other options. */ if (!(m.mcgstatus & MCG_STATUS_RIPV)) - kill_current_task = (cfg->tolerant == 3) ? 0 : 1; + kill_current_task = 1; /* * Check if this MCE is signaled to only this logical processor, * on Intel, Zhaoxin only. @@ -1498,7 +1488,7 @@ noinstr void do_machine_check(struct pt_regs *regs) * to see it will clear it. */ if (lmce) { - if (no_way_out && cfg->tolerant < 3) + if (no_way_out) mce_panic("Fatal local machine check", &m, msg); } else { order = mce_start(&no_way_out); @@ -1518,7 +1508,7 @@ noinstr void do_machine_check(struct pt_regs *regs) if (!no_way_out) no_way_out = worst >= MCE_PANIC_SEVERITY; - if (no_way_out && cfg->tolerant < 3) + if (no_way_out) mce_panic("Fatal machine check on current CPU", &m, msg); } } else { @@ -1530,8 +1520,8 @@ noinstr void do_machine_check(struct pt_regs *regs) * fatal error. We call "mce_severity()" again to * make sure we have the right "msg". */ - if (worst >= MCE_PANIC_SEVERITY && mca_cfg.tolerant < 3) { - mce_severity(&m, regs, cfg->tolerant, &msg, true); + if (worst >= MCE_PANIC_SEVERITY) { + mce_severity(&m, regs, &msg, true); mce_panic("Local fatal machine check!", &m, msg); } } @@ -2267,10 +2257,9 @@ static int __init mcheck_enable(char *str) cfg->bios_cmci_threshold = 1; else if (!strcmp(str, "recovery")) cfg->recovery = 1; - else if (isdigit(str[0])) { - if (get_option(&str, &cfg->tolerant) == 2) - get_option(&str, &(cfg->monarch_timeout)); - } else { + else if (isdigit(str[0])) + get_option(&str, &(cfg->monarch_timeout)); + else { pr_info("mce argument %s ignored. Please use /sys\n", str); return 0; } @@ -2520,7 +2509,6 @@ static ssize_t store_int_with_restart(struct device *s, return ret; } -static DEVICE_INT_ATTR(tolerant, 0644, mca_cfg.tolerant); static DEVICE_INT_ATTR(monarch_timeout, 0644, mca_cfg.monarch_timeout); static DEVICE_BOOL_ATTR(dont_log_ce, 0644, mca_cfg.dont_log_ce); static DEVICE_BOOL_ATTR(print_all, 0644, mca_cfg.print_all); @@ -2541,7 +2529,6 @@ static struct dev_ext_attribute dev_attr_cmci_disabled = { }; static struct device_attribute *mce_device_attrs[] = { - &dev_attr_tolerant.attr, &dev_attr_check_interval.attr, #ifdef CONFIG_X86_MCELOG_LEGACY &dev_attr_trigger, diff --git a/arch/x86/kernel/cpu/mce/internal.h b/arch/x86/kernel/cpu/mce/internal.h index 3efb5037c364..4ae0e603f7fa 100644 --- a/arch/x86/kernel/cpu/mce/internal.h +++ b/arch/x86/kernel/cpu/mce/internal.h @@ -35,7 +35,7 @@ int mce_gen_pool_add(struct mce *mce); int mce_gen_pool_init(void); struct llist_node *mce_gen_pool_prepare_records(void); -int mce_severity(struct mce *a, struct pt_regs *regs, int tolerant, char **msg, bool is_excp); +int mce_severity(struct mce *a, struct pt_regs *regs, char **msg, bool is_excp); struct dentry *mce_get_debugfs_dir(void); extern mce_banks_t mce_banks_ce_disabled; @@ -127,7 +127,6 @@ struct mca_config { bool ignore_ce; bool print_all; - int tolerant; int monarch_timeout; int panic_timeout; u32 rip_msr; diff --git a/arch/x86/kernel/cpu/mce/severity.c b/arch/x86/kernel/cpu/mce/severity.c index ca0d775346fb..1add86935349 100644 --- a/arch/x86/kernel/cpu/mce/severity.c +++ b/arch/x86/kernel/cpu/mce/severity.c @@ -330,8 +330,7 @@ static __always_inline int mce_severity_amd_smca(struct mce *m, enum context err * See AMD Error Scope Hierarchy table in a newer BKDG. For example * 49125_15h_Models_30h-3Fh_BKDG.pdf, section "RAS Features" */ -static noinstr int mce_severity_amd(struct mce *m, struct pt_regs *regs, int tolerant, - char **msg, bool is_excp) +static noinstr int mce_severity_amd(struct mce *m, struct pt_regs *regs, char **msg, bool is_excp) { enum context ctx = error_context(m, regs); @@ -383,8 +382,7 @@ static noinstr int mce_severity_amd(struct mce *m, struct pt_regs *regs, int tol return MCE_KEEP_SEVERITY; } -static noinstr int mce_severity_intel(struct mce *m, struct pt_regs *regs, - int tolerant, char **msg, bool is_excp) +static noinstr int mce_severity_intel(struct mce *m, struct pt_regs *regs, char **msg, bool is_excp) { enum exception excp = (is_excp ? EXCP_CONTEXT : NO_EXCP); enum context ctx = error_context(m, regs); @@ -412,22 +410,21 @@ static noinstr int mce_severity_intel(struct mce *m, struct pt_regs *regs, if (msg) *msg = s->msg; s->covered = 1; - if (s->sev >= MCE_UC_SEVERITY && ctx == IN_KERNEL) { - if (tolerant < 1) - return MCE_PANIC_SEVERITY; - } + + if (s->sev >= MCE_UC_SEVERITY && ctx == IN_KERNEL) + return MCE_PANIC_SEVERITY; + return s->sev; } } -int noinstr mce_severity(struct mce *m, struct pt_regs *regs, int tolerant, char **msg, - bool is_excp) +int noinstr mce_severity(struct mce *m, struct pt_regs *regs, char **msg, bool is_excp) { if (boot_cpu_data.x86_vendor == X86_VENDOR_AMD || boot_cpu_data.x86_vendor == X86_VENDOR_HYGON) - return mce_severity_amd(m, regs, tolerant, msg, is_excp); + return mce_severity_amd(m, regs, msg, is_excp); else - return mce_severity_intel(m, regs, tolerant, msg, is_excp); + return mce_severity_intel(m, regs, msg, is_excp); } #ifdef CONFIG_DEBUG_FS From 07f5ed0eee011f2b76ee01a4939f3ff1d34ac5e3 Mon Sep 17 00:00:00 2001 From: Matan Ziv-Av Date: Sat, 19 Feb 2022 15:54:46 +0200 Subject: [PATCH 077/137] lg-laptop: Move setting of battery charge limit to common location For now leave also the driver specific location, with deprecated warning in documentation. Signed-off-by: Matan Ziv-Av Link: https://lore.kernel.org/r/eca2fa354f60b8a6e5a5c9c8e244fea56616970a.1645278914.git.matan@svgalib.org Reviewed-by: Hans de Goede Signed-off-by: Hans de Goede --- .../ABI/testing/sysfs-platform-lg-laptop | 1 + .../admin-guide/laptops/lg-laptop.rst | 2 +- drivers/platform/x86/Kconfig | 1 + drivers/platform/x86/lg-laptop.c | 57 ++++++++++++++++--- 4 files changed, 52 insertions(+), 9 deletions(-) diff --git a/Documentation/ABI/testing/sysfs-platform-lg-laptop b/Documentation/ABI/testing/sysfs-platform-lg-laptop index cf47749b19df..0570cd524d0e 100644 --- a/Documentation/ABI/testing/sysfs-platform-lg-laptop +++ b/Documentation/ABI/testing/sysfs-platform-lg-laptop @@ -17,6 +17,7 @@ Date: October 2018 KernelVersion: 4.20 Contact: "Matan Ziv-Av Description: + Deprecated use /sys/class/power_supply/CMB0/charge_control_end_threshold Maximal battery charge level. Accepted values are 80 or 100. What: /sys/devices/platform/lg-laptop/fan_mode diff --git a/Documentation/admin-guide/laptops/lg-laptop.rst b/Documentation/admin-guide/laptops/lg-laptop.rst index 6fbe165dcd27..67fd6932cef4 100644 --- a/Documentation/admin-guide/laptops/lg-laptop.rst +++ b/Documentation/admin-guide/laptops/lg-laptop.rst @@ -38,7 +38,7 @@ FN lock. Battery care limit ------------------ -Writing 80/100 to /sys/devices/platform/lg-laptop/battery_care_limit +Writing 80/100 to /sys/class/power_supply/CMB0/charge_control_end_threshold sets the maximum capacity to charge the battery. Limiting the charge reduces battery capacity loss over time. diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index 8d1eec208854..09f4ab013dfa 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -915,6 +915,7 @@ config COMPAL_LAPTOP config LG_LAPTOP tristate "LG Laptop Extras" depends on ACPI + depends on ACPI_BATTERY depends on ACPI_WMI depends on INPUT select INPUT_SPARSEKMAP diff --git a/drivers/platform/x86/lg-laptop.c b/drivers/platform/x86/lg-laptop.c index a91847a551a7..332868b140ed 100644 --- a/drivers/platform/x86/lg-laptop.c +++ b/drivers/platform/x86/lg-laptop.c @@ -17,6 +17,8 @@ #include #include +#include + #define LED_DEVICE(_name, max, flag) struct led_classdev _name = { \ .name = __stringify(_name), \ .max_brightness = max, \ @@ -458,14 +460,14 @@ static ssize_t fn_lock_show(struct device *dev, return sysfs_emit(buffer, "%d\n", status); } -static ssize_t battery_care_limit_store(struct device *dev, - struct device_attribute *attr, - const char *buffer, size_t count) +static ssize_t charge_control_end_threshold_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) { unsigned long value; int ret; - ret = kstrtoul(buffer, 10, &value); + ret = kstrtoul(buf, 10, &value); if (ret) return ret; @@ -486,9 +488,9 @@ static ssize_t battery_care_limit_store(struct device *dev, return -EINVAL; } -static ssize_t battery_care_limit_show(struct device *dev, - struct device_attribute *attr, - char *buffer) +static ssize_t charge_control_end_threshold_show(struct device *device, + struct device_attribute *attr, + char *buf) { unsigned int status; union acpi_object *r; @@ -520,15 +522,52 @@ static ssize_t battery_care_limit_show(struct device *dev, if (status != 80 && status != 100) status = 0; - return sysfs_emit(buffer, "%d\n", status); + return sysfs_emit(buf, "%d\n", status); +} + +static ssize_t battery_care_limit_show(struct device *dev, + struct device_attribute *attr, + char *buffer) +{ + return charge_control_end_threshold_show(dev, attr, buffer); +} + +static ssize_t battery_care_limit_store(struct device *dev, + struct device_attribute *attr, + const char *buffer, size_t count) +{ + return charge_control_end_threshold_store(dev, attr, buffer, count); } static DEVICE_ATTR_RW(fan_mode); static DEVICE_ATTR_RW(usb_charge); static DEVICE_ATTR_RW(reader_mode); static DEVICE_ATTR_RW(fn_lock); +static DEVICE_ATTR_RW(charge_control_end_threshold); static DEVICE_ATTR_RW(battery_care_limit); +static int lg_battery_add(struct power_supply *battery) +{ + if (device_create_file(&battery->dev, + &dev_attr_charge_control_end_threshold)) + return -ENODEV; + + return 0; +} + +static int lg_battery_remove(struct power_supply *battery) +{ + device_remove_file(&battery->dev, + &dev_attr_charge_control_end_threshold); + return 0; +} + +static struct acpi_battery_hook battery_hook = { + .add_battery = lg_battery_add, + .remove_battery = lg_battery_remove, + .name = "LG Battery Extension", +}; + static struct attribute *dev_attributes[] = { &dev_attr_fan_mode.attr, &dev_attr_usb_charge.attr, @@ -711,6 +750,7 @@ static int acpi_add(struct acpi_device *device) led_classdev_register(&pf_device->dev, &tpad_led); wmi_input_setup(); + battery_hook_register(&battery_hook); return 0; @@ -728,6 +768,7 @@ static int acpi_remove(struct acpi_device *device) led_classdev_unregister(&tpad_led); led_classdev_unregister(&kbd_backlight); + battery_hook_unregister(&battery_hook); wmi_input_destroy(); platform_device_unregister(pf_device); pf_device = NULL; From 2f46d7f7e959da3ae18ed5d283935673f7a22354 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Wed, 23 Feb 2022 14:31:49 +0100 Subject: [PATCH 078/137] pinctrl: baytrail: Add pinconf group + function for the pmu_clk On the Lenovo Yoga Tablet 2 830 / 1050 / 1051 models the 32KHz PMU clk, which can be muxed externally to SUS pin 5 and/or 6 is used as a clock for the audio codec. On the 830 and 1050 models, with ship with Android as factory OS the pin-muxing for this is not setup by the BIOS. Add a pinconf group + function for the pmu_clk on SUS pin 5 and 6 to allow setting the pinmux up from within the x86-android-tablets platform code. Signed-off-by: Hans de Goede Reviewed-by: Andy Shevchenko Link: https://lore.kernel.org/r/20220223133153.730337-2-hdegoede@redhat.com --- drivers/pinctrl/intel/pinctrl-baytrail.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/drivers/pinctrl/intel/pinctrl-baytrail.c b/drivers/pinctrl/intel/pinctrl-baytrail.c index 4c01333e1406..1cc660e6458e 100644 --- a/drivers/pinctrl/intel/pinctrl-baytrail.c +++ b/drivers/pinctrl/intel/pinctrl-baytrail.c @@ -443,6 +443,9 @@ static const unsigned int byt_sus_pcu_spi_pins[] = { 21 }; static const unsigned int byt_sus_pcu_spi_mode_values[] = { 0 }; static const unsigned int byt_sus_pcu_spi_gpio_mode_values[] = { 1 }; +static const unsigned int byt_sus_pmu_clk1_pins[] = { 5 }; +static const unsigned int byt_sus_pmu_clk2_pins[] = { 6 }; + static const struct intel_pingroup byt_sus_groups[] = { PIN_GROUP("usb_oc_grp", byt_sus_usb_over_current_pins, byt_sus_usb_over_current_mode_values), PIN_GROUP("usb_ulpi_grp", byt_sus_usb_ulpi_pins, byt_sus_usb_ulpi_mode_values), @@ -450,20 +453,27 @@ static const struct intel_pingroup byt_sus_groups[] = { PIN_GROUP("usb_oc_grp_gpio", byt_sus_usb_over_current_pins, byt_sus_usb_over_current_gpio_mode_values), PIN_GROUP("usb_ulpi_grp_gpio", byt_sus_usb_ulpi_pins, byt_sus_usb_ulpi_gpio_mode_values), PIN_GROUP("pcu_spi_grp_gpio", byt_sus_pcu_spi_pins, byt_sus_pcu_spi_gpio_mode_values), + PIN_GROUP("pmu_clk1_grp", byt_sus_pmu_clk1_pins, 1), + PIN_GROUP("pmu_clk2_grp", byt_sus_pmu_clk2_pins, 1), }; static const char * const byt_sus_usb_groups[] = { "usb_oc_grp", "usb_ulpi_grp", }; static const char * const byt_sus_spi_groups[] = { "pcu_spi_grp" }; +static const char * const byt_sus_pmu_clk_groups[] = { + "pmu_clk1_grp", "pmu_clk2_grp", +}; static const char * const byt_sus_gpio_groups[] = { "usb_oc_grp_gpio", "usb_ulpi_grp_gpio", "pcu_spi_grp_gpio", + "pmu_clk1_grp", "pmu_clk2_grp", }; static const struct intel_function byt_sus_functions[] = { FUNCTION("usb", byt_sus_usb_groups), FUNCTION("spi", byt_sus_spi_groups), FUNCTION("gpio", byt_sus_gpio_groups), + FUNCTION("pmu_clk", byt_sus_pmu_clk_groups), }; static const struct intel_community byt_sus_communities[] = { From 606dadc1878f2fdeaa6e435c9c83f58a01387a7d Mon Sep 17 00:00:00 2001 From: Nikolai Kondrashov Date: Sat, 19 Feb 2022 11:01:49 +0100 Subject: [PATCH 079/137] HID: uclogic: Remove pen usage masking MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove support for pen usage masking from hid-uclogic. Disable whole interfaces instead. Most of those interfaces are useless, and if there is one which has an unused pen usage, but also has useful reports, its report descriptor should be rewritten instead. This simplifies the code and the data structures. Signed-off-by: Nikolai Kondrashov Signed-off-by: José Expósito Signed-off-by: Jiri Kosina --- drivers/hid/hid-uclogic-core.c | 23 +---------------------- drivers/hid/hid-uclogic-params.c | 29 +++++------------------------ drivers/hid/hid-uclogic-params.h | 9 +-------- 3 files changed, 7 insertions(+), 54 deletions(-) diff --git a/drivers/hid/hid-uclogic-core.c b/drivers/hid/hid-uclogic-core.c index 73d79d149869..26849f1f5459 100644 --- a/drivers/hid/hid-uclogic-core.c +++ b/drivers/hid/hid-uclogic-core.c @@ -81,24 +81,6 @@ static __u8 *uclogic_report_fixup(struct hid_device *hdev, __u8 *rdesc, return rdesc; } -static int uclogic_input_mapping(struct hid_device *hdev, - struct hid_input *hi, - struct hid_field *field, - struct hid_usage *usage, - unsigned long **bit, - int *max) -{ - struct uclogic_drvdata *drvdata = hid_get_drvdata(hdev); - struct uclogic_params *params = &drvdata->params; - - /* discard the unused pen interface */ - if (params->pen_unused && (field->application == HID_DG_PEN)) - return -1; - - /* let hid-core decide what to do */ - return 0; -} - static int uclogic_input_configured(struct hid_device *hdev, struct hid_input *hi) { @@ -374,9 +356,7 @@ static int uclogic_raw_event(struct hid_device *hdev, return 0; /* Tweak pen reports, if necessary */ - if (!params->pen_unused && - (report_id == params->pen.id) && - (size >= 2)) { + if ((report_id == params->pen.id) && (size >= 2)) { /* If it's the "virtual" frame controls report */ if (params->frame.id != 0 && data[1] & params->pen_frame_flag) { @@ -464,7 +444,6 @@ static struct hid_driver uclogic_driver = { .remove = uclogic_remove, .report_fixup = uclogic_report_fixup, .raw_event = uclogic_raw_event, - .input_mapping = uclogic_input_mapping, .input_configured = uclogic_input_configured, #ifdef CONFIG_PM .resume = uclogic_resume, diff --git a/drivers/hid/hid-uclogic-params.c b/drivers/hid/hid-uclogic-params.c index 98910d8dae92..71496735cbf0 100644 --- a/drivers/hid/hid-uclogic-params.c +++ b/drivers/hid/hid-uclogic-params.c @@ -514,8 +514,7 @@ void uclogic_params_cleanup(struct uclogic_params *params) { if (!params->invalid) { kfree(params->desc_ptr); - if (!params->pen_unused) - uclogic_params_pen_cleanup(¶ms->pen); + uclogic_params_pen_cleanup(¶ms->pen); uclogic_params_frame_cleanup(¶ms->frame); memset(params, 0, sizeof(*params)); } @@ -557,7 +556,7 @@ int uclogic_params_get_desc(const struct uclogic_params *params, size = 0; common_present = (params->desc_ptr != NULL); - pen_present = (!params->pen_unused && params->pen.desc_ptr != NULL); + pen_present = (params->pen.desc_ptr != NULL); frame_present = (params->frame.desc_ptr != NULL); if (common_present) @@ -680,21 +679,6 @@ static int uclogic_params_init_with_opt_desc(struct uclogic_params *params, return rc; } -/** - * uclogic_params_init_with_pen_unused() - initialize tablet interface - * parameters preserving original reports and generic HID processing, but - * disabling pen usage. - * - * @params: Parameters to initialize (to be cleaned with - * uclogic_params_cleanup()). Not modified in case of - * error. Cannot be NULL. - */ -static void uclogic_params_init_with_pen_unused(struct uclogic_params *params) -{ - memset(params, 0, sizeof(*params)); - params->pen_unused = true; -} - /** * uclogic_params_huion_init() - initialize a Huion tablet interface and discover * its parameters. @@ -734,8 +718,7 @@ static int uclogic_params_huion_init(struct uclogic_params *params, /* If it's not a pen interface */ if (bInterfaceNumber != 0) { - /* TODO: Consider marking the interface invalid */ - uclogic_params_init_with_pen_unused(&p); + uclogic_params_init_invalid(&p); goto output; } @@ -1033,8 +1016,7 @@ int uclogic_params_init(struct uclogic_params *params, uclogic_params_init_invalid(&p); } } else { - /* TODO: Consider marking the interface invalid */ - uclogic_params_init_with_pen_unused(&p); + uclogic_params_init_invalid(&p); } break; case VID_PID(USB_VENDOR_ID_UGEE, @@ -1056,8 +1038,7 @@ int uclogic_params_init(struct uclogic_params *params, if (rc != 0) goto cleanup; } else { - /* TODO: Consider marking the interface invalid */ - uclogic_params_init_with_pen_unused(&p); + uclogic_params_init_invalid(&p); } break; case VID_PID(USB_VENDOR_ID_TRUST, diff --git a/drivers/hid/hid-uclogic-params.h b/drivers/hid/hid-uclogic-params.h index e8381bb77bd0..48b974943bb9 100644 --- a/drivers/hid/hid-uclogic-params.h +++ b/drivers/hid/hid-uclogic-params.h @@ -138,14 +138,9 @@ struct uclogic_params { * Only valid, if "desc_ptr" is not NULL. */ unsigned int desc_size; - /* - * True, if pen usage in report descriptor is invalid, when present. - * Only valid, if "invalid" is false. - */ - bool pen_unused; /* * Pen parameters and optional report descriptor part. - * Only valid if "pen_unused" is valid and false. + * Only valid, if "invalid" is false. */ struct uclogic_params_pen pen; /* @@ -171,7 +166,6 @@ extern int uclogic_params_init(struct uclogic_params *params, ".invalid = %s\n" \ ".desc_ptr = %p\n" \ ".desc_size = %u\n" \ - ".pen_unused = %s\n" \ ".pen.desc_ptr = %p\n" \ ".pen.desc_size = %u\n" \ ".pen.id = %u\n" \ @@ -190,7 +184,6 @@ extern int uclogic_params_init(struct uclogic_params *params, ((_params)->invalid ? "true" : "false"), \ (_params)->desc_ptr, \ (_params)->desc_size, \ - ((_params)->pen_unused ? "true" : "false"), \ (_params)->pen.desc_ptr, \ (_params)->pen.desc_size, \ (_params)->pen.id, \ From 8b013098be2c91ea5e15225c8b39ace08fdd7448 Mon Sep 17 00:00:00 2001 From: Nikolai Kondrashov Date: Sat, 19 Feb 2022 11:01:50 +0100 Subject: [PATCH 080/137] HID: uclogic: Replace pen_frame_flag with subreport_list MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace a single pen_frame_flag in struct uclogic_params with subreport_list in struct uclogic_params_pen to prepare for handling more subreports in Huion HS610. Signed-off-by: Nikolai Kondrashov Signed-off-by: José Expósito Signed-off-by: Jiri Kosina --- drivers/hid/hid-uclogic-core.c | 40 ++++++++++++++------- drivers/hid/hid-uclogic-params.c | 12 ++++--- drivers/hid/hid-uclogic-params.h | 62 +++++++++++++++++++------------- 3 files changed, 73 insertions(+), 41 deletions(-) diff --git a/drivers/hid/hid-uclogic-core.c b/drivers/hid/hid-uclogic-core.c index 26849f1f5459..7092f86517a5 100644 --- a/drivers/hid/hid-uclogic-core.c +++ b/drivers/hid/hid-uclogic-core.c @@ -350,26 +350,40 @@ static int uclogic_raw_event(struct hid_device *hdev, unsigned int report_id = report->id; struct uclogic_drvdata *drvdata = hid_get_drvdata(hdev); struct uclogic_params *params = &drvdata->params; + struct uclogic_params_pen_subreport *subreport; + struct uclogic_params_pen_subreport *subreport_list_end; /* Do not handle anything but input reports */ if (report->type != HID_INPUT_REPORT) return 0; - /* Tweak pen reports, if necessary */ - if ((report_id == params->pen.id) && (size >= 2)) { - /* If it's the "virtual" frame controls report */ - if (params->frame.id != 0 && - data[1] & params->pen_frame_flag) { - /* Change to virtual frame controls report ID */ - report_id = data[0] = params->frame.id; - } else { - return uclogic_raw_event_pen(drvdata, data, size); + while (true) { + /* Tweak pen reports, if necessary */ + if ((report_id == params->pen.id) && (size >= 2)) { + subreport_list_end = + params->pen.subreport_list + + ARRAY_SIZE(params->pen.subreport_list); + /* Try to match a subreport */ + for (subreport = params->pen.subreport_list; + subreport < subreport_list_end && + (data[1] & subreport->mask) != subreport->mask; + subreport++); + /* If a subreport matched */ + if (subreport < subreport_list_end) { + /* Change to subreport ID, and restart */ + report_id = data[0] = subreport->id; + continue; + } else { + return uclogic_raw_event_pen(drvdata, data, size); + } } - } - /* Tweak frame control reports, if necessary */ - if (report_id == params->frame.id) - return uclogic_raw_event_frame(drvdata, data, size); + /* Tweak frame control reports, if necessary */ + if (report_id == params->frame.id) + return uclogic_raw_event_frame(drvdata, data, size); + + break; + } return 0; } diff --git a/drivers/hid/hid-uclogic-params.c b/drivers/hid/hid-uclogic-params.c index 71496735cbf0..138dd8b0a360 100644 --- a/drivers/hid/hid-uclogic-params.c +++ b/drivers/hid/hid-uclogic-params.c @@ -762,8 +762,10 @@ static int uclogic_params_huion_init(struct uclogic_params *params, rc); goto cleanup; } - /* Set bitmask marking frame reports in pen reports */ - p.pen_frame_flag = 0x20; + /* Link frame button subreports from pen reports */ + p.pen.subreport_list[0].mask = 0x20; + p.pen.subreport_list[0].id = + UCLOGIC_RDESC_BUTTONPAD_V2_ID; goto output; } hid_dbg(hdev, "pen v2 parameters not found\n"); @@ -788,8 +790,10 @@ static int uclogic_params_huion_init(struct uclogic_params *params, hid_dbg(hdev, "buttonpad v1 parameters%s found\n", (found ? "" : " not")); if (found) { - /* Set bitmask marking frame reports */ - p.pen_frame_flag = 0x20; + /* Link frame button subreports from pen reports */ + p.pen.subreport_list[0].mask = 0x20; + p.pen.subreport_list[0].id = + UCLOGIC_RDESC_BUTTONPAD_V1_ID; } goto output; } diff --git a/drivers/hid/hid-uclogic-params.h b/drivers/hid/hid-uclogic-params.h index 48b974943bb9..82db8ab36fec 100644 --- a/drivers/hid/hid-uclogic-params.h +++ b/drivers/hid/hid-uclogic-params.h @@ -33,6 +33,24 @@ enum uclogic_params_pen_inrange { extern const char *uclogic_params_pen_inrange_to_str( enum uclogic_params_pen_inrange inrange); + +/* + * Pen report's subreport data. + */ +struct uclogic_params_pen_subreport { + /* + * The subreport's bitmask matching the second byte of the pen report. + * If zero, the subreport is considered invalid, and won't match. + */ + __u8 mask; + + /* + * The ID to be assigned to the report, if the "mask" matches. + * Only valid if "mask" is not zero. + */ + __u8 id; +}; + /* * Tablet interface's pen input parameters. * @@ -54,6 +72,8 @@ struct uclogic_params_pen { unsigned int desc_size; /* Report ID, if reports should be tweaked, zero if not */ unsigned int id; + /* The list of subreports */ + struct uclogic_params_pen_subreport subreport_list[1]; /* Type of in-range reporting, only valid if "id" is not zero */ enum uclogic_params_pen_inrange inrange; /* @@ -148,13 +168,6 @@ struct uclogic_params { * Only valid, if "invalid" is false. */ struct uclogic_params_frame frame; - /* - * Bitmask matching frame controls "sub-report" flag in the second - * byte of the pen report, or zero if it's not expected. - * Only valid if both "pen" and "frame" are valid, and "frame.id" is - * not zero. - */ - __u8 pen_frame_flag; }; /* Initialize a tablet interface and discover its parameters */ @@ -163,21 +176,21 @@ extern int uclogic_params_init(struct uclogic_params *params, /* Tablet interface parameters *printf format string */ #define UCLOGIC_PARAMS_FMT_STR \ - ".invalid = %s\n" \ - ".desc_ptr = %p\n" \ - ".desc_size = %u\n" \ - ".pen.desc_ptr = %p\n" \ - ".pen.desc_size = %u\n" \ - ".pen.id = %u\n" \ - ".pen.inrange = %s\n" \ - ".pen.fragmented_hires = %s\n" \ - ".pen.tilt_y_flipped = %s\n" \ - ".frame.desc_ptr = %p\n" \ - ".frame.desc_size = %u\n" \ - ".frame.id = %u\n" \ - ".frame.re_lsb = %u\n" \ - ".frame.dev_id_byte = %u\n" \ - ".pen_frame_flag = 0x%02x\n" + ".invalid = %s\n" \ + ".desc_ptr = %p\n" \ + ".desc_size = %u\n" \ + ".pen.desc_ptr = %p\n" \ + ".pen.desc_size = %u\n" \ + ".pen.id = %u\n" \ + ".pen.subreport_list[0] = {0x%02hhx, %hhu}\n" \ + ".pen.inrange = %s\n" \ + ".pen.fragmented_hires = %s\n" \ + ".pen.tilt_y_flipped = %s\n" \ + ".frame.desc_ptr = %p\n" \ + ".frame.desc_size = %u\n" \ + ".frame.id = %u\n" \ + ".frame.re_lsb = %u\n" \ + ".frame.dev_id_byte = %u\n" /* Tablet interface parameters *printf format arguments */ #define UCLOGIC_PARAMS_FMT_ARGS(_params) \ @@ -187,6 +200,8 @@ extern int uclogic_params_init(struct uclogic_params *params, (_params)->pen.desc_ptr, \ (_params)->pen.desc_size, \ (_params)->pen.id, \ + (_params)->pen.subreport_list[0].mask, \ + (_params)->pen.subreport_list[0].id, \ uclogic_params_pen_inrange_to_str((_params)->pen.inrange), \ ((_params)->pen.fragmented_hires ? "true" : "false"), \ ((_params)->pen.tilt_y_flipped ? "true" : "false"), \ @@ -194,8 +209,7 @@ extern int uclogic_params_init(struct uclogic_params *params, (_params)->frame.desc_size, \ (_params)->frame.id, \ (_params)->frame.re_lsb, \ - (_params)->frame.dev_id_byte, \ - (_params)->pen_frame_flag + (_params)->frame.dev_id_byte /* Get a replacement report descriptor for a tablet's interface. */ extern int uclogic_params_get_desc(const struct uclogic_params *params, From e6be956fd7e834428a940e23442c5d934c1c8835 Mon Sep 17 00:00:00 2001 From: Nikolai Kondrashov Date: Sat, 19 Feb 2022 11:01:51 +0100 Subject: [PATCH 081/137] HID: uclogic: Switch to matching subreport bytes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Switch from matching UC-Logic subreport with a mask to a full value to support detecting Huion HS610 touch dial reports. Those would match the button mask otherwise. Signed-off-by: Nikolai Kondrashov Signed-off-by: José Expósito Signed-off-by: Jiri Kosina --- drivers/hid/hid-uclogic-core.c | 9 ++++++--- drivers/hid/hid-uclogic-params.c | 4 ++-- drivers/hid/hid-uclogic-params.h | 13 +++++++------ 3 files changed, 15 insertions(+), 11 deletions(-) diff --git a/drivers/hid/hid-uclogic-core.c b/drivers/hid/hid-uclogic-core.c index 7092f86517a5..a02edeb30a35 100644 --- a/drivers/hid/hid-uclogic-core.c +++ b/drivers/hid/hid-uclogic-core.c @@ -365,9 +365,12 @@ static int uclogic_raw_event(struct hid_device *hdev, ARRAY_SIZE(params->pen.subreport_list); /* Try to match a subreport */ for (subreport = params->pen.subreport_list; - subreport < subreport_list_end && - (data[1] & subreport->mask) != subreport->mask; - subreport++); + subreport < subreport_list_end; subreport++) { + if (subreport->value != 0 && + subreport->value == data[1]) { + break; + } + } /* If a subreport matched */ if (subreport < subreport_list_end) { /* Change to subreport ID, and restart */ diff --git a/drivers/hid/hid-uclogic-params.c b/drivers/hid/hid-uclogic-params.c index 138dd8b0a360..a4a53ab1e72c 100644 --- a/drivers/hid/hid-uclogic-params.c +++ b/drivers/hid/hid-uclogic-params.c @@ -763,7 +763,7 @@ static int uclogic_params_huion_init(struct uclogic_params *params, goto cleanup; } /* Link frame button subreports from pen reports */ - p.pen.subreport_list[0].mask = 0x20; + p.pen.subreport_list[0].value = 0xe0; p.pen.subreport_list[0].id = UCLOGIC_RDESC_BUTTONPAD_V2_ID; goto output; @@ -791,7 +791,7 @@ static int uclogic_params_huion_init(struct uclogic_params *params, (found ? "" : " not")); if (found) { /* Link frame button subreports from pen reports */ - p.pen.subreport_list[0].mask = 0x20; + p.pen.subreport_list[0].value = 0xe0; p.pen.subreport_list[0].id = UCLOGIC_RDESC_BUTTONPAD_V1_ID; } diff --git a/drivers/hid/hid-uclogic-params.h b/drivers/hid/hid-uclogic-params.h index 82db8ab36fec..c18569591b75 100644 --- a/drivers/hid/hid-uclogic-params.h +++ b/drivers/hid/hid-uclogic-params.h @@ -39,14 +39,15 @@ extern const char *uclogic_params_pen_inrange_to_str( */ struct uclogic_params_pen_subreport { /* - * The subreport's bitmask matching the second byte of the pen report. - * If zero, the subreport is considered invalid, and won't match. + * The value of the second byte of the pen report indicating this + * subreport. If zero, the subreport should be considered invalid and + * not matched. */ - __u8 mask; + __u8 value; /* - * The ID to be assigned to the report, if the "mask" matches. - * Only valid if "mask" is not zero. + * The ID to be assigned to the report, if the second byte of the pen + * report is equal to "value". Only valid if "value" is not zero. */ __u8 id; }; @@ -200,7 +201,7 @@ extern int uclogic_params_init(struct uclogic_params *params, (_params)->pen.desc_ptr, \ (_params)->pen.desc_size, \ (_params)->pen.id, \ - (_params)->pen.subreport_list[0].mask, \ + (_params)->pen.subreport_list[0].value, \ (_params)->pen.subreport_list[0].id, \ uclogic_params_pen_inrange_to_str((_params)->pen.inrange), \ ((_params)->pen.fragmented_hires ? "true" : "false"), \ From ce2401047520c917aa140c3658f8875ad0381553 Mon Sep 17 00:00:00 2001 From: Nikolai Kondrashov Date: Sat, 19 Feb 2022 11:01:52 +0100 Subject: [PATCH 082/137] HID: uclogic: Specify total report size to buttonpad macro MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Simplify the UCLOGIC_RDESC_BUTTONPAD_BYTES macro by passing as param the size of the report to pad to in bytes. Signed-off-by: Nikolai Kondrashov Signed-off-by: José Expósito Signed-off-by: Jiri Kosina --- drivers/hid/hid-uclogic-rdesc.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/hid/hid-uclogic-rdesc.c b/drivers/hid/hid-uclogic-rdesc.c index ec16355d200b..6e5bef39417e 100644 --- a/drivers/hid/hid-uclogic-rdesc.c +++ b/drivers/hid/hid-uclogic-rdesc.c @@ -654,10 +654,9 @@ const size_t uclogic_rdesc_pen_v2_template_size = /* * Expand to the contents of a generic buttonpad report descriptor. * - * @_padding: Padding from the end of button bits at bit 44, until - * the end of the report, in bits. + * @_size: Size of the report to pad to, including report ID, bytes. */ -#define UCLOGIC_RDESC_BUTTONPAD_BYTES(_padding) \ +#define UCLOGIC_RDESC_BUTTONPAD_BYTES(_size) \ 0x05, 0x01, /* Usage Page (Desktop), */ \ 0x09, 0x07, /* Usage (Keypad), */ \ 0xA1, 0x01, /* Collection (Application), */ \ @@ -692,21 +691,22 @@ const size_t uclogic_rdesc_pen_v2_template_size = 0x29, 0x03, /* Usage Maximum (03h), */ \ 0x95, 0x03, /* Report Count (3), */ \ 0x81, 0x02, /* Input (Variable), */ \ - 0x95, _padding, /* Report Count (_padding), */ \ + 0x95, ((_size) * 8 - 45), \ + /* Report Count (padding), */ \ 0x81, 0x01, /* Input (Constant), */ \ 0xC0, /* End Collection, */ \ 0xC0 /* End Collection */ /* Fixed report descriptor for (tweaked) v1 buttonpad reports */ const __u8 uclogic_rdesc_buttonpad_v1_arr[] = { - UCLOGIC_RDESC_BUTTONPAD_BYTES(19) + UCLOGIC_RDESC_BUTTONPAD_BYTES(8) }; const size_t uclogic_rdesc_buttonpad_v1_size = sizeof(uclogic_rdesc_buttonpad_v1_arr); /* Fixed report descriptor for (tweaked) v2 buttonpad reports */ const __u8 uclogic_rdesc_buttonpad_v2_arr[] = { - UCLOGIC_RDESC_BUTTONPAD_BYTES(51) + UCLOGIC_RDESC_BUTTONPAD_BYTES(12) }; const size_t uclogic_rdesc_buttonpad_v2_size = sizeof(uclogic_rdesc_buttonpad_v2_arr); From 2f290b39e6a005385584e00718e4a2d6aff1710d Mon Sep 17 00:00:00 2001 From: Nikolai Kondrashov Date: Sat, 19 Feb 2022 11:01:53 +0100 Subject: [PATCH 083/137] HID: uclogic: Use different constants for frame report IDs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Allow to set the report ID in UCLOGIC_RDESC_FRAME_BYTES instead of using a hardcoded value. Signed-off-by: Nikolai Kondrashov Signed-off-by: José Expósito Signed-off-by: Jiri Kosina --- drivers/hid/hid-uclogic-rdesc.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/drivers/hid/hid-uclogic-rdesc.c b/drivers/hid/hid-uclogic-rdesc.c index 6e5bef39417e..66aa83f67ccc 100644 --- a/drivers/hid/hid-uclogic-rdesc.c +++ b/drivers/hid/hid-uclogic-rdesc.c @@ -654,13 +654,14 @@ const size_t uclogic_rdesc_pen_v2_template_size = /* * Expand to the contents of a generic buttonpad report descriptor. * + * @_id: The report ID to use. * @_size: Size of the report to pad to, including report ID, bytes. */ -#define UCLOGIC_RDESC_BUTTONPAD_BYTES(_size) \ +#define UCLOGIC_RDESC_BUTTONPAD_BYTES(_id, _size) \ 0x05, 0x01, /* Usage Page (Desktop), */ \ 0x09, 0x07, /* Usage (Keypad), */ \ 0xA1, 0x01, /* Collection (Application), */ \ - 0x85, 0xF7, /* Report ID (247), */ \ + 0x85, (_id), /* Report ID (_id), */ \ 0x14, /* Logical Minimum (0), */ \ 0x25, 0x01, /* Logical Maximum (1), */ \ 0x75, 0x01, /* Report Size (1), */ \ @@ -699,14 +700,14 @@ const size_t uclogic_rdesc_pen_v2_template_size = /* Fixed report descriptor for (tweaked) v1 buttonpad reports */ const __u8 uclogic_rdesc_buttonpad_v1_arr[] = { - UCLOGIC_RDESC_BUTTONPAD_BYTES(8) + UCLOGIC_RDESC_BUTTONPAD_BYTES(UCLOGIC_RDESC_BUTTONPAD_V1_ID, 8) }; const size_t uclogic_rdesc_buttonpad_v1_size = sizeof(uclogic_rdesc_buttonpad_v1_arr); /* Fixed report descriptor for (tweaked) v2 buttonpad reports */ const __u8 uclogic_rdesc_buttonpad_v2_arr[] = { - UCLOGIC_RDESC_BUTTONPAD_BYTES(12) + UCLOGIC_RDESC_BUTTONPAD_BYTES(UCLOGIC_RDESC_BUTTONPAD_V2_ID, 12) }; const size_t uclogic_rdesc_buttonpad_v2_size = sizeof(uclogic_rdesc_buttonpad_v2_arr); From 2e28f3e099f24bf976624baa1fe3a6ab90702406 Mon Sep 17 00:00:00 2001 From: Nikolai Kondrashov Date: Sat, 19 Feb 2022 11:01:54 +0100 Subject: [PATCH 084/137] HID: uclogic: Use "frame" instead of "buttonpad" MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Use the term "frame" instead of "buttonpad" for consistency, in UC-Logic driver. Signed-off-by: Nikolai Kondrashov Signed-off-by: José Expósito Signed-off-by: Jiri Kosina --- drivers/hid/hid-uclogic-params.c | 46 +++++++++++++++----------------- drivers/hid/hid-uclogic-rdesc.c | 32 +++++++++++----------- drivers/hid/hid-uclogic-rdesc.h | 26 +++++++++--------- 3 files changed, 51 insertions(+), 53 deletions(-) diff --git a/drivers/hid/hid-uclogic-params.c b/drivers/hid/hid-uclogic-params.c index a4a53ab1e72c..168749d9a112 100644 --- a/drivers/hid/hid-uclogic-params.c +++ b/drivers/hid/hid-uclogic-params.c @@ -431,8 +431,8 @@ static int uclogic_params_frame_init_with_desc( } /** - * uclogic_params_frame_init_v1_buttonpad() - initialize abstract buttonpad - * on a v1 tablet interface. + * uclogic_params_frame_init_v1() - initialize v1 tablet interface frame + * controls. * * @frame: Pointer to the frame parameters to initialize (to be cleaned * up with uclogic_params_frame_cleanup()). Not modified in case @@ -446,8 +446,7 @@ static int uclogic_params_frame_init_with_desc( * Returns: * Zero, if successful. A negative errno code on error. */ -static int uclogic_params_frame_init_v1_buttonpad( - struct uclogic_params_frame *frame, +static int uclogic_params_frame_init_v1(struct uclogic_params_frame *frame, bool *pfound, struct hid_device *hdev) { @@ -488,9 +487,9 @@ static int uclogic_params_frame_init_v1_buttonpad( hid_dbg(hdev, "generic buttons enabled\n"); rc = uclogic_params_frame_init_with_desc( frame, - uclogic_rdesc_buttonpad_v1_arr, - uclogic_rdesc_buttonpad_v1_size, - UCLOGIC_RDESC_BUTTONPAD_V1_ID); + uclogic_rdesc_frame_v1_arr, + uclogic_rdesc_frame_v1_size, + UCLOGIC_RDESC_FRAME_V1_ID); if (rc != 0) goto cleanup; found = true; @@ -750,22 +749,22 @@ static int uclogic_params_huion_init(struct uclogic_params *params, goto cleanup; } else if (found) { hid_dbg(hdev, "pen v2 parameters found\n"); - /* Create v2 buttonpad parameters */ + /* Create v2 frame parameters */ rc = uclogic_params_frame_init_with_desc( &p.frame, - uclogic_rdesc_buttonpad_v2_arr, - uclogic_rdesc_buttonpad_v2_size, - UCLOGIC_RDESC_BUTTONPAD_V2_ID); + uclogic_rdesc_frame_v2_arr, + uclogic_rdesc_frame_v2_size, + UCLOGIC_RDESC_FRAME_V2_ID); if (rc != 0) { hid_err(hdev, - "failed creating v2 buttonpad parameters: %d\n", + "failed creating v2 frame parameters: %d\n", rc); goto cleanup; } /* Link frame button subreports from pen reports */ p.pen.subreport_list[0].value = 0xe0; p.pen.subreport_list[0].id = - UCLOGIC_RDESC_BUTTONPAD_V2_ID; + UCLOGIC_RDESC_FRAME_V2_ID; goto output; } hid_dbg(hdev, "pen v2 parameters not found\n"); @@ -779,21 +778,20 @@ static int uclogic_params_huion_init(struct uclogic_params *params, goto cleanup; } else if (found) { hid_dbg(hdev, "pen v1 parameters found\n"); - /* Try to probe v1 buttonpad */ - rc = uclogic_params_frame_init_v1_buttonpad( - &p.frame, - &found, hdev); + /* Try to probe v1 frame */ + rc = uclogic_params_frame_init_v1(&p.frame, + &found, hdev); if (rc != 0) { - hid_err(hdev, "v1 buttonpad probing failed: %d\n", rc); + hid_err(hdev, "v1 frame probing failed: %d\n", rc); goto cleanup; } - hid_dbg(hdev, "buttonpad v1 parameters%s found\n", + hid_dbg(hdev, "frame v1 parameters%s found\n", (found ? "" : " not")); if (found) { /* Link frame button subreports from pen reports */ p.pen.subreport_list[0].value = 0xe0; p.pen.subreport_list[0].id = - UCLOGIC_RDESC_BUTTONPAD_V1_ID; + UCLOGIC_RDESC_FRAME_V1_ID; } goto output; } @@ -1067,7 +1065,7 @@ int uclogic_params_init(struct uclogic_params *params, UCLOGIC_RDESC_UGEE_G5_FRAME_ID); if (rc != 0) { hid_err(hdev, - "failed creating buttonpad parameters: %d\n", + "failed creating frame parameters: %d\n", rc); goto cleanup; } @@ -1096,12 +1094,12 @@ int uclogic_params_init(struct uclogic_params *params, } else if (found) { rc = uclogic_params_frame_init_with_desc( &p.frame, - uclogic_rdesc_ugee_ex07_buttonpad_arr, - uclogic_rdesc_ugee_ex07_buttonpad_size, + uclogic_rdesc_ugee_ex07_frame_arr, + uclogic_rdesc_ugee_ex07_frame_size, 0); if (rc != 0) { hid_err(hdev, - "failed creating buttonpad parameters: %d\n", + "failed creating frame parameters: %d\n", rc); goto cleanup; } diff --git a/drivers/hid/hid-uclogic-rdesc.c b/drivers/hid/hid-uclogic-rdesc.c index 66aa83f67ccc..e8dedede3395 100644 --- a/drivers/hid/hid-uclogic-rdesc.c +++ b/drivers/hid/hid-uclogic-rdesc.c @@ -652,12 +652,12 @@ const size_t uclogic_rdesc_pen_v2_template_size = sizeof(uclogic_rdesc_pen_v2_template_arr); /* - * Expand to the contents of a generic buttonpad report descriptor. + * Expand to the contents of a generic frame report descriptor. * * @_id: The report ID to use. * @_size: Size of the report to pad to, including report ID, bytes. */ -#define UCLOGIC_RDESC_BUTTONPAD_BYTES(_id, _size) \ +#define UCLOGIC_RDESC_FRAME_BYTES(_id, _size) \ 0x05, 0x01, /* Usage Page (Desktop), */ \ 0x09, 0x07, /* Usage (Keypad), */ \ 0xA1, 0x01, /* Collection (Application), */ \ @@ -698,22 +698,22 @@ const size_t uclogic_rdesc_pen_v2_template_size = 0xC0, /* End Collection, */ \ 0xC0 /* End Collection */ -/* Fixed report descriptor for (tweaked) v1 buttonpad reports */ -const __u8 uclogic_rdesc_buttonpad_v1_arr[] = { - UCLOGIC_RDESC_BUTTONPAD_BYTES(UCLOGIC_RDESC_BUTTONPAD_V1_ID, 8) +/* Fixed report descriptor for (tweaked) v1 frame reports */ +const __u8 uclogic_rdesc_frame_v1_arr[] = { + UCLOGIC_RDESC_FRAME_BYTES(UCLOGIC_RDESC_FRAME_V1_ID, 8) }; -const size_t uclogic_rdesc_buttonpad_v1_size = - sizeof(uclogic_rdesc_buttonpad_v1_arr); +const size_t uclogic_rdesc_frame_v1_size = + sizeof(uclogic_rdesc_frame_v1_arr); -/* Fixed report descriptor for (tweaked) v2 buttonpad reports */ -const __u8 uclogic_rdesc_buttonpad_v2_arr[] = { - UCLOGIC_RDESC_BUTTONPAD_BYTES(UCLOGIC_RDESC_BUTTONPAD_V2_ID, 12) +/* Fixed report descriptor for (tweaked) v2 frame reports */ +const __u8 uclogic_rdesc_frame_v2_arr[] = { + UCLOGIC_RDESC_FRAME_BYTES(UCLOGIC_RDESC_FRAME_V2_ID, 12) }; -const size_t uclogic_rdesc_buttonpad_v2_size = - sizeof(uclogic_rdesc_buttonpad_v2_arr); +const size_t uclogic_rdesc_frame_v2_size = + sizeof(uclogic_rdesc_frame_v2_arr); -/* Fixed report descriptor for Ugee EX07 buttonpad */ -const __u8 uclogic_rdesc_ugee_ex07_buttonpad_arr[] = { +/* Fixed report descriptor for Ugee EX07 frame */ +const __u8 uclogic_rdesc_ugee_ex07_frame_arr[] = { 0x05, 0x01, /* Usage Page (Desktop), */ 0x09, 0x07, /* Usage (Keypad), */ 0xA1, 0x01, /* Collection (Application), */ @@ -736,8 +736,8 @@ const __u8 uclogic_rdesc_ugee_ex07_buttonpad_arr[] = { 0xC0, /* End Collection, */ 0xC0 /* End Collection */ }; -const size_t uclogic_rdesc_ugee_ex07_buttonpad_size = - sizeof(uclogic_rdesc_ugee_ex07_buttonpad_arr); +const size_t uclogic_rdesc_ugee_ex07_frame_size = + sizeof(uclogic_rdesc_ugee_ex07_frame_arr); /* Fixed report descriptor for Ugee G5 frame controls */ const __u8 uclogic_rdesc_ugee_g5_frame_arr[] = { diff --git a/drivers/hid/hid-uclogic-rdesc.h b/drivers/hid/hid-uclogic-rdesc.h index c5da51055af3..40c6f02c6bab 100644 --- a/drivers/hid/hid-uclogic-rdesc.h +++ b/drivers/hid/hid-uclogic-rdesc.h @@ -117,23 +117,23 @@ extern const size_t uclogic_rdesc_pen_v1_template_size; extern const __u8 uclogic_rdesc_pen_v2_template_arr[]; extern const size_t uclogic_rdesc_pen_v2_template_size; -/* Fixed report descriptor for (tweaked) v1 buttonpad reports */ -extern const __u8 uclogic_rdesc_buttonpad_v1_arr[]; -extern const size_t uclogic_rdesc_buttonpad_v1_size; +/* Fixed report descriptor for (tweaked) v1 frame reports */ +extern const __u8 uclogic_rdesc_frame_v1_arr[]; +extern const size_t uclogic_rdesc_frame_v1_size; -/* Report ID for tweaked v1 buttonpad reports */ -#define UCLOGIC_RDESC_BUTTONPAD_V1_ID 0xf7 +/* Report ID for tweaked v1 frame reports */ +#define UCLOGIC_RDESC_FRAME_V1_ID 0xf7 -/* Fixed report descriptor for (tweaked) v2 buttonpad reports */ -extern const __u8 uclogic_rdesc_buttonpad_v2_arr[]; -extern const size_t uclogic_rdesc_buttonpad_v2_size; +/* Fixed report descriptor for (tweaked) v2 frame reports */ +extern const __u8 uclogic_rdesc_frame_v2_arr[]; +extern const size_t uclogic_rdesc_frame_v2_size; -/* Report ID for tweaked v2 buttonpad reports */ -#define UCLOGIC_RDESC_BUTTONPAD_V2_ID 0xf7 +/* Report ID for tweaked v2 frame reports */ +#define UCLOGIC_RDESC_FRAME_V2_ID 0xf7 -/* Fixed report descriptor for Ugee EX07 buttonpad */ -extern const __u8 uclogic_rdesc_ugee_ex07_buttonpad_arr[]; -extern const size_t uclogic_rdesc_ugee_ex07_buttonpad_size; +/* Fixed report descriptor for Ugee EX07 frame */ +extern const __u8 uclogic_rdesc_ugee_ex07_frame_arr[]; +extern const size_t uclogic_rdesc_ugee_ex07_frame_size; /* Fixed report descriptor for XP-Pen Deco 01 frame controls */ extern const __u8 uclogic_rdesc_xppen_deco01_frame_arr[]; From a985de58186fc37992bbebc386ced8d7d25aa400 Mon Sep 17 00:00:00 2001 From: Nikolai Kondrashov Date: Sat, 19 Feb 2022 11:01:55 +0100 Subject: [PATCH 085/137] HID: uclogic: Put version first in rdesc namespace MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Put general hardware version before everything else in uclogic_rdesc_ namespace. Signed-off-by: Nikolai Kondrashov Signed-off-by: José Expósito Signed-off-by: Jiri Kosina --- drivers/hid/hid-uclogic-params.c | 32 ++++++++++++++++---------------- drivers/hid/hid-uclogic-rdesc.c | 28 ++++++++++++++-------------- drivers/hid/hid-uclogic-rdesc.h | 24 ++++++++++++------------ 3 files changed, 42 insertions(+), 42 deletions(-) diff --git a/drivers/hid/hid-uclogic-params.c b/drivers/hid/hid-uclogic-params.c index 168749d9a112..f667347ad0df 100644 --- a/drivers/hid/hid-uclogic-params.c +++ b/drivers/hid/hid-uclogic-params.c @@ -207,8 +207,8 @@ static int uclogic_params_pen_init_v1(struct uclogic_params_pen *pen, * Generate pen report descriptor */ desc_ptr = uclogic_rdesc_template_apply( - uclogic_rdesc_pen_v1_template_arr, - uclogic_rdesc_pen_v1_template_size, + uclogic_rdesc_v1_pen_template_arr, + uclogic_rdesc_v1_pen_template_size, desc_params, ARRAY_SIZE(desc_params)); if (desc_ptr == NULL) { rc = -ENOMEM; @@ -221,8 +221,8 @@ static int uclogic_params_pen_init_v1(struct uclogic_params_pen *pen, memset(pen, 0, sizeof(*pen)); pen->desc_ptr = desc_ptr; desc_ptr = NULL; - pen->desc_size = uclogic_rdesc_pen_v1_template_size; - pen->id = UCLOGIC_RDESC_PEN_V1_ID; + pen->desc_size = uclogic_rdesc_v1_pen_template_size; + pen->id = UCLOGIC_RDESC_V1_PEN_ID; pen->inrange = UCLOGIC_PARAMS_PEN_INRANGE_INVERTED; found = true; finish: @@ -351,8 +351,8 @@ static int uclogic_params_pen_init_v2(struct uclogic_params_pen *pen, * Generate pen report descriptor */ desc_ptr = uclogic_rdesc_template_apply( - uclogic_rdesc_pen_v2_template_arr, - uclogic_rdesc_pen_v2_template_size, + uclogic_rdesc_v2_pen_template_arr, + uclogic_rdesc_v2_pen_template_size, desc_params, ARRAY_SIZE(desc_params)); if (desc_ptr == NULL) { rc = -ENOMEM; @@ -365,8 +365,8 @@ static int uclogic_params_pen_init_v2(struct uclogic_params_pen *pen, memset(pen, 0, sizeof(*pen)); pen->desc_ptr = desc_ptr; desc_ptr = NULL; - pen->desc_size = uclogic_rdesc_pen_v2_template_size; - pen->id = UCLOGIC_RDESC_PEN_V2_ID; + pen->desc_size = uclogic_rdesc_v2_pen_template_size; + pen->id = UCLOGIC_RDESC_V2_PEN_ID; pen->inrange = UCLOGIC_PARAMS_PEN_INRANGE_NONE; pen->fragmented_hires = true; pen->tilt_y_flipped = true; @@ -487,9 +487,9 @@ static int uclogic_params_frame_init_v1(struct uclogic_params_frame *frame, hid_dbg(hdev, "generic buttons enabled\n"); rc = uclogic_params_frame_init_with_desc( frame, - uclogic_rdesc_frame_v1_arr, - uclogic_rdesc_frame_v1_size, - UCLOGIC_RDESC_FRAME_V1_ID); + uclogic_rdesc_v1_frame_arr, + uclogic_rdesc_v1_frame_size, + UCLOGIC_RDESC_V1_FRAME_ID); if (rc != 0) goto cleanup; found = true; @@ -752,9 +752,9 @@ static int uclogic_params_huion_init(struct uclogic_params *params, /* Create v2 frame parameters */ rc = uclogic_params_frame_init_with_desc( &p.frame, - uclogic_rdesc_frame_v2_arr, - uclogic_rdesc_frame_v2_size, - UCLOGIC_RDESC_FRAME_V2_ID); + uclogic_rdesc_v2_frame_arr, + uclogic_rdesc_v2_frame_size, + UCLOGIC_RDESC_V2_FRAME_ID); if (rc != 0) { hid_err(hdev, "failed creating v2 frame parameters: %d\n", @@ -764,7 +764,7 @@ static int uclogic_params_huion_init(struct uclogic_params *params, /* Link frame button subreports from pen reports */ p.pen.subreport_list[0].value = 0xe0; p.pen.subreport_list[0].id = - UCLOGIC_RDESC_FRAME_V2_ID; + UCLOGIC_RDESC_V2_FRAME_ID; goto output; } hid_dbg(hdev, "pen v2 parameters not found\n"); @@ -791,7 +791,7 @@ static int uclogic_params_huion_init(struct uclogic_params *params, /* Link frame button subreports from pen reports */ p.pen.subreport_list[0].value = 0xe0; p.pen.subreport_list[0].id = - UCLOGIC_RDESC_FRAME_V1_ID; + UCLOGIC_RDESC_V1_FRAME_ID; } goto output; } diff --git a/drivers/hid/hid-uclogic-rdesc.c b/drivers/hid/hid-uclogic-rdesc.c index e8dedede3395..04644d93bd11 100644 --- a/drivers/hid/hid-uclogic-rdesc.c +++ b/drivers/hid/hid-uclogic-rdesc.c @@ -532,7 +532,7 @@ const size_t uclogic_rdesc_twha60_fixed1_size = sizeof(uclogic_rdesc_twha60_fixed1_arr); /* Fixed report descriptor template for (tweaked) v1 pen reports */ -const __u8 uclogic_rdesc_pen_v1_template_arr[] = { +const __u8 uclogic_rdesc_v1_pen_template_arr[] = { 0x05, 0x0D, /* Usage Page (Digitizer), */ 0x09, 0x02, /* Usage (Pen), */ 0xA1, 0x01, /* Collection (Application), */ @@ -582,11 +582,11 @@ const __u8 uclogic_rdesc_pen_v1_template_arr[] = { 0xC0 /* End Collection */ }; -const size_t uclogic_rdesc_pen_v1_template_size = - sizeof(uclogic_rdesc_pen_v1_template_arr); +const size_t uclogic_rdesc_v1_pen_template_size = + sizeof(uclogic_rdesc_v1_pen_template_arr); /* Fixed report descriptor template for (tweaked) v2 pen reports */ -const __u8 uclogic_rdesc_pen_v2_template_arr[] = { +const __u8 uclogic_rdesc_v2_pen_template_arr[] = { 0x05, 0x0D, /* Usage Page (Digitizer), */ 0x09, 0x02, /* Usage (Pen), */ 0xA1, 0x01, /* Collection (Application), */ @@ -648,8 +648,8 @@ const __u8 uclogic_rdesc_pen_v2_template_arr[] = { 0xC0 /* End Collection */ }; -const size_t uclogic_rdesc_pen_v2_template_size = - sizeof(uclogic_rdesc_pen_v2_template_arr); +const size_t uclogic_rdesc_v2_pen_template_size = + sizeof(uclogic_rdesc_v2_pen_template_arr); /* * Expand to the contents of a generic frame report descriptor. @@ -699,18 +699,18 @@ const size_t uclogic_rdesc_pen_v2_template_size = 0xC0 /* End Collection */ /* Fixed report descriptor for (tweaked) v1 frame reports */ -const __u8 uclogic_rdesc_frame_v1_arr[] = { - UCLOGIC_RDESC_FRAME_BYTES(UCLOGIC_RDESC_FRAME_V1_ID, 8) +const __u8 uclogic_rdesc_v1_frame_arr[] = { + UCLOGIC_RDESC_FRAME_BYTES(UCLOGIC_RDESC_V1_FRAME_ID, 8) }; -const size_t uclogic_rdesc_frame_v1_size = - sizeof(uclogic_rdesc_frame_v1_arr); +const size_t uclogic_rdesc_v1_frame_size = + sizeof(uclogic_rdesc_v1_frame_arr); /* Fixed report descriptor for (tweaked) v2 frame reports */ -const __u8 uclogic_rdesc_frame_v2_arr[] = { - UCLOGIC_RDESC_FRAME_BYTES(UCLOGIC_RDESC_FRAME_V2_ID, 12) +const __u8 uclogic_rdesc_v2_frame_arr[] = { + UCLOGIC_RDESC_FRAME_BYTES(UCLOGIC_RDESC_V2_FRAME_ID, 12) }; -const size_t uclogic_rdesc_frame_v2_size = - sizeof(uclogic_rdesc_frame_v2_arr); +const size_t uclogic_rdesc_v2_frame_size = + sizeof(uclogic_rdesc_v2_frame_arr); /* Fixed report descriptor for Ugee EX07 frame */ const __u8 uclogic_rdesc_ugee_ex07_frame_arr[] = { diff --git a/drivers/hid/hid-uclogic-rdesc.h b/drivers/hid/hid-uclogic-rdesc.h index 40c6f02c6bab..d1ab1f346f22 100644 --- a/drivers/hid/hid-uclogic-rdesc.h +++ b/drivers/hid/hid-uclogic-rdesc.h @@ -104,32 +104,32 @@ enum uclogic_rdesc_pen_ph_id { UCLOGIC_RDESC_PH_HEAD, UCLOGIC_RDESC_PEN_PH_ID_##_ID /* Report ID for v1 pen reports */ -#define UCLOGIC_RDESC_PEN_V1_ID 0x07 +#define UCLOGIC_RDESC_V1_PEN_ID 0x07 /* Fixed report descriptor template for (tweaked) v1 pen reports */ -extern const __u8 uclogic_rdesc_pen_v1_template_arr[]; -extern const size_t uclogic_rdesc_pen_v1_template_size; +extern const __u8 uclogic_rdesc_v1_pen_template_arr[]; +extern const size_t uclogic_rdesc_v1_pen_template_size; /* Report ID for v2 pen reports */ -#define UCLOGIC_RDESC_PEN_V2_ID 0x08 +#define UCLOGIC_RDESC_V2_PEN_ID 0x08 /* Fixed report descriptor template for (tweaked) v2 pen reports */ -extern const __u8 uclogic_rdesc_pen_v2_template_arr[]; -extern const size_t uclogic_rdesc_pen_v2_template_size; +extern const __u8 uclogic_rdesc_v2_pen_template_arr[]; +extern const size_t uclogic_rdesc_v2_pen_template_size; /* Fixed report descriptor for (tweaked) v1 frame reports */ -extern const __u8 uclogic_rdesc_frame_v1_arr[]; -extern const size_t uclogic_rdesc_frame_v1_size; +extern const __u8 uclogic_rdesc_v1_frame_arr[]; +extern const size_t uclogic_rdesc_v1_frame_size; /* Report ID for tweaked v1 frame reports */ -#define UCLOGIC_RDESC_FRAME_V1_ID 0xf7 +#define UCLOGIC_RDESC_V1_FRAME_ID 0xf7 /* Fixed report descriptor for (tweaked) v2 frame reports */ -extern const __u8 uclogic_rdesc_frame_v2_arr[]; -extern const size_t uclogic_rdesc_frame_v2_size; +extern const __u8 uclogic_rdesc_v2_frame_arr[]; +extern const size_t uclogic_rdesc_v2_frame_size; /* Report ID for tweaked v2 frame reports */ -#define UCLOGIC_RDESC_FRAME_V2_ID 0xf7 +#define UCLOGIC_RDESC_V2_FRAME_ID 0xf7 /* Fixed report descriptor for Ugee EX07 frame */ extern const __u8 uclogic_rdesc_ugee_ex07_frame_arr[]; From 2daaeff3824dfb27409f499a2a945970729ba86f Mon Sep 17 00:00:00 2001 From: Nikolai Kondrashov Date: Sat, 19 Feb 2022 11:01:56 +0100 Subject: [PATCH 086/137] HID: uclogic: Define report IDs before their descriptors MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Nikolai Kondrashov Signed-off-by: José Expósito Signed-off-by: Jiri Kosina --- drivers/hid/hid-uclogic-rdesc.h | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/drivers/hid/hid-uclogic-rdesc.h b/drivers/hid/hid-uclogic-rdesc.h index d1ab1f346f22..3d904c27b86a 100644 --- a/drivers/hid/hid-uclogic-rdesc.h +++ b/drivers/hid/hid-uclogic-rdesc.h @@ -117,20 +117,20 @@ extern const size_t uclogic_rdesc_v1_pen_template_size; extern const __u8 uclogic_rdesc_v2_pen_template_arr[]; extern const size_t uclogic_rdesc_v2_pen_template_size; +/* Report ID for tweaked v1 frame reports */ +#define UCLOGIC_RDESC_V1_FRAME_ID 0xf7 + /* Fixed report descriptor for (tweaked) v1 frame reports */ extern const __u8 uclogic_rdesc_v1_frame_arr[]; extern const size_t uclogic_rdesc_v1_frame_size; -/* Report ID for tweaked v1 frame reports */ -#define UCLOGIC_RDESC_V1_FRAME_ID 0xf7 +/* Report ID for tweaked v2 frame reports */ +#define UCLOGIC_RDESC_V2_FRAME_ID 0xf7 /* Fixed report descriptor for (tweaked) v2 frame reports */ extern const __u8 uclogic_rdesc_v2_frame_arr[]; extern const size_t uclogic_rdesc_v2_frame_size; -/* Report ID for tweaked v2 frame reports */ -#define UCLOGIC_RDESC_V2_FRAME_ID 0xf7 - /* Fixed report descriptor for Ugee EX07 frame */ extern const __u8 uclogic_rdesc_ugee_ex07_frame_arr[]; extern const size_t uclogic_rdesc_ugee_ex07_frame_size; From 337fa051d9b8aabf7081795d40e92e9d96f3442d Mon Sep 17 00:00:00 2001 From: Nikolai Kondrashov Date: Sat, 19 Feb 2022 11:01:57 +0100 Subject: [PATCH 087/137] HID: uclogic: Support multiple frame input devices MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add support for multiple frame input devices and their parameters to the UC-Logic driver. This prepares for creating a separate input device for Huion HS610 virtual touch ring reports. Signed-off-by: Nikolai Kondrashov Signed-off-by: José Expósito Signed-off-by: Jiri Kosina --- drivers/hid/hid-uclogic-core.c | 19 ++++-- drivers/hid/hid-uclogic-params.c | 102 +++++++++++++++---------------- drivers/hid/hid-uclogic-params.h | 26 ++++---- 3 files changed, 75 insertions(+), 72 deletions(-) diff --git a/drivers/hid/hid-uclogic-core.c b/drivers/hid/hid-uclogic-core.c index a02edeb30a35..05147f2d7564 100644 --- a/drivers/hid/hid-uclogic-core.c +++ b/drivers/hid/hid-uclogic-core.c @@ -296,17 +296,18 @@ static int uclogic_raw_event_pen(struct uclogic_drvdata *drvdata, * uclogic_raw_event_frame - handle raw frame events (frame HID reports). * * @drvdata: Driver data. + * @frame: The parameters of the frame controls to handle. * @data: Report data buffer, can be modified. * @size: Report data size, bytes. * * Returns: * Negative value on error (stops event delivery), zero for success. */ -static int uclogic_raw_event_frame(struct uclogic_drvdata *drvdata, - u8 *data, int size) +static int uclogic_raw_event_frame( + struct uclogic_drvdata *drvdata, + const struct uclogic_params_frame *frame, + u8 *data, int size) { - struct uclogic_params_frame *frame = &drvdata->params.frame; - WARN_ON(drvdata == NULL); WARN_ON(data == NULL && size != 0); @@ -352,6 +353,7 @@ static int uclogic_raw_event(struct hid_device *hdev, struct uclogic_params *params = &drvdata->params; struct uclogic_params_pen_subreport *subreport; struct uclogic_params_pen_subreport *subreport_list_end; + size_t i; /* Do not handle anything but input reports */ if (report->type != HID_INPUT_REPORT) @@ -382,8 +384,13 @@ static int uclogic_raw_event(struct hid_device *hdev, } /* Tweak frame control reports, if necessary */ - if (report_id == params->frame.id) - return uclogic_raw_event_frame(drvdata, data, size); + for (i = 0; i < ARRAY_SIZE(params->frame_list); i++) { + if (report_id == params->frame_list[i].id) { + return uclogic_raw_event_frame( + drvdata, ¶ms->frame_list[i], + data, size); + } + } break; } diff --git a/drivers/hid/hid-uclogic-params.c b/drivers/hid/hid-uclogic-params.c index f667347ad0df..5f50ceb875d6 100644 --- a/drivers/hid/hid-uclogic-params.c +++ b/drivers/hid/hid-uclogic-params.c @@ -512,9 +512,12 @@ static int uclogic_params_frame_init_v1(struct uclogic_params_frame *frame, void uclogic_params_cleanup(struct uclogic_params *params) { if (!params->invalid) { + size_t i; kfree(params->desc_ptr); uclogic_params_pen_cleanup(¶ms->pen); - uclogic_params_frame_cleanup(¶ms->frame); + for (i = 0; i < ARRAY_SIZE(params->frame_list); i++) + uclogic_params_frame_cleanup(¶ms->frame_list[i]); + memset(params, 0, sizeof(*params)); } } @@ -542,60 +545,53 @@ int uclogic_params_get_desc(const struct uclogic_params *params, __u8 **pdesc, unsigned int *psize) { - bool common_present; - bool pen_present; - bool frame_present; - unsigned int size; + int rc = -ENOMEM; + bool present = false; + unsigned int size = 0; __u8 *desc = NULL; + size_t i; /* Check arguments */ if (params == NULL || pdesc == NULL || psize == NULL) return -EINVAL; - size = 0; + /* Concatenate descriptors */ +#define ADD_DESC(_desc_ptr, _desc_size) \ + do { \ + unsigned int new_size; \ + __u8 *new_desc; \ + if ((_desc_ptr) == NULL) { \ + break; \ + } \ + new_size = size + (_desc_size); \ + new_desc = krealloc(desc, new_size, GFP_KERNEL); \ + if (new_desc == NULL) { \ + goto cleanup; \ + } \ + memcpy(new_desc + size, (_desc_ptr), (_desc_size)); \ + desc = new_desc; \ + size = new_size; \ + present = true; \ + } while (0) - common_present = (params->desc_ptr != NULL); - pen_present = (params->pen.desc_ptr != NULL); - frame_present = (params->frame.desc_ptr != NULL); - - if (common_present) - size += params->desc_size; - if (pen_present) - size += params->pen.desc_size; - if (frame_present) - size += params->frame.desc_size; - - if (common_present || pen_present || frame_present) { - __u8 *p; - - desc = kmalloc(size, GFP_KERNEL); - if (desc == NULL) - return -ENOMEM; - p = desc; - - if (common_present) { - memcpy(p, params->desc_ptr, - params->desc_size); - p += params->desc_size; - } - if (pen_present) { - memcpy(p, params->pen.desc_ptr, - params->pen.desc_size); - p += params->pen.desc_size; - } - if (frame_present) { - memcpy(p, params->frame.desc_ptr, - params->frame.desc_size); - p += params->frame.desc_size; - } - - WARN_ON(p != desc + size); - - *psize = size; + ADD_DESC(params->desc_ptr, params->desc_size); + ADD_DESC(params->pen.desc_ptr, params->pen.desc_size); + for (i = 0; i < ARRAY_SIZE(params->frame_list); i++) { + ADD_DESC(params->frame_list[i].desc_ptr, + params->frame_list[i].desc_size); } - *pdesc = desc; - return 0; +#undef ADD_DESC + + if (present) { + *pdesc = desc; + *psize = size; + desc = NULL; + } + rc = 0; +cleanup: + kfree(desc); + return rc; } /** @@ -751,7 +747,7 @@ static int uclogic_params_huion_init(struct uclogic_params *params, hid_dbg(hdev, "pen v2 parameters found\n"); /* Create v2 frame parameters */ rc = uclogic_params_frame_init_with_desc( - &p.frame, + &p.frame_list[0], uclogic_rdesc_v2_frame_arr, uclogic_rdesc_v2_frame_size, UCLOGIC_RDESC_V2_FRAME_ID); @@ -779,7 +775,7 @@ static int uclogic_params_huion_init(struct uclogic_params *params, } else if (found) { hid_dbg(hdev, "pen v1 parameters found\n"); /* Try to probe v1 frame */ - rc = uclogic_params_frame_init_v1(&p.frame, + rc = uclogic_params_frame_init_v1(&p.frame_list[0], &found, hdev); if (rc != 0) { hid_err(hdev, "v1 frame probing failed: %d\n", rc); @@ -1033,7 +1029,7 @@ int uclogic_params_init(struct uclogic_params *params, } /* Initialize frame parameters */ rc = uclogic_params_frame_init_with_desc( - &p.frame, + &p.frame_list[0], uclogic_rdesc_xppen_deco01_frame_arr, uclogic_rdesc_xppen_deco01_frame_size, 0); @@ -1059,7 +1055,7 @@ int uclogic_params_init(struct uclogic_params *params, goto cleanup; } else if (found) { rc = uclogic_params_frame_init_with_desc( - &p.frame, + &p.frame_list[0], uclogic_rdesc_ugee_g5_frame_arr, uclogic_rdesc_ugee_g5_frame_size, UCLOGIC_RDESC_UGEE_G5_FRAME_ID); @@ -1069,9 +1065,9 @@ int uclogic_params_init(struct uclogic_params *params, rc); goto cleanup; } - p.frame.re_lsb = + p.frame_list[0].re_lsb = UCLOGIC_RDESC_UGEE_G5_FRAME_RE_LSB; - p.frame.dev_id_byte = + p.frame_list[0].dev_id_byte = UCLOGIC_RDESC_UGEE_G5_FRAME_DEV_ID_BYTE; } else { hid_warn(hdev, "pen parameters not found"); @@ -1093,7 +1089,7 @@ int uclogic_params_init(struct uclogic_params *params, goto cleanup; } else if (found) { rc = uclogic_params_frame_init_with_desc( - &p.frame, + &p.frame_list[0], uclogic_rdesc_ugee_ex07_frame_arr, uclogic_rdesc_ugee_ex07_frame_size, 0); diff --git a/drivers/hid/hid-uclogic-params.h b/drivers/hid/hid-uclogic-params.h index c18569591b75..86f616dfbb53 100644 --- a/drivers/hid/hid-uclogic-params.h +++ b/drivers/hid/hid-uclogic-params.h @@ -165,10 +165,10 @@ struct uclogic_params { */ struct uclogic_params_pen pen; /* - * Frame control parameters and optional report descriptor part. - * Only valid, if "invalid" is false. + * The list of frame control parameters and optional report descriptor + * parts. Only valid, if "invalid" is false. */ - struct uclogic_params_frame frame; + struct uclogic_params_frame frame_list[1]; }; /* Initialize a tablet interface and discover its parameters */ @@ -187,11 +187,11 @@ extern int uclogic_params_init(struct uclogic_params *params, ".pen.inrange = %s\n" \ ".pen.fragmented_hires = %s\n" \ ".pen.tilt_y_flipped = %s\n" \ - ".frame.desc_ptr = %p\n" \ - ".frame.desc_size = %u\n" \ - ".frame.id = %u\n" \ - ".frame.re_lsb = %u\n" \ - ".frame.dev_id_byte = %u\n" + ".frame_list[0].desc_ptr = %p\n" \ + ".frame_list[0].desc_size = %u\n" \ + ".frame_list[0].id = %u\n" \ + ".frame_list[0].re_lsb = %u\n" \ + ".frame_list[0].dev_id_byte = %u\n" /* Tablet interface parameters *printf format arguments */ #define UCLOGIC_PARAMS_FMT_ARGS(_params) \ @@ -206,11 +206,11 @@ extern int uclogic_params_init(struct uclogic_params *params, uclogic_params_pen_inrange_to_str((_params)->pen.inrange), \ ((_params)->pen.fragmented_hires ? "true" : "false"), \ ((_params)->pen.tilt_y_flipped ? "true" : "false"), \ - (_params)->frame.desc_ptr, \ - (_params)->frame.desc_size, \ - (_params)->frame.id, \ - (_params)->frame.re_lsb, \ - (_params)->frame.dev_id_byte + (_params)->frame_list[0].desc_ptr, \ + (_params)->frame_list[0].desc_size, \ + (_params)->frame_list[0].id, \ + (_params)->frame_list[0].re_lsb, \ + (_params)->frame_list[0].dev_id_byte /* Get a replacement report descriptor for a tablet's interface. */ extern int uclogic_params_get_desc(const struct uclogic_params *params, From 1c1813a743fe84d0dcf53743baa4edc1f74c44c8 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Thu, 3 Feb 2022 15:32:15 +0100 Subject: [PATCH 088/137] HID: core: statically allocate read buffers This is a preparation patch for rethinking the generic processing of HID reports. We can actually pre-allocate all of our memory instead of dynamically allocating/freeing it whenever we parse a report. Signed-off-by: Benjamin Tissoires Reviewed-by: Ping Cheng Acked-by: Peter Hutterer Signed-off-by: Jiri Kosina --- drivers/hid/hid-core.c | 12 +++++------- include/linux/hid.h | 1 + 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c index f1aed5bbd000..75e7b8447bf7 100644 --- a/drivers/hid/hid-core.c +++ b/drivers/hid/hid-core.c @@ -101,7 +101,7 @@ static struct hid_field *hid_register_field(struct hid_report *report, unsigned field = kzalloc((sizeof(struct hid_field) + usages * sizeof(struct hid_usage) + - usages * sizeof(unsigned)), GFP_KERNEL); + 2 * usages * sizeof(unsigned int)), GFP_KERNEL); if (!field) return NULL; @@ -109,6 +109,7 @@ static struct hid_field *hid_register_field(struct hid_report *report, unsigned report->field[field->index] = field; field->usage = (struct hid_usage *)(field + 1); field->value = (s32 *)(field->usage + usages); + field->new_value = (s32 *)(field->value + usages); field->report = report; return field; @@ -1541,9 +1542,8 @@ static void hid_input_field(struct hid_device *hid, struct hid_field *field, __s32 max = field->logical_maximum; __s32 *value; - value = kmalloc_array(count, sizeof(__s32), GFP_ATOMIC); - if (!value) - return; + value = field->new_value; + memset(value, 0, count * sizeof(__s32)); for (n = 0; n < count; n++) { @@ -1557,7 +1557,7 @@ static void hid_input_field(struct hid_device *hid, struct hid_field *field, value[n] >= min && value[n] <= max && value[n] - min < field->maxusage && field->usage[value[n] - min].hid == HID_UP_KEYBOARD + 1) - goto exit; + return; } for (n = 0; n < count; n++) { @@ -1581,8 +1581,6 @@ static void hid_input_field(struct hid_device *hid, struct hid_field *field, } memcpy(field->value, value, count * sizeof(__s32)); -exit: - kfree(value); } /* diff --git a/include/linux/hid.h b/include/linux/hid.h index 7487b0586fe6..3fbfe0986659 100644 --- a/include/linux/hid.h +++ b/include/linux/hid.h @@ -476,6 +476,7 @@ struct hid_field { unsigned report_count; /* number of this field in the report */ unsigned report_type; /* (input,output,feature) */ __s32 *value; /* last known value(s) */ + __s32 *new_value; /* newly read value(s) */ __s32 logical_minimum; __s32 logical_maximum; __s32 physical_minimum; From 74acc277b65219002bb103e51e461c2934563ada Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Thu, 3 Feb 2022 15:32:16 +0100 Subject: [PATCH 089/137] HID: core: de-duplicate some code in hid_input_field() I had to go twice through the history to get a grasp at this code. De-duplicate the various tests in one common helper to make it more explicit. Note that the `HID_UP_KEYBOARD + 1` condition is tested through https://gitlab.freedesktop.org/libevdev/hid-tools/-/merge_requests/121 Signed-off-by: Benjamin Tissoires Reviewed-by: Ping Cheng Acked-by: Peter Hutterer Signed-off-by: Jiri Kosina --- drivers/hid/hid-core.c | 52 ++++++++++++++++++++++++++++++------------ 1 file changed, 38 insertions(+), 14 deletions(-) diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c index 75e7b8447bf7..fad4dbdf6391 100644 --- a/drivers/hid/hid-core.c +++ b/drivers/hid/hid-core.c @@ -1525,6 +1525,24 @@ static void hid_process_event(struct hid_device *hid, struct hid_field *field, hid->hiddev_hid_event(hid, field, usage, value); } +/* + * Checks if the given value is valid within this field + */ +static inline int hid_array_value_is_valid(struct hid_field *field, + __s32 value) +{ + __s32 min = field->logical_minimum; + + /* + * Value needs to be between logical min and max, and + * (value - min) is used as an index in the usage array. + * This array is of size field->maxusage + */ + return value >= min && + value <= field->logical_maximum && + value - min < field->maxusage; +} + /* * Analyse a received field, and fetch the data from it. The field * content is stored for next report processing (we do differential @@ -1539,7 +1557,6 @@ static void hid_input_field(struct hid_device *hid, struct hid_field *field, unsigned offset = field->report_offset; unsigned size = field->report_size; __s32 min = field->logical_minimum; - __s32 max = field->logical_maximum; __s32 *value; value = field->new_value; @@ -1554,8 +1571,7 @@ static void hid_input_field(struct hid_device *hid, struct hid_field *field, /* Ignore report if ErrorRollOver */ if (!(field->flags & HID_MAIN_ITEM_VARIABLE) && - value[n] >= min && value[n] <= max && - value[n] - min < field->maxusage && + hid_array_value_is_valid(field, value[n]) && field->usage[value[n] - min].hid == HID_UP_KEYBOARD + 1) return; } @@ -1563,21 +1579,29 @@ static void hid_input_field(struct hid_device *hid, struct hid_field *field, for (n = 0; n < count; n++) { if (HID_MAIN_ITEM_VARIABLE & field->flags) { - hid_process_event(hid, field, &field->usage[n], value[n], interrupt); + hid_process_event(hid, + field, + &field->usage[n], + value[n], + interrupt); continue; } - if (field->value[n] >= min && field->value[n] <= max - && field->value[n] - min < field->maxusage - && field->usage[field->value[n] - min].hid - && search(value, field->value[n], count)) - hid_process_event(hid, field, &field->usage[field->value[n] - min], 0, interrupt); + if (hid_array_value_is_valid(field, field->value[n]) && + search(value, field->value[n], count)) + hid_process_event(hid, + field, + &field->usage[field->value[n] - min], + 0, + interrupt); - if (value[n] >= min && value[n] <= max - && value[n] - min < field->maxusage - && field->usage[value[n] - min].hid - && search(field->value, value[n], count)) - hid_process_event(hid, field, &field->usage[value[n] - min], 1, interrupt); + if (hid_array_value_is_valid(field, value[n]) && + search(field->value, value[n], count)) + hid_process_event(hid, + field, + &field->usage[value[n] - min], + 1, + interrupt); } memcpy(field->value, value, count * sizeof(__s32)); From b79c1abae5e19726c5060749e4e7c9e426b045c8 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Thu, 3 Feb 2022 15:32:17 +0100 Subject: [PATCH 090/137] HID: core: split data fetching from processing in hid_input_field() This is a preparatory patch for being able to process the usages out of order. We split the retrieval of the data in a separate function and also split out the processing of the usages depending if the field is an array or a variable. No functional changes from this patch. Signed-off-by: Benjamin Tissoires Reviewed-by: Ping Cheng Acked-by: Peter Hutterer Signed-off-by: Jiri Kosina --- drivers/hid/hid-core.c | 98 +++++++++++++++++++++++++++++++++--------- include/linux/hid.h | 3 +- 2 files changed, 80 insertions(+), 21 deletions(-) diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c index fad4dbdf6391..34188d7ac0f7 100644 --- a/drivers/hid/hid-core.c +++ b/drivers/hid/hid-core.c @@ -1544,13 +1544,12 @@ static inline int hid_array_value_is_valid(struct hid_field *field, } /* - * Analyse a received field, and fetch the data from it. The field - * content is stored for next report processing (we do differential - * reporting to the layer). + * Fetch the field from the data. The field content is stored for next + * report processing (we do differential reporting to the layer). */ - -static void hid_input_field(struct hid_device *hid, struct hid_field *field, - __u8 *data, int interrupt) +static void hid_input_fetch_field(struct hid_device *hid, + struct hid_field *field, + __u8 *data) { unsigned n; unsigned count = field->report_count; @@ -1561,6 +1560,7 @@ static void hid_input_field(struct hid_device *hid, struct hid_field *field, value = field->new_value; memset(value, 0, count * sizeof(__s32)); + field->ignored = false; for (n = 0; n < count; n++) { @@ -1572,21 +1572,56 @@ static void hid_input_field(struct hid_device *hid, struct hid_field *field, /* Ignore report if ErrorRollOver */ if (!(field->flags & HID_MAIN_ITEM_VARIABLE) && hid_array_value_is_valid(field, value[n]) && - field->usage[value[n] - min].hid == HID_UP_KEYBOARD + 1) + field->usage[value[n] - min].hid == HID_UP_KEYBOARD + 1) { + field->ignored = true; return; + } } +} + +/* + * Process a received variable field. + */ + +static void hid_input_var_field(struct hid_device *hid, + struct hid_field *field, + int interrupt) +{ + unsigned int count = field->report_count; + __s32 *value = field->new_value; + unsigned int n; + + for (n = 0; n < count; n++) + hid_process_event(hid, + field, + &field->usage[n], + value[n], + interrupt); + + memcpy(field->value, value, count * sizeof(__s32)); +} + +/* + * Process a received array field. The field content is stored for + * next report processing (we do differential reporting to the layer). + */ + +static void hid_input_array_field(struct hid_device *hid, + struct hid_field *field, + int interrupt) +{ + unsigned int n; + unsigned int count = field->report_count; + __s32 min = field->logical_minimum; + __s32 *value; + + value = field->new_value; + + /* ErrorRollOver */ + if (field->ignored) + return; for (n = 0; n < count; n++) { - - if (HID_MAIN_ITEM_VARIABLE & field->flags) { - hid_process_event(hid, - field, - &field->usage[n], - value[n], - interrupt); - continue; - } - if (hid_array_value_is_valid(field, field->value[n]) && search(value, field->value[n], count)) hid_process_event(hid, @@ -1607,6 +1642,31 @@ static void hid_input_field(struct hid_device *hid, struct hid_field *field, memcpy(field->value, value, count * sizeof(__s32)); } +/* + * Analyse a received report, and fetch the data from it. The field + * content is stored for next report processing (we do differential + * reporting to the layer). + */ +static void hid_process_report(struct hid_device *hid, + struct hid_report *report, + __u8 *data, + int interrupt) +{ + unsigned int a; + struct hid_field *field; + + for (a = 0; a < report->maxfield; a++) { + field = report->field[a]; + + hid_input_fetch_field(hid, field, data); + + if (field->flags & HID_MAIN_ITEM_VARIABLE) + hid_input_var_field(hid, field, interrupt); + else + hid_input_array_field(hid, field, interrupt); + } +} + /* * Output the field into the report. */ @@ -1768,7 +1828,6 @@ int hid_report_raw_event(struct hid_device *hid, int type, u8 *data, u32 size, struct hid_report_enum *report_enum = hid->report_enum + type; struct hid_report *report; struct hid_driver *hdrv; - unsigned int a; u32 rsize, csize = size; u8 *cdata = data; int ret = 0; @@ -1804,8 +1863,7 @@ int hid_report_raw_event(struct hid_device *hid, int type, u8 *data, u32 size, } if (hid->claimed != HID_CLAIMED_HIDRAW && report->maxfield) { - for (a = 0; a < report->maxfield; a++) - hid_input_field(hid, report->field[a], cdata, interrupt); + hid_process_report(hid, report, cdata, interrupt); hdrv = hid->driver; if (hdrv && hdrv->report) hdrv->report(hid, report); diff --git a/include/linux/hid.h b/include/linux/hid.h index 3fbfe0986659..cf79eb3da465 100644 --- a/include/linux/hid.h +++ b/include/linux/hid.h @@ -342,7 +342,7 @@ struct hid_item { * HID device quirks. */ -/* +/* * Increase this if you need to configure more HID quirks at module load time */ #define MAX_USBHID_BOOT_QUIRKS 4 @@ -483,6 +483,7 @@ struct hid_field { __s32 physical_maximum; __s32 unit_exponent; unsigned unit; + bool ignored; /* this field is ignored in this event */ struct hid_report *report; /* associated report */ unsigned index; /* index into report->field[] */ /* hidinput data */ From 3c2b0dbd69901b71848c83d5c642be290198aa13 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Thu, 3 Feb 2022 15:32:18 +0100 Subject: [PATCH 091/137] HID: input: tag touchscreens as such if the physical is not there Some devices (Elan, Synaptics...) are sometimes not setting a physical in their finger collections. hid-input will consider them to be pen devices, leading to some wrong behavior in user space. Signed-off-by: Benjamin Tissoires Reviewed-by: Ping Cheng Acked-by: Peter Hutterer Signed-off-by: Jiri Kosina --- drivers/hid/hid-input.c | 29 +++++++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/drivers/hid/hid-input.c b/drivers/hid/hid-input.c index 112901d2d8d2..d2562497a726 100644 --- a/drivers/hid/hid-input.c +++ b/drivers/hid/hid-input.c @@ -828,10 +828,31 @@ static void hidinput_configure_usage(struct hid_input *hidinput, struct hid_fiel break; case 0x32: /* InRange */ - switch (field->physical & 0xff) { - case 0x21: map_key(BTN_TOOL_MOUSE); break; - case 0x22: map_key(BTN_TOOL_FINGER); break; - default: map_key(BTN_TOOL_PEN); break; + switch (field->physical) { + case HID_DG_PUCK: + map_key(BTN_TOOL_MOUSE); + break; + case HID_DG_FINGER: + map_key(BTN_TOOL_FINGER); + break; + default: + /* + * If the physical is not given, + * rely on the application. + */ + if (!field->physical) { + switch (field->application) { + case HID_DG_TOUCHSCREEN: + case HID_DG_TOUCHPAD: + map_key_clear(BTN_TOOL_FINGER); + break; + default: + map_key_clear(BTN_TOOL_PEN); + } + } else { + map_key(BTN_TOOL_PEN); + } + break; } break; From f2d4ddfa640d2371bad55891159102acd2504108 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Thu, 3 Feb 2022 15:32:19 +0100 Subject: [PATCH 092/137] HID: input: rework spaghetti code with switch statements Instead of using multiple `if (a == b)`, use the switch statement which has been done exactly for that. There should be no functional change (I don't expect moving down HID_QUIRK_{X|Y}_INVERT having any impact. Signed-off-by: Benjamin Tissoires Reviewed-by: Ping Cheng Acked-by: Peter Hutterer Signed-off-by: Jiri Kosina --- drivers/hid/hid-input.c | 82 ++++++++++++++++++++++------------------- 1 file changed, 44 insertions(+), 38 deletions(-) diff --git a/drivers/hid/hid-input.c b/drivers/hid/hid-input.c index d2562497a726..54b3e9c5ccc4 100644 --- a/drivers/hid/hid-input.c +++ b/drivers/hid/hid-input.c @@ -1354,12 +1354,6 @@ void hidinput_hid_event(struct hid_device *hid, struct hid_field *field, struct input = field->hidinput->input; - if (usage->type == EV_ABS && - (((*quirks & HID_QUIRK_X_INVERT) && usage->code == ABS_X) || - ((*quirks & HID_QUIRK_Y_INVERT) && usage->code == ABS_Y))) { - value = field->logical_maximum - value; - } - if (usage->hat_min < usage->hat_max || usage->hat_dir) { int hat_dir = usage->hat_dir; if (!hat_dir) @@ -1370,12 +1364,12 @@ void hidinput_hid_event(struct hid_device *hid, struct hid_field *field, struct return; } - if (usage->hid == HID_DG_INVERT) { + switch (usage->hid) { + case HID_DG_INVERT: *quirks = value ? (*quirks | HID_QUIRK_INVERT) : (*quirks & ~HID_QUIRK_INVERT); return; - } - if (usage->hid == HID_DG_INRANGE) { + case HID_DG_INRANGE: if (value) { input_event(input, usage->type, (*quirks & HID_QUIRK_INVERT) ? BTN_TOOL_RUBBER : usage->code, 1); return; @@ -1383,46 +1377,58 @@ void hidinput_hid_event(struct hid_device *hid, struct hid_field *field, struct input_event(input, usage->type, usage->code, 0); input_event(input, usage->type, BTN_TOOL_RUBBER, 0); return; - } - if (usage->hid == HID_DG_TIPPRESSURE && (*quirks & HID_QUIRK_NOTOUCH)) { - int a = field->logical_minimum; - int b = field->logical_maximum; - input_event(input, EV_KEY, BTN_TOUCH, value > a + ((b - a) >> 3)); - } + case HID_DG_TIPPRESSURE: + if (*quirks & HID_QUIRK_NOTOUCH) { + int a = field->logical_minimum; + int b = field->logical_maximum; - if (usage->hid == (HID_UP_PID | 0x83UL)) { /* Simultaneous Effects Max */ + input_event(input, EV_KEY, BTN_TOUCH, value > a + ((b - a) >> 3)); + } + break; + + case HID_UP_PID | 0x83UL: /* Simultaneous Effects Max */ dbg_hid("Maximum Effects - %d\n",value); return; - } - if (usage->hid == (HID_UP_PID | 0x7fUL)) { + case HID_UP_PID | 0x7fUL: dbg_hid("PID Pool Report\n"); return; } - if ((usage->type == EV_KEY) && (usage->code == 0)) /* Key 0 is "unassigned", not KEY_UNKNOWN */ - return; + switch (usage->type) { + case EV_KEY: + if (usage->code == 0) /* Key 0 is "unassigned", not KEY_UNKNOWN */ + return; + break; - if ((usage->type == EV_REL) && (usage->code == REL_WHEEL_HI_RES || - usage->code == REL_HWHEEL_HI_RES)) { - hidinput_handle_scroll(usage, input, value); - return; - } - - if ((usage->type == EV_ABS) && (field->flags & HID_MAIN_ITEM_RELATIVE) && - (usage->code == ABS_VOLUME)) { - int count = abs(value); - int direction = value > 0 ? KEY_VOLUMEUP : KEY_VOLUMEDOWN; - int i; - - for (i = 0; i < count; i++) { - input_event(input, EV_KEY, direction, 1); - input_sync(input); - input_event(input, EV_KEY, direction, 0); - input_sync(input); + case EV_REL: + if (usage->code == REL_WHEEL_HI_RES || + usage->code == REL_HWHEEL_HI_RES) { + hidinput_handle_scroll(usage, input, value); + return; } - return; + break; + + case EV_ABS: + if ((field->flags & HID_MAIN_ITEM_RELATIVE) && + usage->code == ABS_VOLUME) { + int count = abs(value); + int direction = value > 0 ? KEY_VOLUMEUP : KEY_VOLUMEDOWN; + int i; + + for (i = 0; i < count; i++) { + input_event(input, EV_KEY, direction, 1); + input_sync(input); + input_event(input, EV_KEY, direction, 0); + input_sync(input); + } + return; + + } else if (((*quirks & HID_QUIRK_X_INVERT) && usage->code == ABS_X) || + ((*quirks & HID_QUIRK_Y_INVERT) && usage->code == ABS_Y)) + value = field->logical_maximum - value; + break; } /* From 187ccd6caa4b40dcfbcb2531746e9fe500414e79 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Thu, 3 Feb 2022 15:32:20 +0100 Subject: [PATCH 093/137] HID: input: move up out-of-range processing of input values It actually makes sense to clamp the value to its boundaries before doing further processing. Signed-off-by: Benjamin Tissoires Reviewed-by: Ping Cheng Acked-by: Peter Hutterer Signed-off-by: Jiri Kosina --- drivers/hid/hid-input.c | 48 ++++++++++++++++++++--------------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/drivers/hid/hid-input.c b/drivers/hid/hid-input.c index 54b3e9c5ccc4..8770d9a2b2af 100644 --- a/drivers/hid/hid-input.c +++ b/drivers/hid/hid-input.c @@ -1364,6 +1364,30 @@ void hidinput_hid_event(struct hid_device *hid, struct hid_field *field, struct return; } + /* + * Ignore out-of-range values as per HID specification, + * section 5.10 and 6.2.25, when NULL state bit is present. + * When it's not, clamp the value to match Microsoft's input + * driver as mentioned in "Required HID usages for digitizers": + * https://msdn.microsoft.com/en-us/library/windows/hardware/dn672278(v=vs.85).asp + * + * The logical_minimum < logical_maximum check is done so that we + * don't unintentionally discard values sent by devices which + * don't specify logical min and max. + */ + if ((field->flags & HID_MAIN_ITEM_VARIABLE) && + field->logical_minimum < field->logical_maximum) { + if (field->flags & HID_MAIN_ITEM_NULL_STATE && + (value < field->logical_minimum || + value > field->logical_maximum)) { + dbg_hid("Ignoring out-of-range value %x\n", value); + return; + } + value = clamp(value, + field->logical_minimum, + field->logical_maximum); + } + switch (usage->hid) { case HID_DG_INVERT: *quirks = value ? (*quirks | HID_QUIRK_INVERT) : (*quirks & ~HID_QUIRK_INVERT); @@ -1431,30 +1455,6 @@ void hidinput_hid_event(struct hid_device *hid, struct hid_field *field, struct break; } - /* - * Ignore out-of-range values as per HID specification, - * section 5.10 and 6.2.25, when NULL state bit is present. - * When it's not, clamp the value to match Microsoft's input - * driver as mentioned in "Required HID usages for digitizers": - * https://msdn.microsoft.com/en-us/library/windows/hardware/dn672278(v=vs.85).asp - * - * The logical_minimum < logical_maximum check is done so that we - * don't unintentionally discard values sent by devices which - * don't specify logical min and max. - */ - if ((field->flags & HID_MAIN_ITEM_VARIABLE) && - (field->logical_minimum < field->logical_maximum)) { - if (field->flags & HID_MAIN_ITEM_NULL_STATE && - (value < field->logical_minimum || - value > field->logical_maximum)) { - dbg_hid("Ignoring out-of-range value %x\n", value); - return; - } - value = clamp(value, - field->logical_minimum, - field->logical_maximum); - } - /* * Ignore reports for absolute data if the data didn't change. This is * not only an optimization but also fixes 'dead' key reports. Some From 22f4b026c3ddd4b26c5baa202bd3ee38feaa2e9a Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Thu, 3 Feb 2022 15:32:21 +0100 Subject: [PATCH 094/137] HID: compute an ordered list of input fields to process This will be used in a later commit: we build a list of input fields (and usage_index) that is ordered based on a usage priority. Changing the usage priority allows to re-order the processed list, meaning that we can enforce some usages to be process before others. For instance, before processing InRange in the HID tablets, we need to know if we are using the eraser (side or button). Enforcing a higher (lower number) priority for Invert allows to force the input stack to process that field before. Signed-off-by: Benjamin Tissoires Reviewed-by: Ping Cheng Acked-by: Peter Hutterer Signed-off-by: Jiri Kosina --- drivers/hid/hid-core.c | 105 ++++++++++++++++++++++++++++++++++++++++- include/linux/hid.h | 10 ++++ 2 files changed, 114 insertions(+), 1 deletion(-) diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c index 34188d7ac0f7..b38528118642 100644 --- a/drivers/hid/hid-core.c +++ b/drivers/hid/hid-core.c @@ -81,6 +81,7 @@ struct hid_report *hid_register_report(struct hid_device *device, report_enum->report_id_hash[id] = report; list_add_tail(&report->list, &report_enum->report_list); + INIT_LIST_HEAD(&report->field_entry_list); return report; } @@ -101,7 +102,7 @@ static struct hid_field *hid_register_field(struct hid_report *report, unsigned field = kzalloc((sizeof(struct hid_field) + usages * sizeof(struct hid_usage) + - 2 * usages * sizeof(unsigned int)), GFP_KERNEL); + 3 * usages * sizeof(unsigned int)), GFP_KERNEL); if (!field) return NULL; @@ -110,6 +111,7 @@ static struct hid_field *hid_register_field(struct hid_report *report, unsigned field->usage = (struct hid_usage *)(field + 1); field->value = (s32 *)(field->usage + usages); field->new_value = (s32 *)(field->value + usages); + field->usages_priorities = (s32 *)(field->new_value + usages); field->report = report; return field; @@ -657,6 +659,8 @@ static void hid_free_report(struct hid_report *report) { unsigned n; + kfree(report->field_entries); + for (n = 0; n < report->maxfield; n++) kfree(report->field[n]); kfree(report); @@ -1667,6 +1671,103 @@ static void hid_process_report(struct hid_device *hid, } } +/* + * Insert a given usage_index in a field in the list + * of processed usages in the report. + * + * The elements of lower priority score are processed + * first. + */ +static void __hid_insert_field_entry(struct hid_device *hid, + struct hid_report *report, + struct hid_field_entry *entry, + struct hid_field *field, + unsigned int usage_index) +{ + struct hid_field_entry *next; + + entry->field = field; + entry->index = usage_index; + entry->priority = field->usages_priorities[usage_index]; + + /* insert the element at the correct position */ + list_for_each_entry(next, + &report->field_entry_list, + list) { + /* + * the priority of our element is strictly higher + * than the next one, insert it before + */ + if (entry->priority > next->priority) { + list_add_tail(&entry->list, &next->list); + return; + } + } + + /* lowest priority score: insert at the end */ + list_add_tail(&entry->list, &report->field_entry_list); +} + +static void hid_report_process_ordering(struct hid_device *hid, + struct hid_report *report) +{ + struct hid_field *field; + struct hid_field_entry *entries; + unsigned int a, u, usages; + unsigned int count = 0; + + /* count the number of individual fields in the report */ + for (a = 0; a < report->maxfield; a++) { + field = report->field[a]; + + if (field->flags & HID_MAIN_ITEM_VARIABLE) + count += field->report_count; + else + count++; + } + + /* allocate the memory to process the fields */ + entries = kcalloc(count, sizeof(*entries), GFP_KERNEL); + if (!entries) + return; + + report->field_entries = entries; + + /* + * walk through all fields in the report and + * store them by priority order in report->field_entry_list + * + * - Var elements are individualized (field + usage_index) + * - Arrays are taken as one, we can not chose an order for them + */ + usages = 0; + for (a = 0; a < report->maxfield; a++) { + field = report->field[a]; + + if (field->flags & HID_MAIN_ITEM_VARIABLE) { + for (u = 0; u < field->report_count; u++) { + __hid_insert_field_entry(hid, report, + &entries[usages], + field, u); + usages++; + } + } else { + __hid_insert_field_entry(hid, report, &entries[usages], + field, 0); + usages++; + } + } +} + +static void hid_process_ordering(struct hid_device *hid) +{ + struct hid_report *report; + struct hid_report_enum *report_enum = &hid->report_enum[HID_INPUT_REPORT]; + + list_for_each_entry(report, &report_enum->report_list, list) + hid_report_process_ordering(hid, report); +} + /* * Output the field into the report. */ @@ -2050,6 +2151,8 @@ int hid_connect(struct hid_device *hdev, unsigned int connect_mask) return -ENODEV; } + hid_process_ordering(hdev); + if ((hdev->claimed & HID_CLAIMED_INPUT) && (connect_mask & HID_CONNECT_FF) && hdev->ff_init) hdev->ff_init(hdev); diff --git a/include/linux/hid.h b/include/linux/hid.h index cf79eb3da465..f25020c0d6b8 100644 --- a/include/linux/hid.h +++ b/include/linux/hid.h @@ -477,6 +477,7 @@ struct hid_field { unsigned report_type; /* (input,output,feature) */ __s32 *value; /* last known value(s) */ __s32 *new_value; /* newly read value(s) */ + __s32 *usages_priorities; /* priority of each usage when reading the report */ __s32 logical_minimum; __s32 logical_maximum; __s32 physical_minimum; @@ -493,13 +494,22 @@ struct hid_field { #define HID_MAX_FIELDS 256 +struct hid_field_entry { + struct list_head list; + struct hid_field *field; + unsigned int index; + __s32 priority; +}; + struct hid_report { struct list_head list; struct list_head hidinput_list; + struct list_head field_entry_list; /* ordered list of input fields */ unsigned int id; /* id of this report */ unsigned int type; /* report type */ unsigned int application; /* application usage for this report */ struct hid_field *field[HID_MAX_FIELDS]; /* fields of the report */ + struct hid_field_entry *field_entries; /* allocated memory of input field_entry */ unsigned maxfield; /* maximum valid field index */ unsigned size; /* size of the report (bits) */ struct hid_device *device; /* associated device */ From bebcc522fbee4bf51a2571378154ef2a4bf14724 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Thu, 3 Feb 2022 15:32:22 +0100 Subject: [PATCH 095/137] HID: core: for input reports, process the usages by priority list Now that we have a list of fields/usages by priority order, walk through that list to process the inputs instead of using the order provided by the manufacturer. Note that this changes the way we update the values in the struct hid_field: Previously, once a field was processed, we updated the new values. Now we need to wait for the entire report to be processed to update the values. I don't think it will be an issue: because we were relying on the device ordering, there were no guarantees to have a field stored before an other. Which is why we introduced .report() in drivers to have those values updated. Signed-off-by: Benjamin Tissoires Reviewed-by: Ping Cheng Acked-by: Peter Hutterer Signed-off-by: Jiri Kosina --- drivers/hid/hid-core.c | 45 +++++++++++++++++++++++++++++++++++------- 1 file changed, 38 insertions(+), 7 deletions(-) diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c index b38528118642..db925794fbe6 100644 --- a/drivers/hid/hid-core.c +++ b/drivers/hid/hid-core.c @@ -1657,17 +1657,48 @@ static void hid_process_report(struct hid_device *hid, int interrupt) { unsigned int a; + struct hid_field_entry *entry; struct hid_field *field; - for (a = 0; a < report->maxfield; a++) { - field = report->field[a]; + /* first retrieve all incoming values in data */ + for (a = 0; a < report->maxfield; a++) + hid_input_fetch_field(hid, field = report->field[a], data); - hid_input_fetch_field(hid, field, data); + if (!list_empty(&report->field_entry_list)) { + /* INPUT_REPORT, we have a priority list of fields */ + list_for_each_entry(entry, + &report->field_entry_list, + list) { + field = entry->field; - if (field->flags & HID_MAIN_ITEM_VARIABLE) - hid_input_var_field(hid, field, interrupt); - else - hid_input_array_field(hid, field, interrupt); + if (field->flags & HID_MAIN_ITEM_VARIABLE) + hid_process_event(hid, + field, + &field->usage[entry->index], + field->new_value[entry->index], + interrupt); + else + hid_input_array_field(hid, field, interrupt); + } + + /* we need to do the memcpy at the end for var items */ + for (a = 0; a < report->maxfield; a++) { + field = report->field[a]; + + if (field->flags & HID_MAIN_ITEM_VARIABLE) + memcpy(field->value, field->new_value, + field->report_count * sizeof(__s32)); + } + } else { + /* FEATURE_REPORT, regular processing */ + for (a = 0; a < report->maxfield; a++) { + field = report->field[a]; + + if (field->flags & HID_MAIN_ITEM_VARIABLE) + hid_input_var_field(hid, field, interrupt); + else + hid_input_array_field(hid, field, interrupt); + } } } From 048cddfd440583a07530774fe20c7d26d7378155 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Thu, 3 Feb 2022 15:32:23 +0100 Subject: [PATCH 096/137] HID: input: enforce Invert usage to be processed before InRange When a device exposes both Invert and InRange, Invert must be processed before InRange. If we keep the order of the device and we process them out of order, InRange will first set BTN_TOOL_PEN, and then Invert will set BTN_TOOL_RUBBER. Userspace knows how to deal with that situation, but fixing it in the kernel is now easier. Signed-off-by: Benjamin Tissoires Reviewed-by: Ping Cheng Acked-by: Peter Hutterer Signed-off-by: Jiri Kosina --- drivers/hid/hid-input.c | 34 ++++++++++++++++++++++++++++++++-- include/linux/hid.h | 4 +++- 2 files changed, 35 insertions(+), 3 deletions(-) diff --git a/drivers/hid/hid-input.c b/drivers/hid/hid-input.c index 8770d9a2b2af..61d91117f4ae 100644 --- a/drivers/hid/hid-input.c +++ b/drivers/hid/hid-input.c @@ -48,6 +48,25 @@ static const struct { __s32 y; } hid_hat_to_axis[] = {{ 0, 0}, { 0,-1}, { 1,-1}, { 1, 0}, { 1, 1}, { 0, 1}, {-1, 1}, {-1, 0}, {-1,-1}}; +/* + * hid-input will convert this list into priorities: + * the first element will have the highest priority + * (the length of the following array) and the last + * element the lowest (1). + * + * hid-input will then shift the priority by 8 bits to leave some space + * in case drivers want to interleave other fields. + * + * If drivers want to add fields before those, hid-input will + * leave out the first 8 bits of the priority value. + * + * This still leaves us 65535 individual priority values. + */ +static const __u32 hidinput_usages_priorities[] = { + HID_DG_INVERT, /* Invert must always come before In Range */ + HID_DG_INRANGE, +}; + #define map_abs(c) hid_map_usage(hidinput, usage, &bit, &max, EV_ABS, (c)) #define map_rel(c) hid_map_usage(hidinput, usage, &bit, &max, EV_REL, (c)) #define map_key(c) hid_map_usage(hidinput, usage, &bit, &max, EV_KEY, (c)) @@ -586,11 +605,12 @@ static bool hidinput_field_in_collection(struct hid_device *device, struct hid_f } static void hidinput_configure_usage(struct hid_input *hidinput, struct hid_field *field, - struct hid_usage *usage) + struct hid_usage *usage, unsigned int usage_index) { struct input_dev *input = hidinput->input; struct hid_device *device = input_get_drvdata(input); int max = 0, code; + unsigned int i = 0; unsigned long *bit = NULL; field->hidinput = hidinput; @@ -608,6 +628,15 @@ static void hidinput_configure_usage(struct hid_input *hidinput, struct hid_fiel goto ignore; } + /* assign a priority based on the static list declared here */ + for (i = 0; i < ARRAY_SIZE(hidinput_usages_priorities); i++) { + if (usage->hid == hidinput_usages_priorities[i]) { + field->usages_priorities[usage_index] = + (ARRAY_SIZE(hidinput_usages_priorities) - i) << 8; + break; + } + } + if (device->driver->input_mapping) { int ret = device->driver->input_mapping(device, hidinput, field, usage, &bit, &max); @@ -1962,7 +1991,8 @@ static inline void hidinput_configure_usages(struct hid_input *hidinput, for (i = 0; i < report->maxfield; i++) for (j = 0; j < report->field[i]->maxusage; j++) hidinput_configure_usage(hidinput, report->field[i], - report->field[i]->usage + j); + report->field[i]->usage + j, + j); } /* diff --git a/include/linux/hid.h b/include/linux/hid.h index f25020c0d6b8..eaad0655b05c 100644 --- a/include/linux/hid.h +++ b/include/linux/hid.h @@ -477,7 +477,9 @@ struct hid_field { unsigned report_type; /* (input,output,feature) */ __s32 *value; /* last known value(s) */ __s32 *new_value; /* newly read value(s) */ - __s32 *usages_priorities; /* priority of each usage when reading the report */ + __s32 *usages_priorities; /* priority of each usage when reading the report + * bits 8-16 are reserved for hid-input usage + */ __s32 logical_minimum; __s32 logical_maximum; __s32 physical_minimum; From 87562fcd134214a68e58d0714b820f2f2da75b1f Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Thu, 3 Feb 2022 15:32:24 +0100 Subject: [PATCH 097/137] HID: input: remove the need for HID_QUIRK_INVERT HID_QUIRK_INVERT is kind of complex to deal with and was bogus. Furthermore, it didn't make sense to use a global per struct hid_device quirk for something dynamic as the current state. Store the current tool information in the report itself, and re-order the processing of the fields to enforce having all the tablet "state" fields before getting to In Range and other input fields. This way, we now have all the information whether a tool is present or not while processing In Range. This new behavior enforces that only one tool gets forwarded to userspace at the same time, and that if either eraser or invert is set, we enforce BTN_TOOL_RUBBER. Note that the release of the previous tool now happens in its own EV_SYN report so userspace doesn't get confused by having 2 tools. These changes are tested in the following hid-tools regression tests: https://gitlab.freedesktop.org/libevdev/hid-tools/-/merge_requests/127 Signed-off-by: Benjamin Tissoires Reviewed-by: Ping Cheng Acked-by: Peter Hutterer Signed-off-by: Jiri Kosina --- drivers/hid/hid-input.c | 98 +++++++++++++++++++++++++++++++++++++---- include/linux/hid.h | 6 ++- 2 files changed, 95 insertions(+), 9 deletions(-) diff --git a/drivers/hid/hid-input.c b/drivers/hid/hid-input.c index 61d91117f4ae..9f8853640648 100644 --- a/drivers/hid/hid-input.c +++ b/drivers/hid/hid-input.c @@ -63,8 +63,11 @@ static const struct { * This still leaves us 65535 individual priority values. */ static const __u32 hidinput_usages_priorities[] = { + HID_DG_ERASER, /* Eraser (eraser touching) must always come before tipswitch */ HID_DG_INVERT, /* Invert must always come before In Range */ - HID_DG_INRANGE, + HID_DG_TIPSWITCH, /* Is the tip of the tool touching? */ + HID_DG_TIPPRESSURE, /* Tip Pressure might emulate tip switch */ + HID_DG_INRANGE, /* In Range needs to come after the other tool states */ }; #define map_abs(c) hid_map_usage(hidinput, usage, &bit, &max, EV_ABS, (c)) @@ -1365,9 +1368,38 @@ static void hidinput_handle_scroll(struct hid_usage *usage, input_event(input, EV_REL, usage->code, hi_res); } +static void hid_report_release_tool(struct hid_report *report, struct input_dev *input, + unsigned int tool) +{ + /* if the given tool is not currently reported, ignore */ + if (!test_bit(tool, input->key)) + return; + + /* + * if the given tool was previously set, release it, + * release any TOUCH and send an EV_SYN + */ + input_event(input, EV_KEY, BTN_TOUCH, 0); + input_event(input, EV_KEY, tool, 0); + input_event(input, EV_SYN, SYN_REPORT, 0); + + report->tool = 0; +} + +static void hid_report_set_tool(struct hid_report *report, struct input_dev *input, + unsigned int new_tool) +{ + if (report->tool != new_tool) + hid_report_release_tool(report, input, report->tool); + + input_event(input, EV_KEY, new_tool, 1); + report->tool = new_tool; +} + void hidinput_hid_event(struct hid_device *hid, struct hid_field *field, struct hid_usage *usage, __s32 value) { struct input_dev *input; + struct hid_report *report = field->report; unsigned *quirks = &hid->quirks; if (!usage->type) @@ -1418,25 +1450,75 @@ void hidinput_hid_event(struct hid_device *hid, struct hid_field *field, struct } switch (usage->hid) { + case HID_DG_ERASER: + report->tool_active |= !!value; + + /* + * if eraser is set, we must enforce BTN_TOOL_RUBBER + * to accommodate for devices not following the spec. + */ + if (value) + hid_report_set_tool(report, input, BTN_TOOL_RUBBER); + else if (report->tool != BTN_TOOL_RUBBER) + /* value is off, tool is not rubber, ignore */ + return; + + /* let hid-input set BTN_TOUCH */ + break; + case HID_DG_INVERT: - *quirks = value ? (*quirks | HID_QUIRK_INVERT) : (*quirks & ~HID_QUIRK_INVERT); + report->tool_active |= !!value; + + /* + * If invert is set, we store BTN_TOOL_RUBBER. + */ + if (value) + hid_report_set_tool(report, input, BTN_TOOL_RUBBER); + else if (!report->tool_active) + /* tool_active not set means Invert and Eraser are not set */ + hid_report_release_tool(report, input, BTN_TOOL_RUBBER); + + /* no further processing */ return; case HID_DG_INRANGE: - if (value) { - input_event(input, usage->type, (*quirks & HID_QUIRK_INVERT) ? BTN_TOOL_RUBBER : usage->code, 1); - return; + report->tool_active |= !!value; + + if (report->tool_active) { + /* + * if tool is not set but is marked as active, + * assume ours + */ + if (!report->tool) + hid_report_set_tool(report, input, usage->code); + } else { + hid_report_release_tool(report, input, usage->code); } - input_event(input, usage->type, usage->code, 0); - input_event(input, usage->type, BTN_TOOL_RUBBER, 0); + + /* reset tool_active for the next event */ + report->tool_active = false; + + /* no further processing */ return; + case HID_DG_TIPSWITCH: + report->tool_active |= !!value; + + /* if tool is set to RUBBER we should ignore the current value */ + if (report->tool == BTN_TOOL_RUBBER) + return; + + break; + case HID_DG_TIPPRESSURE: if (*quirks & HID_QUIRK_NOTOUCH) { int a = field->logical_minimum; int b = field->logical_maximum; - input_event(input, EV_KEY, BTN_TOUCH, value > a + ((b - a) >> 3)); + if (value > a + ((b - a) >> 3)) { + input_event(input, EV_KEY, BTN_TOUCH, 1); + report->tool_active = true; + } } break; diff --git a/include/linux/hid.h b/include/linux/hid.h index eaad0655b05c..feb8df61168f 100644 --- a/include/linux/hid.h +++ b/include/linux/hid.h @@ -347,7 +347,7 @@ struct hid_item { */ #define MAX_USBHID_BOOT_QUIRKS 4 -#define HID_QUIRK_INVERT BIT(0) +/* BIT(0) reserved for backward compatibility, was HID_QUIRK_INVERT */ #define HID_QUIRK_NOTOUCH BIT(1) #define HID_QUIRK_IGNORE BIT(2) #define HID_QUIRK_NOGET BIT(3) @@ -515,6 +515,10 @@ struct hid_report { unsigned maxfield; /* maximum valid field index */ unsigned size; /* size of the report (bits) */ struct hid_device *device; /* associated device */ + + /* tool related state */ + bool tool_active; /* whether the current tool is active */ + unsigned int tool; /* BTN_TOOL_* */ }; #define HID_MAX_IDS 256 From 5c20000a4756f57c824e3045c978ef19136a676d Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Thu, 3 Feb 2022 15:32:25 +0100 Subject: [PATCH 098/137] HID: input: accommodate priorities for slotted devices Multitouch devices in hybrid mode are reporting multiple times the same collection. We should accommodate for this in our handling of priorities by defining the slots they belong to. Signed-off-by: Benjamin Tissoires Reviewed-by: Ping Cheng Acked-by: Peter Hutterer Signed-off-by: Jiri Kosina --- drivers/hid/hid-input.c | 103 ++++++++++++++++++++++++++++++++++++---- include/linux/hid.h | 1 + 2 files changed, 96 insertions(+), 8 deletions(-) diff --git a/drivers/hid/hid-input.c b/drivers/hid/hid-input.c index 9f8853640648..56d4e91c4750 100644 --- a/drivers/hid/hid-input.c +++ b/drivers/hid/hid-input.c @@ -48,6 +48,16 @@ static const struct { __s32 y; } hid_hat_to_axis[] = {{ 0, 0}, { 0,-1}, { 1,-1}, { 1, 0}, { 1, 1}, { 0, 1}, {-1, 1}, {-1, 0}, {-1,-1}}; +struct usage_priority { + __u32 usage; /* the HID usage associated */ + bool global; /* we assume all usages to be slotted, + * unless global + */ + unsigned int slot_overwrite; /* for globals: allows to set the usage + * before or after the slots + */ +}; + /* * hid-input will convert this list into priorities: * the first element will have the highest priority @@ -57,17 +67,30 @@ static const struct { * hid-input will then shift the priority by 8 bits to leave some space * in case drivers want to interleave other fields. * + * To accommodate slotted devices, the slot priority is + * defined in the next 8 bits (defined by 0xff - slot). + * * If drivers want to add fields before those, hid-input will * leave out the first 8 bits of the priority value. * * This still leaves us 65535 individual priority values. */ -static const __u32 hidinput_usages_priorities[] = { - HID_DG_ERASER, /* Eraser (eraser touching) must always come before tipswitch */ - HID_DG_INVERT, /* Invert must always come before In Range */ - HID_DG_TIPSWITCH, /* Is the tip of the tool touching? */ - HID_DG_TIPPRESSURE, /* Tip Pressure might emulate tip switch */ - HID_DG_INRANGE, /* In Range needs to come after the other tool states */ +static const struct usage_priority hidinput_usages_priorities[] = { + { /* Eraser (eraser touching) must always come before tipswitch */ + .usage = HID_DG_ERASER, + }, + { /* Invert must always come before In Range */ + .usage = HID_DG_INVERT, + }, + { /* Is the tip of the tool touching? */ + .usage = HID_DG_TIPSWITCH, + }, + { /* Tip Pressure might emulate tip switch */ + .usage = HID_DG_TIPPRESSURE, + }, + { /* In Range needs to come after the other tool states */ + .usage = HID_DG_INRANGE, + }, }; #define map_abs(c) hid_map_usage(hidinput, usage, &bit, &max, EV_ABS, (c)) @@ -612,6 +635,7 @@ static void hidinput_configure_usage(struct hid_input *hidinput, struct hid_fiel { struct input_dev *input = hidinput->input; struct hid_device *device = input_get_drvdata(input); + const struct usage_priority *usage_priority = NULL; int max = 0, code; unsigned int i = 0; unsigned long *bit = NULL; @@ -633,13 +657,26 @@ static void hidinput_configure_usage(struct hid_input *hidinput, struct hid_fiel /* assign a priority based on the static list declared here */ for (i = 0; i < ARRAY_SIZE(hidinput_usages_priorities); i++) { - if (usage->hid == hidinput_usages_priorities[i]) { + if (usage->hid == hidinput_usages_priorities[i].usage) { + usage_priority = &hidinput_usages_priorities[i]; + field->usages_priorities[usage_index] = (ARRAY_SIZE(hidinput_usages_priorities) - i) << 8; break; } } + /* + * For slotted devices, we need to also add the slot index + * in the priority. + */ + if (usage_priority && usage_priority->global) + field->usages_priorities[usage_index] |= + usage_priority->slot_overwrite; + else + field->usages_priorities[usage_index] |= + (0xff - field->slot_idx) << 16; + if (device->driver->input_mapping) { int ret = device->driver->input_mapping(device, hidinput, field, usage, &bit, &max); @@ -2068,7 +2105,57 @@ static struct hid_input *hidinput_match_application(struct hid_report *report) static inline void hidinput_configure_usages(struct hid_input *hidinput, struct hid_report *report) { - int i, j; + int i, j, k; + int first_field_index = 0; + int slot_collection_index = -1; + int prev_collection_index = -1; + unsigned int slot_idx = 0; + struct hid_field *field; + + /* + * First tag all the fields that are part of a slot, + * a slot needs to have one Contact ID in the collection + */ + for (i = 0; i < report->maxfield; i++) { + field = report->field[i]; + + /* ignore fields without usage */ + if (field->maxusage < 1) + continue; + + /* + * janitoring when collection_index changes + */ + if (prev_collection_index != field->usage->collection_index) { + prev_collection_index = field->usage->collection_index; + first_field_index = i; + } + + /* + * if we already found a Contact ID in the collection, + * tag and continue to the next. + */ + if (slot_collection_index == field->usage->collection_index) { + field->slot_idx = slot_idx; + continue; + } + + /* check if the current field has Contact ID */ + for (j = 0; j < field->maxusage; j++) { + if (field->usage[j].hid == HID_DG_CONTACTID) { + slot_collection_index = field->usage->collection_index; + slot_idx++; + + /* + * mark all previous fields and this one in the + * current collection to be slotted. + */ + for (k = first_field_index; k <= i; k++) + report->field[k]->slot_idx = slot_idx; + break; + } + } + } for (i = 0; i < report->maxfield; i++) for (j = 0; j < report->field[i]->maxusage; j++) diff --git a/include/linux/hid.h b/include/linux/hid.h index feb8df61168f..4363a63b9775 100644 --- a/include/linux/hid.h +++ b/include/linux/hid.h @@ -492,6 +492,7 @@ struct hid_field { /* hidinput data */ struct hid_input *hidinput; /* associated input structure */ __u16 dpad; /* dpad input code */ + unsigned int slot_idx; /* slot index in a report */ }; #define HID_MAX_FIELDS 256 From 42764f97e6ebf1ff518c83e4a36395fddf7a6085 Mon Sep 17 00:00:00 2001 From: Benjamin Tissoires Date: Thu, 3 Feb 2022 15:32:26 +0100 Subject: [PATCH 099/137] Input: docs: add more details on the use of BTN_TOOL The HID core stack used to be very relaxed considering the BTN_TOOL_* usage. With the recent commits, we should now enforce to have only one tool at a time, meaning that we can now express that requirement in the docs. Signed-off-by: Benjamin Tissoires Reviewed-by: Ping Cheng Acked-by: Peter Hutterer Signed-off-by: Jiri Kosina --- Documentation/input/event-codes.rst | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Documentation/input/event-codes.rst b/Documentation/input/event-codes.rst index b24ae7d292cc..8741d390b184 100644 --- a/Documentation/input/event-codes.rst +++ b/Documentation/input/event-codes.rst @@ -137,7 +137,11 @@ A few EV_KEY codes have special meanings: code should be set to a value of 1. When the tool is no longer interacting with the input device, the BTN_TOOL_ code should be reset to 0. All trackpads, tablets, and touchscreens should use at least one BTN_TOOL_ - code when events are generated. + code when events are generated. Likewise all trackpads, tablets, and + touchscreens should export only one BTN_TOOL_ at a time. To not break + existing userspace, it is recommended to not switch tool in one EV_SYN frame + but first emitting the old BTN_TOOL_ at 0, then emit one SYN_REPORT + and then set the new BTN_TOOL_ at 1. * BTN_TOUCH: From 32370191c0851da069d242f581cbe2fdb80040cb Mon Sep 17 00:00:00 2001 From: Mario Limonciello Date: Wed, 23 Feb 2022 11:52:37 -0600 Subject: [PATCH 100/137] platform/x86: amd-pmc: Set QOS during suspend on CZN w/ timer wakeup commit 59348401ebed ("platform/x86: amd-pmc: Add special handling for timer based S0i3 wakeup") adds support for using another platform timer in lieu of the RTC which doesn't work properly on some systems. This path was validated and worked well before submission. During the 5.16-rc1 merge window other patches were merged that caused this to stop working properly. When this feature was used with 5.16-rc1 or later some OEM laptops with the matching firmware requirements from that commit would shutdown instead of program a timer based wakeup. This was bisected to commit 8d89835b0467 ("PM: suspend: Do not pause cpuidle in the suspend-to-idle path"). This wasn't supposed to cause any negative impacts and also tested well on both Intel and ARM platforms. However this changed the semantics of when CPUs are allowed to be in the deepest state. For the AMD systems in question it appears this causes a firmware crash for timer based wakeup. It's hypothesized to be caused by the `amd-pmc` driver sending `OS_HINT` and all the CPUs going into a deep state while the timer is still being programmed. It's likely a firmware bug, but to avoid it don't allow setting CPUs into the deepest state while using CZN timer wakeup path. If later it's discovered that this also occurs from "regular" suspends without a timer as well or on other silicon, this may be later expanded to run in the suspend path for more scenarios. Cc: stable@vger.kernel.org # 5.16+ Suggested-by: Rafael J. Wysocki Link: https://lore.kernel.org/linux-acpi/BL1PR12MB51570F5BD05980A0DCA1F3F4E23A9@BL1PR12MB5157.namprd12.prod.outlook.com/T/#mee35f39c41a04b624700ab2621c795367f19c90e Fixes: 8d89835b0467 ("PM: suspend: Do not pause cpuidle in the suspend-to-idle path") Fixes: 23f62d7ab25b ("PM: sleep: Pause cpuidle later and resume it earlier during system transitions") Fixes: 59348401ebed ("platform/x86: amd-pmc: Add special handling for timer based S0i3 wakeup") Reviewed-by: Rafael J. Wysocki Signed-off-by: Mario Limonciello Link: https://lore.kernel.org/r/20220223175237.6209-1-mario.limonciello@amd.com Reviewed-by: Hans de Goede Signed-off-by: Hans de Goede --- drivers/platform/x86/amd-pmc.c | 42 ++++++++++++++++++++++++++-------- 1 file changed, 33 insertions(+), 9 deletions(-) diff --git a/drivers/platform/x86/amd-pmc.c b/drivers/platform/x86/amd-pmc.c index 69fdbb0d9f45..425a86108f75 100644 --- a/drivers/platform/x86/amd-pmc.c +++ b/drivers/platform/x86/amd-pmc.c @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -95,6 +96,9 @@ #define PMC_MSG_DELAY_MIN_US 50 #define RESPONSE_REGISTER_LOOP_MAX 20000 +/* QoS request for letting CPUs in idle states, but not the deepest */ +#define AMD_PMC_MAX_IDLE_STATE_LATENCY 3 + #define SOC_SUBSYSTEM_IP_MAX 12 #define DELAY_MIN_US 2000 #define DELAY_MAX_US 3000 @@ -149,6 +153,7 @@ struct amd_pmc_dev { struct device *dev; struct pci_dev *rdev; struct mutex lock; /* generic mutex lock */ + struct pm_qos_request amd_pmc_pm_qos_req; #if IS_ENABLED(CONFIG_DEBUG_FS) struct dentry *dbgfs_dir; #endif /* CONFIG_DEBUG_FS */ @@ -603,6 +608,14 @@ static int amd_pmc_verify_czn_rtc(struct amd_pmc_dev *pdev, u32 *arg) rc = rtc_alarm_irq_enable(rtc_device, 0); dev_dbg(pdev->dev, "wakeup timer programmed for %lld seconds\n", duration); + /* + * Prevent CPUs from getting into deep idle states while sending OS_HINT + * which is otherwise generally safe to send when at least one of the CPUs + * is not in deep idle states. + */ + cpu_latency_qos_update_request(&pdev->amd_pmc_pm_qos_req, AMD_PMC_MAX_IDLE_STATE_LATENCY); + wake_up_all_idle_cpus(); + return rc; } @@ -620,24 +633,31 @@ static int __maybe_unused amd_pmc_suspend(struct device *dev) /* Activate CZN specific RTC functionality */ if (pdev->cpu_id == AMD_CPU_ID_CZN) { rc = amd_pmc_verify_czn_rtc(pdev, &arg); - if (rc < 0) - return rc; + if (rc) + goto fail; } /* Dump the IdleMask before we send hint to SMU */ amd_pmc_idlemask_read(pdev, dev, NULL); msg = amd_pmc_get_os_hint(pdev); rc = amd_pmc_send_cmd(pdev, arg, NULL, msg, 0); - if (rc) + if (rc) { dev_err(pdev->dev, "suspend failed\n"); + goto fail; + } if (enable_stb) rc = amd_pmc_write_stb(pdev, AMD_PMC_STB_PREDEF); - if (rc) { + if (rc) { dev_err(pdev->dev, "error writing to STB\n"); - return rc; + goto fail; } + return 0; +fail: + if (pdev->cpu_id == AMD_CPU_ID_CZN) + cpu_latency_qos_update_request(&pdev->amd_pmc_pm_qos_req, + PM_QOS_DEFAULT_VALUE); return rc; } @@ -661,12 +681,15 @@ static int __maybe_unused amd_pmc_resume(struct device *dev) /* Write data incremented by 1 to distinguish in stb_read */ if (enable_stb) rc = amd_pmc_write_stb(pdev, AMD_PMC_STB_PREDEF + 1); - if (rc) { + if (rc) dev_err(pdev->dev, "error writing to STB\n"); - return rc; - } - return 0; + /* Restore the QoS request back to defaults if it was set */ + if (pdev->cpu_id == AMD_CPU_ID_CZN) + cpu_latency_qos_update_request(&pdev->amd_pmc_pm_qos_req, + PM_QOS_DEFAULT_VALUE); + + return rc; } static const struct dev_pm_ops amd_pmc_pm_ops = { @@ -838,6 +861,7 @@ static int amd_pmc_probe(struct platform_device *pdev) amd_pmc_get_smu_version(dev); platform_set_drvdata(pdev, dev); amd_pmc_dbgfs_register(dev); + cpu_latency_qos_add_request(&dev->amd_pmc_pm_qos_req, PM_QOS_DEFAULT_VALUE); return 0; err_pci_dev_put: From f094399fae9cde11c3f98565eb150522aa7c15ab Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Thu, 24 Feb 2022 11:18:48 +0100 Subject: [PATCH 101/137] surface: surface3_power: Fix battery readings on batteries without a serial number The battery on the 2nd hand Surface 3 which I recently bought appears to not have a serial number programmed in. This results in any I2C reads from the registers containing the serial number failing with an I2C NACK. This was causing mshw0011_bix() to fail causing the battery readings to not work at all. Ignore EREMOTEIO (I2C NACK) errors when retrieving the serial number and continue with an empty serial number to fix this. Fixes: b1f81b496b0d ("platform/x86: surface3_power: MSHW0011 rev-eng implementation") BugLink: https://github.com/linux-surface/linux-surface/issues/608 Reviewed-by: Benjamin Tissoires Reviewed-by: Maximilian Luz Signed-off-by: Hans de Goede Link: https://lore.kernel.org/r/20220224101848.7219-1-hdegoede@redhat.com --- drivers/platform/surface/surface3_power.c | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/drivers/platform/surface/surface3_power.c b/drivers/platform/surface/surface3_power.c index abac3eec565e..444ec81ba02d 100644 --- a/drivers/platform/surface/surface3_power.c +++ b/drivers/platform/surface/surface3_power.c @@ -232,14 +232,21 @@ static int mshw0011_bix(struct mshw0011_data *cdata, struct bix *bix) } bix->last_full_charg_capacity = ret; - /* get serial number */ + /* + * Get serial number, on some devices (with unofficial replacement + * battery?) reading any of the serial number range addresses gets + * nacked in this case just leave the serial number empty. + */ ret = i2c_smbus_read_i2c_block_data(client, MSHW0011_BAT0_REG_SERIAL_NO, sizeof(buf), buf); - if (ret != sizeof(buf)) { + if (ret == -EREMOTEIO) { + /* no serial number available */ + } else if (ret != sizeof(buf)) { dev_err(&client->dev, "Error reading serial no: %d\n", ret); return ret; + } else { + snprintf(bix->serial, ARRAY_SIZE(bix->serial), "%3pE%6pE", buf + 7, buf); } - snprintf(bix->serial, ARRAY_SIZE(bix->serial), "%3pE%6pE", buf + 7, buf); /* get cycle count */ ret = i2c_smbus_read_word_data(client, MSHW0011_BAT0_REG_CYCLE_CNT); From 91f410aa679a035e7abdff47daca4418c384c770 Mon Sep 17 00:00:00 2001 From: Suma Hegde Date: Tue, 22 Feb 2022 10:35:00 +0530 Subject: [PATCH 102/137] platform/x86: Add AMD system management interface Recent Fam19h EPYC server line of processors from AMD support system management functionality via HSMP (Host System Management Port) interface. The Host System Management Port (HSMP) is an interface to provide OS-level software with access to system management functions via a set of mailbox registers. More details on the interface can be found in chapter "7 Host System Management Port (HSMP)" of the following PPR https://www.amd.com/system/files/TechDocs/55898_B1_pub_0.50.zip This patch adds new amd_hsmp module under the drivers/platforms/x86/ which creates miscdevice with an IOCTL interface to the user space. /dev/hsmp is for running the hsmp mailbox commands. Signed-off-by: Suma Hegde Signed-off-by: Naveen Krishna Chatradhi Reviewed-by: Carlos Bilbao Acked-by: Song Liu Reviewed-by: Nathan Fontenot Link: https://lore.kernel.org/r/20220222050501.18789-1-nchatrad@amd.com Reviewed-by: Hans de Goede Signed-off-by: Hans de Goede --- .../userspace-api/ioctl/ioctl-number.rst | 2 + MAINTAINERS | 10 + arch/x86/include/asm/amd_hsmp.h | 16 + arch/x86/include/uapi/asm/amd_hsmp.h | 203 +++++++++ drivers/platform/x86/Kconfig | 13 + drivers/platform/x86/Makefile | 1 + drivers/platform/x86/amd_hsmp.c | 425 ++++++++++++++++++ 7 files changed, 670 insertions(+) create mode 100644 arch/x86/include/asm/amd_hsmp.h create mode 100644 arch/x86/include/uapi/asm/amd_hsmp.h create mode 100644 drivers/platform/x86/amd_hsmp.c diff --git a/Documentation/userspace-api/ioctl/ioctl-number.rst b/Documentation/userspace-api/ioctl/ioctl-number.rst index 687efcf245c1..663e316d320c 100644 --- a/Documentation/userspace-api/ioctl/ioctl-number.rst +++ b/Documentation/userspace-api/ioctl/ioctl-number.rst @@ -372,6 +372,8 @@ Code Seq# Include File Comments 0xF6 all LTTng Linux Trace Toolkit Next Generation +0xF8 all arch/x86/include/uapi/asm/amd_hsmp.h AMD HSMP EPYC system management interface driver + 0xFD all linux/dm-ioctl.h 0xFE all linux/isst_if.h ==== ===== ======================================================= ================================================================ diff --git a/MAINTAINERS b/MAINTAINERS index 136f817428cf..a419a6938786 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -988,6 +988,16 @@ L: platform-driver-x86@vger.kernel.org S: Maintained F: drivers/platform/x86/amd-pmc.* +AMD HSMP DRIVER +M: Naveen Krishna Chatradhi +R: Carlos Bilbao +L: platform-driver-x86@vger.kernel.org +S: Maintained +F: Documentation/x86/amd_hsmp.rst +F: arch/x86/include/asm/amd_hsmp.h +F: arch/x86/include/uapi/asm/amd_hsmp.h +F: drivers/platform/x86/amd_hsmp.c + AMD POWERPLAY AND SWSMU M: Evan Quan L: amd-gfx@lists.freedesktop.org diff --git a/arch/x86/include/asm/amd_hsmp.h b/arch/x86/include/asm/amd_hsmp.h new file mode 100644 index 000000000000..03c2ce3edaf5 --- /dev/null +++ b/arch/x86/include/asm/amd_hsmp.h @@ -0,0 +1,16 @@ +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ + +#ifndef _ASM_X86_AMD_HSMP_H_ +#define _ASM_X86_AMD_HSMP_H_ + +#include + +#if IS_ENABLED(CONFIG_AMD_HSMP) +int hsmp_send_message(struct hsmp_message *msg); +#else +static inline int hsmp_send_message(struct hsmp_message *msg) +{ + return -ENODEV; +} +#endif +#endif /*_ASM_X86_AMD_HSMP_H_*/ diff --git a/arch/x86/include/uapi/asm/amd_hsmp.h b/arch/x86/include/uapi/asm/amd_hsmp.h new file mode 100644 index 000000000000..7ee7ba0d63a3 --- /dev/null +++ b/arch/x86/include/uapi/asm/amd_hsmp.h @@ -0,0 +1,203 @@ +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ + +#ifndef _UAPI_ASM_X86_AMD_HSMP_H_ +#define _UAPI_ASM_X86_AMD_HSMP_H_ + +#include + +#pragma pack(4) + +#define HSMP_MAX_MSG_LEN 8 + +/* + * HSMP Messages supported + */ +enum hsmp_message_ids { + HSMP_TEST = 1, /* 01h Increments input value by 1 */ + HSMP_GET_SMU_VER, /* 02h SMU FW version */ + HSMP_GET_PROTO_VER, /* 03h HSMP interface version */ + HSMP_GET_SOCKET_POWER, /* 04h average package power consumption */ + HSMP_SET_SOCKET_POWER_LIMIT, /* 05h Set the socket power limit */ + HSMP_GET_SOCKET_POWER_LIMIT, /* 06h Get current socket power limit */ + HSMP_GET_SOCKET_POWER_LIMIT_MAX,/* 07h Get maximum socket power value */ + HSMP_SET_BOOST_LIMIT, /* 08h Set a core maximum frequency limit */ + HSMP_SET_BOOST_LIMIT_SOCKET, /* 09h Set socket maximum frequency level */ + HSMP_GET_BOOST_LIMIT, /* 0Ah Get current frequency limit */ + HSMP_GET_PROC_HOT, /* 0Bh Get PROCHOT status */ + HSMP_SET_XGMI_LINK_WIDTH, /* 0Ch Set max and min width of xGMI Link */ + HSMP_SET_DF_PSTATE, /* 0Dh Alter APEnable/Disable messages behavior */ + HSMP_SET_AUTO_DF_PSTATE, /* 0Eh Enable DF P-State Performance Boost algorithm */ + HSMP_GET_FCLK_MCLK, /* 0Fh Get FCLK and MEMCLK for current socket */ + HSMP_GET_CCLK_THROTTLE_LIMIT, /* 10h Get CCLK frequency limit in socket */ + HSMP_GET_C0_PERCENT, /* 11h Get average C0 residency in socket */ + HSMP_SET_NBIO_DPM_LEVEL, /* 12h Set max/min LCLK DPM Level for a given NBIO */ + /* 13h Reserved */ + HSMP_GET_DDR_BANDWIDTH = 0x14, /* 14h Get theoretical maximum and current DDR Bandwidth */ + HSMP_GET_TEMP_MONITOR, /* 15h Get per-DIMM temperature and refresh rates */ + HSMP_MSG_ID_MAX, +}; + +struct hsmp_message { + __u32 msg_id; /* Message ID */ + __u16 num_args; /* Number of input argument words in message */ + __u16 response_sz; /* Number of expected output/response words */ + __u32 args[HSMP_MAX_MSG_LEN]; /* argument/response buffer */ + __u16 sock_ind; /* socket number */ +}; + +enum hsmp_msg_type { + HSMP_RSVD = -1, + HSMP_SET = 0, + HSMP_GET = 1, +}; + +struct hsmp_msg_desc { + int num_args; + int response_sz; + enum hsmp_msg_type type; +}; + +/* + * User may use these comments as reference, please find the + * supported list of messages and message definition in the + * HSMP chapter of respective family/model PPR. + * + * Not supported messages would return -ENOMSG. + */ +static const struct hsmp_msg_desc hsmp_msg_desc_table[] = { + /* RESERVED */ + {0, 0, HSMP_RSVD}, + + /* + * HSMP_TEST, num_args = 1, response_sz = 1 + * input: args[0] = xx + * output: args[0] = xx + 1 + */ + {1, 1, HSMP_GET}, + + /* + * HSMP_GET_SMU_VER, num_args = 0, response_sz = 1 + * output: args[0] = smu fw ver + */ + {0, 1, HSMP_GET}, + + /* + * HSMP_GET_PROTO_VER, num_args = 0, response_sz = 1 + * output: args[0] = proto version + */ + {0, 1, HSMP_GET}, + + /* + * HSMP_GET_SOCKET_POWER, num_args = 0, response_sz = 1 + * output: args[0] = socket power in mWatts + */ + {0, 1, HSMP_GET}, + + /* + * HSMP_SET_SOCKET_POWER_LIMIT, num_args = 1, response_sz = 0 + * input: args[0] = power limit value in mWatts + */ + {1, 0, HSMP_SET}, + + /* + * HSMP_GET_SOCKET_POWER_LIMIT, num_args = 0, response_sz = 1 + * output: args[0] = socket power limit value in mWatts + */ + {0, 1, HSMP_GET}, + + /* + * HSMP_GET_SOCKET_POWER_LIMIT_MAX, num_args = 0, response_sz = 1 + * output: args[0] = maximuam socket power limit in mWatts + */ + {0, 1, HSMP_GET}, + + /* + * HSMP_SET_BOOST_LIMIT, num_args = 1, response_sz = 0 + * input: args[0] = apic id[31:16] + boost limit value in MHz[15:0] + */ + {1, 0, HSMP_SET}, + + /* + * HSMP_SET_BOOST_LIMIT_SOCKET, num_args = 1, response_sz = 0 + * input: args[0] = boost limit value in MHz + */ + {1, 0, HSMP_SET}, + + /* + * HSMP_GET_BOOST_LIMIT, num_args = 1, response_sz = 1 + * input: args[0] = apic id + * output: args[0] = boost limit value in MHz + */ + {1, 1, HSMP_GET}, + + /* + * HSMP_GET_PROC_HOT, num_args = 0, response_sz = 1 + * output: args[0] = proc hot status + */ + {0, 1, HSMP_GET}, + + /* + * HSMP_SET_XGMI_LINK_WIDTH, num_args = 1, response_sz = 0 + * input: args[0] = min link width[15:8] + max link width[7:0] + */ + {1, 0, HSMP_SET}, + + /* + * HSMP_SET_DF_PSTATE, num_args = 1, response_sz = 0 + * input: args[0] = df pstate[7:0] + */ + {1, 0, HSMP_SET}, + + /* HSMP_SET_AUTO_DF_PSTATE, num_args = 0, response_sz = 0 */ + {0, 0, HSMP_SET}, + + /* + * HSMP_GET_FCLK_MCLK, num_args = 0, response_sz = 2 + * output: args[0] = fclk in MHz, args[1] = mclk in MHz + */ + {0, 2, HSMP_GET}, + + /* + * HSMP_GET_CCLK_THROTTLE_LIMIT, num_args = 0, response_sz = 1 + * output: args[0] = core clock in MHz + */ + {0, 1, HSMP_GET}, + + /* + * HSMP_GET_C0_PERCENT, num_args = 0, response_sz = 1 + * output: args[0] = average c0 residency + */ + {0, 1, HSMP_GET}, + + /* + * HSMP_SET_NBIO_DPM_LEVEL, num_args = 1, response_sz = 0 + * input: args[0] = nbioid[23:16] + max dpm level[15:8] + min dpm level[7:0] + */ + {1, 0, HSMP_SET}, + + /* RESERVED message */ + {0, 0, HSMP_RSVD}, + + /* + * HSMP_GET_DDR_BANDWIDTH, num_args = 0, response_sz = 1 + * output: args[0] = max bw in Gbps[31:20] + utilised bw in Gbps[19:8] + + * bw in percentage[7:0] + */ + {0, 1, HSMP_GET}, + + /* + * HSMP_GET_TEMP_MONITOR, num_args = 0, response_sz = 1 + * output: args[0] = temperature in degree celsius. [15:8] integer part + + * [7:5] fractional part + */ + {0, 1, HSMP_GET}, +}; + +/* Reset to default packing */ +#pragma pack() + +/* Define unique ioctl command for hsmp msgs using generic _IOWR */ +#define HSMP_BASE_IOCTL_NR 0xF8 +#define HSMP_IOCTL_CMD _IOWR(HSMP_BASE_IOCTL_NR, 0, struct hsmp_message) + +#endif /*_ASM_X86_AMD_HSMP_H_*/ diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index 09f4ab013dfa..0d51011d5d90 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -210,6 +210,19 @@ config AMD_PMC If you choose to compile this driver as a module the module will be called amd-pmc. +config AMD_HSMP + tristate "AMD HSMP Driver" + depends on AMD_NB && X86_64 + help + The driver provides a way for user space tools to monitor and manage + system management functionality on EPYC server CPUs from AMD. + + Host System Management Port (HSMP) interface is a mailbox interface + between the x86 core and the System Management Unit (SMU) firmware. + + If you choose to compile this driver as a module the module will be + called amd_hsmp. + config ADV_SWBUTTON tristate "Advantech ACPI Software Button Driver" depends on ACPI && INPUT diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile index 9527088bba7f..fe4d4c8970ef 100644 --- a/drivers/platform/x86/Makefile +++ b/drivers/platform/x86/Makefile @@ -24,6 +24,7 @@ obj-$(CONFIG_ACER_WMI) += acer-wmi.o # AMD obj-$(CONFIG_AMD_PMC) += amd-pmc.o +obj-$(CONFIG_AMD_HSMP) += amd_hsmp.o # Advantech obj-$(CONFIG_ADV_SWBUTTON) += adv_swbutton.o diff --git a/drivers/platform/x86/amd_hsmp.c b/drivers/platform/x86/amd_hsmp.c new file mode 100644 index 000000000000..a0c54b838c11 --- /dev/null +++ b/drivers/platform/x86/amd_hsmp.c @@ -0,0 +1,425 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * AMD HSMP Platform Driver + * Copyright (c) 2022, AMD. + * All Rights Reserved. + * + * This file provides a device implementation for HSMP interface + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DRIVER_NAME "amd_hsmp" +#define DRIVER_VERSION "1.0" + +/* HSMP Status / Error codes */ +#define HSMP_STATUS_NOT_READY 0x00 +#define HSMP_STATUS_OK 0x01 +#define HSMP_ERR_INVALID_MSG 0xFE +#define HSMP_ERR_INVALID_INPUT 0xFF + +/* Timeout in millsec */ +#define HSMP_MSG_TIMEOUT 100 +#define HSMP_SHORT_SLEEP 1 + +#define HSMP_WR true +#define HSMP_RD false + +/* + * To access specific HSMP mailbox register, s/w writes the SMN address of HSMP mailbox + * register into the SMN_INDEX register, and reads/writes the SMN_DATA reg. + * Below are required SMN address for HSMP Mailbox register offsets in SMU address space + */ +#define SMN_HSMP_MSG_ID 0x3B10534 +#define SMN_HSMP_MSG_RESP 0x3B10980 +#define SMN_HSMP_MSG_DATA 0x3B109E0 + +#define HSMP_INDEX_REG 0xc4 +#define HSMP_DATA_REG 0xc8 + +static struct semaphore *hsmp_sem; + +static struct miscdevice hsmp_device; + +static int amd_hsmp_rdwr(struct pci_dev *root, u32 address, + u32 *value, bool write) +{ + int ret; + + ret = pci_write_config_dword(root, HSMP_INDEX_REG, address); + if (ret) + return ret; + + ret = (write ? pci_write_config_dword(root, HSMP_DATA_REG, *value) + : pci_read_config_dword(root, HSMP_DATA_REG, value)); + + return ret; +} + +/* + * Send a message to the HSMP port via PCI-e config space registers. + * + * The caller is expected to zero out any unused arguments. + * If a response is expected, the number of response words should be greater than 0. + * + * Returns 0 for success and populates the requested number of arguments. + * Returns a negative error code for failure. + */ +static int __hsmp_send_message(struct pci_dev *root, struct hsmp_message *msg) +{ + unsigned long timeout, short_sleep; + u32 mbox_status; + u32 index; + int ret; + + /* Clear the status register */ + mbox_status = HSMP_STATUS_NOT_READY; + ret = amd_hsmp_rdwr(root, SMN_HSMP_MSG_RESP, &mbox_status, HSMP_WR); + if (ret) { + pr_err("Error %d clearing mailbox status register\n", ret); + return ret; + } + + index = 0; + /* Write any message arguments */ + while (index < msg->num_args) { + ret = amd_hsmp_rdwr(root, SMN_HSMP_MSG_DATA + (index << 2), + &msg->args[index], HSMP_WR); + if (ret) { + pr_err("Error %d writing message argument %d\n", ret, index); + return ret; + } + index++; + } + + /* Write the message ID which starts the operation */ + ret = amd_hsmp_rdwr(root, SMN_HSMP_MSG_ID, &msg->msg_id, HSMP_WR); + if (ret) { + pr_err("Error %d writing message ID %u\n", ret, msg->msg_id); + return ret; + } + + /* + * Depending on when the trigger write completes relative to the SMU + * firmware 1 ms cycle, the operation may take from tens of us to 1 ms + * to complete. Some operations may take more. Therefore we will try + * a few short duration sleeps and switch to long sleeps if we don't + * succeed quickly. + */ + short_sleep = jiffies + msecs_to_jiffies(HSMP_SHORT_SLEEP); + timeout = jiffies + msecs_to_jiffies(HSMP_MSG_TIMEOUT); + + while (time_before(jiffies, timeout)) { + ret = amd_hsmp_rdwr(root, SMN_HSMP_MSG_RESP, &mbox_status, HSMP_RD); + if (ret) { + pr_err("Error %d reading mailbox status\n", ret); + return ret; + } + + if (mbox_status != HSMP_STATUS_NOT_READY) + break; + if (time_before(jiffies, short_sleep)) + usleep_range(50, 100); + else + usleep_range(1000, 2000); + } + + if (unlikely(mbox_status == HSMP_STATUS_NOT_READY)) { + return -ETIMEDOUT; + } else if (unlikely(mbox_status == HSMP_ERR_INVALID_MSG)) { + return -ENOMSG; + } else if (unlikely(mbox_status == HSMP_ERR_INVALID_INPUT)) { + return -EINVAL; + } else if (unlikely(mbox_status != HSMP_STATUS_OK)) { + pr_err("Message ID %u unknown failure (status = 0x%X)\n", + msg->msg_id, mbox_status); + return -EIO; + } + + /* + * SMU has responded OK. Read response data. + * SMU reads the input arguments from eight 32 bit registers starting + * from SMN_HSMP_MSG_DATA and writes the response data to the same + * SMN_HSMP_MSG_DATA address. + * We copy the response data if any, back to the args[]. + */ + index = 0; + while (index < msg->response_sz) { + ret = amd_hsmp_rdwr(root, SMN_HSMP_MSG_DATA + (index << 2), + &msg->args[index], HSMP_RD); + if (ret) { + pr_err("Error %d reading response %u for message ID:%u\n", + ret, index, msg->msg_id); + break; + } + index++; + } + + return ret; +} + +static int validate_message(struct hsmp_message *msg) +{ + /* msg_id against valid range of message IDs */ + if (msg->msg_id < HSMP_TEST || msg->msg_id >= HSMP_MSG_ID_MAX) + return -ENOMSG; + + /* msg_id is a reserved message ID */ + if (hsmp_msg_desc_table[msg->msg_id].type == HSMP_RSVD) + return -ENOMSG; + + /* num_args and response_sz against the HSMP spec */ + if (msg->num_args != hsmp_msg_desc_table[msg->msg_id].num_args || + msg->response_sz != hsmp_msg_desc_table[msg->msg_id].response_sz) + return -EINVAL; + + return 0; +} + +int hsmp_send_message(struct hsmp_message *msg) +{ + struct amd_northbridge *nb; + int ret; + + if (!msg) + return -EINVAL; + + nb = node_to_amd_nb(msg->sock_ind); + if (!nb || !nb->root) + return -ENODEV; + + ret = validate_message(msg); + if (ret) + return ret; + + /* + * The time taken by smu operation to complete is between + * 10us to 1ms. Sometime it may take more time. + * In SMP system timeout of 100 millisecs should + * be enough for the previous thread to finish the operation + */ + ret = down_timeout(&hsmp_sem[msg->sock_ind], + msecs_to_jiffies(HSMP_MSG_TIMEOUT)); + if (ret < 0) + return ret; + + ret = __hsmp_send_message(nb->root, msg); + + up(&hsmp_sem[msg->sock_ind]); + + return ret; +} +EXPORT_SYMBOL_GPL(hsmp_send_message); + +static int hsmp_test(u16 sock_ind, u32 value) +{ + struct hsmp_message msg = { 0 }; + struct amd_northbridge *nb; + int ret = -ENODEV; + + nb = node_to_amd_nb(sock_ind); + if (!nb || !nb->root) + return ret; + + /* + * Test the hsmp port by performing TEST command. The test message + * takes one argument and returns the value of that argument + 1. + */ + msg.msg_id = HSMP_TEST; + msg.num_args = 1; + msg.response_sz = 1; + msg.args[0] = value; + msg.sock_ind = sock_ind; + + ret = __hsmp_send_message(nb->root, &msg); + if (ret) + return ret; + + /* Check the response value */ + if (msg.args[0] != (value + 1)) { + pr_err("Socket %d test message failed, Expected 0x%08X, received 0x%08X\n", + sock_ind, (value + 1), msg.args[0]); + return -EBADE; + } + + return ret; +} + +static long hsmp_ioctl(struct file *fp, unsigned int cmd, unsigned long arg) +{ + int __user *arguser = (int __user *)arg; + struct hsmp_message msg = { 0 }; + int ret; + + if (copy_struct_from_user(&msg, sizeof(msg), arguser, sizeof(struct hsmp_message))) + return -EFAULT; + + /* + * Check msg_id is within the range of supported msg ids + * i.e within the array bounds of hsmp_msg_desc_table + */ + if (msg.msg_id < HSMP_TEST || msg.msg_id >= HSMP_MSG_ID_MAX) + return -ENOMSG; + + switch (fp->f_mode & (FMODE_WRITE | FMODE_READ)) { + case FMODE_WRITE: + /* + * Device is opened in O_WRONLY mode + * Execute only set/configure commands + */ + if (hsmp_msg_desc_table[msg.msg_id].type != HSMP_SET) + return -EINVAL; + break; + case FMODE_READ: + /* + * Device is opened in O_RDONLY mode + * Execute only get/monitor commands + */ + if (hsmp_msg_desc_table[msg.msg_id].type != HSMP_GET) + return -EINVAL; + break; + case FMODE_READ | FMODE_WRITE: + /* + * Device is opened in O_RDWR mode + * Execute both get/monitor and set/configure commands + */ + break; + default: + return -EINVAL; + } + + ret = hsmp_send_message(&msg); + if (ret) + return ret; + + if (hsmp_msg_desc_table[msg.msg_id].response_sz > 0) { + /* Copy results back to user for get/monitor commands */ + if (copy_to_user(arguser, &msg, sizeof(struct hsmp_message))) + return -EFAULT; + } + + return 0; +} + +static const struct file_operations hsmp_fops = { + .owner = THIS_MODULE, + .unlocked_ioctl = hsmp_ioctl, + .compat_ioctl = hsmp_ioctl, +}; + +static int hsmp_pltdrv_probe(struct platform_device *pdev) +{ + int i; + + hsmp_sem = devm_kzalloc(&pdev->dev, + (amd_nb_num() * sizeof(struct semaphore)), + GFP_KERNEL); + if (!hsmp_sem) + return -ENOMEM; + + for (i = 0; i < amd_nb_num(); i++) + sema_init(&hsmp_sem[i], 1); + + hsmp_device.name = "hsmp_cdev"; + hsmp_device.minor = MISC_DYNAMIC_MINOR; + hsmp_device.fops = &hsmp_fops; + hsmp_device.parent = &pdev->dev; + hsmp_device.nodename = "hsmp"; + hsmp_device.mode = 0644; + + return misc_register(&hsmp_device); +} + +static int hsmp_pltdrv_remove(struct platform_device *pdev) +{ + misc_deregister(&hsmp_device); + + return 0; +} + +static struct platform_driver amd_hsmp_driver = { + .probe = hsmp_pltdrv_probe, + .remove = hsmp_pltdrv_remove, + .driver = { + .name = DRIVER_NAME, + }, +}; + +static struct platform_device *amd_hsmp_platdev; + +static int __init hsmp_plt_init(void) +{ + int ret = -ENODEV; + u16 num_sockets; + int i; + + if (boot_cpu_data.x86_vendor != X86_VENDOR_AMD || boot_cpu_data.x86 < 0x19) { + pr_err("HSMP is not supported on Family:%x model:%x\n", + boot_cpu_data.x86, boot_cpu_data.x86_model); + return ret; + } + + /* + * amd_nb_num() returns number of SMN/DF interfaces present in the system + * if we have N SMN/DF interfaces that ideally means N sockets + */ + num_sockets = amd_nb_num(); + if (num_sockets == 0) + return ret; + + /* Test the hsmp interface on each socket */ + for (i = 0; i < num_sockets; i++) { + ret = hsmp_test(i, 0xDEADBEEF); + if (ret) { + pr_err("HSMP is not supported on Fam:%x model:%x\n", + boot_cpu_data.x86, boot_cpu_data.x86_model); + pr_err("Or Is HSMP disabled in BIOS ?\n"); + return -EOPNOTSUPP; + } + } + + ret = platform_driver_register(&amd_hsmp_driver); + if (ret) + return ret; + + amd_hsmp_platdev = platform_device_alloc(DRIVER_NAME, -1); + if (!amd_hsmp_platdev) { + ret = -ENOMEM; + goto drv_unregister; + } + + ret = platform_device_add(amd_hsmp_platdev); + if (ret) { + platform_device_put(amd_hsmp_platdev); + goto drv_unregister; + } + + return 0; + +drv_unregister: + platform_driver_unregister(&amd_hsmp_driver); + return ret; +} + +static void __exit hsmp_plt_exit(void) +{ + platform_device_unregister(amd_hsmp_platdev); + platform_driver_unregister(&amd_hsmp_driver); +} + +device_initcall(hsmp_plt_init); +module_exit(hsmp_plt_exit); + +MODULE_DESCRIPTION("AMD HSMP Platform Interface Driver"); +MODULE_VERSION(DRIVER_VERSION); +MODULE_LICENSE("GPL v2"); From 402576d9804e9dc9b31af4e3613aa8da9ebce84e Mon Sep 17 00:00:00 2001 From: Naveen Krishna Chatradhi Date: Tue, 22 Feb 2022 10:35:01 +0530 Subject: [PATCH 103/137] Documentation: Add x86/amd_hsmp driver This documentation for amd_hsmp driver explains how to use the device interface. Signed-off-by: Naveen Krishna Chatradhi Acked-by: Song Liu Link: https://lore.kernel.org/r/20220222050501.18789-2-nchatrad@amd.com Reviewed-by: Hans de Goede Signed-off-by: Hans de Goede --- Documentation/x86/amd_hsmp.rst | 86 ++++++++++++++++++++++++++++++++++ Documentation/x86/index.rst | 1 + 2 files changed, 87 insertions(+) create mode 100644 Documentation/x86/amd_hsmp.rst diff --git a/Documentation/x86/amd_hsmp.rst b/Documentation/x86/amd_hsmp.rst new file mode 100644 index 000000000000..440e4b645a1c --- /dev/null +++ b/Documentation/x86/amd_hsmp.rst @@ -0,0 +1,86 @@ +.. SPDX-License-Identifier: GPL-2.0 + +============================================ +AMD HSMP interface +============================================ + +Newer Fam19h EPYC server line of processors from AMD support system +management functionality via HSMP (Host System Management Port). + +The Host System Management Port (HSMP) is an interface to provide +OS-level software with access to system management functions via a +set of mailbox registers. + +More details on the interface can be found in chapter +"7 Host System Management Port (HSMP)" of the family/model PPR +Eg: https://www.amd.com/system/files/TechDocs/55898_B1_pub_0.50.zip + +HSMP interface is supported on EPYC server CPU models only. + + +HSMP device +============================================ + +amd_hsmp driver under the drivers/platforms/x86/ creates miscdevice +/dev/hsmp to let user space programs run hsmp mailbox commands. + +$ ls -al /dev/hsmp +crw-r--r-- 1 root root 10, 123 Jan 21 21:41 /dev/hsmp + +Characteristics of the dev node: + * Write mode is used for running set/configure commands + * Read mode is used for running get/status monitor commands + +Access restrictions: + * Only root user is allowed to open the file in write mode. + * The file can be opened in read mode by all the users. + +In-kernel integration: + * Other subsystems in the kernel can use the exported transport + function hsmp_send_message(). + * Locking across callers is taken care by the driver. + + +An example +========== + +To access hsmp device from a C program. +First, you need to include the headers:: + + #include + +Which defines the supported messages/message IDs. + +Next thing, open the device file, as follows:: + + int file; + + file = open("/dev/hsmp", O_RDWR); + if (file < 0) { + /* ERROR HANDLING; you can check errno to see what went wrong */ + exit(1); + } + +The following IOCTL is defined: + +``ioctl(file, HSMP_IOCTL_CMD, struct hsmp_message *msg)`` + The argument is a pointer to a:: + + struct hsmp_message { + __u32 msg_id; /* Message ID */ + __u16 num_args; /* Number of input argument words in message */ + __u16 response_sz; /* Number of expected output/response words */ + __u32 args[HSMP_MAX_MSG_LEN]; /* argument/response buffer */ + __u16 sock_ind; /* socket number */ + }; + +The ioctl would return a non-zero on failure; you can read errno to see +what happened. The transaction returns 0 on success. + +More details on the interface and message definitions can be found in chapter +"7 Host System Management Port (HSMP)" of the respective family/model PPR +eg: https://www.amd.com/system/files/TechDocs/55898_B1_pub_0.50.zip + +User space C-APIs are made available by linking against the esmi library, +which is provided by the E-SMS project https://developer.amd.com/e-sms/. +See: https://github.com/amd/esmi_ib_library diff --git a/Documentation/x86/index.rst b/Documentation/x86/index.rst index f498f1d36cd3..334d0a498749 100644 --- a/Documentation/x86/index.rst +++ b/Documentation/x86/index.rst @@ -24,6 +24,7 @@ x86-specific Documentation intel-iommu intel_txt amd-memory-encryption + amd_hsmp pti mds microcode From bf779aaf56ea23864e39e9862b3b3a8436236e07 Mon Sep 17 00:00:00 2001 From: Mark Pearson Date: Tue, 22 Feb 2022 13:51:37 -0500 Subject: [PATCH 104/137] platform/x86: thinkpad_acpi: Add dual fan probe Instead of having quirks for systems that have a second fan it would be nice to detect this setup. Unfortunately, confirmed by the Lenovo FW team, there is no way to retrieve this information from the EC or BIOS. Recommendation was to attempt to read the fan and if successful then assume a 2nd fan is present. The fans are also supposed to spin up on boot for some time, so in theory we could check for a speed > 0. In testing this seems to hold true but as I couldn't test on all platforms I've avoided implementing this. It also breaks for the corner case where you load the module once the fans are idle. Tested on P1G4, P1G3, X1C9 and T14 (no fans) and it works correctly. For the platforms with dual fans where it was confirmed to work I have removed the quirks. Potentially this could be done for all platforms but I've left untested platforms in for now. On these platforms the fans will be enabled and then detected - so no impact. Signed-off-by: Mark Pearson Link: https://lore.kernel.org/r/20220222185137.4325-1-markpearson@lenovo.com Reviewed-by: Hans de Goede Signed-off-by: Hans de Goede --- drivers/platform/x86/thinkpad_acpi.c | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/drivers/platform/x86/thinkpad_acpi.c b/drivers/platform/x86/thinkpad_acpi.c index e9b1574729b9..d0599e8a7b4d 100644 --- a/drivers/platform/x86/thinkpad_acpi.c +++ b/drivers/platform/x86/thinkpad_acpi.c @@ -8698,10 +8698,7 @@ static const struct tpacpi_quirk fan_quirk_table[] __initconst = { TPACPI_Q_LNV3('N', '2', 'N', TPACPI_FAN_2CTL), /* P53 / P73 */ TPACPI_Q_LNV3('N', '2', 'E', TPACPI_FAN_2CTL), /* P1 / X1 Extreme (1st gen) */ TPACPI_Q_LNV3('N', '2', 'O', TPACPI_FAN_2CTL), /* P1 / X1 Extreme (2nd gen) */ - TPACPI_Q_LNV3('N', '2', 'V', TPACPI_FAN_2CTL), /* P1 / X1 Extreme (3nd gen) */ - TPACPI_Q_LNV3('N', '4', '0', TPACPI_FAN_2CTL), /* P1 / X1 Extreme (4nd gen) */ TPACPI_Q_LNV3('N', '3', '0', TPACPI_FAN_2CTL), /* P15 (1st gen) / P15v (1st gen) */ - TPACPI_Q_LNV3('N', '3', '2', TPACPI_FAN_2CTL), /* X1 Carbon (9th gen) */ TPACPI_Q_LNV3('N', '3', '7', TPACPI_FAN_2CTL), /* T15g (2nd gen) */ TPACPI_Q_LNV3('N', '1', 'O', TPACPI_FAN_NOFAN), /* X1 Tablet (2nd gen) */ }; @@ -8745,6 +8742,9 @@ static int __init fan_init(struct ibm_init_struct *iibm) * ThinkPad ECs supports the fan control register */ if (likely(acpi_ec_read(fan_status_offset, &fan_control_initial_status))) { + int res; + unsigned int speed; + fan_status_access_mode = TPACPI_FAN_RD_TPEC; if (quirks & TPACPI_FAN_Q1) fan_quirk1_setup(); @@ -8757,6 +8757,15 @@ static int __init fan_init(struct ibm_init_struct *iibm) tp_features.second_fan_ctl = 1; pr_info("secondary fan control enabled\n"); } + /* Try and probe the 2nd fan */ + res = fan2_get_speed(&speed); + if (res >= 0) { + /* It responded - so let's assume it's there */ + tp_features.second_fan = 1; + tp_features.second_fan_ctl = 1; + pr_info("secondary fan control detected & enabled\n"); + } + } else { pr_err("ThinkPad ACPI EC access misbehaving, fan status and control unavailable\n"); return -ENODEV; From 7fa7dfafe40a75e93a5f59bcb4e382d0af436ace Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Wed, 23 Feb 2022 14:31:50 +0100 Subject: [PATCH 105/137] platform/x86: x86-android-tablets: Fix EBUSY error when requesting IOAPIC IRQs Sometimes IRQs used by GPIOs in direct-IRQ mode are already registered because they are used as ACPI "Interrupt () {}" resource for one of the many bogus I2C devices present in the broken DSDTs of Android x86 tablets. This is an issue if the existing (bogus) ACPI resource uses different trigger settings then what is being requested, leading to an -EBUSY error return of acpi_register_gsi(). Fix this by calling acpi_unregister_gsi() first, so that the acpi_register_gsi() is allowed to change the trigger settings. In cases where the GSI has not been registered yet the acpi_unregister_gsi() is a no-op. Signed-off-by: Hans de Goede Link: https://lore.kernel.org/r/20220223133153.730337-3-hdegoede@redhat.com --- drivers/platform/x86/x86-android-tablets.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/platform/x86/x86-android-tablets.c b/drivers/platform/x86/x86-android-tablets.c index f280c82d5ba5..61e526e048c3 100644 --- a/drivers/platform/x86/x86-android-tablets.c +++ b/drivers/platform/x86/x86-android-tablets.c @@ -89,6 +89,12 @@ static int x86_acpi_irq_helper_get(const struct x86_acpi_irq_data *data) switch (data->type) { case X86_ACPI_IRQ_TYPE_APIC: + /* + * The DSDT may already reference the GSI in a device skipped by + * acpi_quirk_skip_i2c_client_enumeration(). Unregister the GSI + * to avoid EBUSY errors in this case. + */ + acpi_unregister_gsi(data->index); irq = acpi_register_gsi(NULL, data->index, data->trigger, data->polarity); if (irq < 0) pr_err("error %d getting APIC IRQ %d\n", irq, data->index); From 67dfc2b441b4b724acb74361e9e2e23506721e4f Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Wed, 23 Feb 2022 14:31:51 +0100 Subject: [PATCH 106/137] platform/x86: x86-android-tablets: Add Lenovo Yoga Tablet 2 830 / 1050 data The Lenovo Yoga Tablet 2 series comes in 4 versions: 830F, 830L, 1050F and 1050L. The F postfix indicates a wifi only version and the L postfix indicates a LTE version. The 830 models are 8" and the 1050 models are 10". Despite there being 8" and 10" versions all models use the same mainboard, with an identical BIOS and thus identical DMI strings, so support for all 4 models is added through a single DMI table entry. As all devices dealt with in the x86-android-tablets modules, these are x86 ACPI tablets which ships with Android x86 as factory OS. The mainboard's DSDT contain a bunch of I2C devices which are not actually there, causing various resource conflicts. Enumeration of these is skipped through the acpi_quirk_skip_i2c_client_enumeration(). Add support for manually instantiating the I2C devices which are actually present on this tablet by adding the necessary device info to the x86-android-tablets module. This has been tested on a 830F and a 1050L tablet. Signed-off-by: Hans de Goede Reviewed-by: Andy Shevchenko Link: https://lore.kernel.org/r/20220223133153.730337-4-hdegoede@redhat.com --- drivers/platform/x86/x86-android-tablets.c | 184 +++++++++++++++++++++ 1 file changed, 184 insertions(+) diff --git a/drivers/platform/x86/x86-android-tablets.c b/drivers/platform/x86/x86-android-tablets.c index 61e526e048c3..89972723f546 100644 --- a/drivers/platform/x86/x86-android-tablets.c +++ b/drivers/platform/x86/x86-android-tablets.c @@ -22,8 +22,10 @@ #include #include #include +#include #include #include +#include #include #include /* For gpio_get_desc() which is EXPORT_SYMBOL_GPL() */ @@ -182,6 +184,15 @@ static const char * const tusb1211_chg_det_psy[] = { "tusb1211-charger-detect" } static const char * const bq24190_psy[] = { "bq24190-charger" }; static const char * const bq25890_psy[] = { "bq25890-charger" }; +static const struct property_entry fg_bq24190_supply_props[] = { + PROPERTY_ENTRY_STRING_ARRAY("supplied-from", bq24190_psy), + { } +}; + +static const struct software_node fg_bq24190_supply_node = { + .properties = fg_bq24190_supply_props, +}; + static const struct property_entry fg_bq25890_supply_props[] = { PROPERTY_ENTRY_STRING_ARRAY("supplied-from", bq25890_psy), { } @@ -704,6 +715,165 @@ static const struct x86_dev_info lenovo_yogabook_x9x_info __initconst = { .i2c_client_count = ARRAY_SIZE(lenovo_yogabook_x9x_i2c_clients), }; +/* Lenovo Yoga Tablet 2 1050F/L's Android factory img has everything hardcoded */ +static const struct property_entry lenovo_yoga_tab2_830_1050_bq24190_props[] = { + PROPERTY_ENTRY_STRING_ARRAY("supplied-from", tusb1211_chg_det_psy), + PROPERTY_ENTRY_REF("monitored-battery", &generic_lipo_hv_4v35_battery_node), + PROPERTY_ENTRY_BOOL("omit-battery-class"), + PROPERTY_ENTRY_BOOL("disable-reset"), + { } +}; + +static const struct software_node lenovo_yoga_tab2_830_1050_bq24190_node = { + .properties = lenovo_yoga_tab2_830_1050_bq24190_props, +}; + +/* This gets filled by lenovo_yoga_tab2_830_1050_init() */ +static struct rmi_device_platform_data lenovo_yoga_tab2_830_1050_rmi_pdata = { }; + +static struct lp855x_platform_data lenovo_yoga_tab2_830_1050_lp8557_pdata = { + .device_control = 0x86, + .initial_brightness = 128, +}; + +static const struct x86_i2c_client_info lenovo_yoga_tab2_830_1050_i2c_clients[] __initconst = { + { + /* bq24292i battery charger */ + .board_info = { + .type = "bq24190", + .addr = 0x6b, + .dev_name = "bq24292i", + .swnode = &lenovo_yoga_tab2_830_1050_bq24190_node, + .platform_data = &bq24190_pdata, + }, + .adapter_path = "\\_SB_.I2C1", + .irq_data = { + .type = X86_ACPI_IRQ_TYPE_GPIOINT, + .chip = "INT33FC:02", + .index = 2, + .trigger = ACPI_EDGE_SENSITIVE, + .polarity = ACPI_ACTIVE_HIGH, + }, + }, { + /* BQ27541 fuel-gauge */ + .board_info = { + .type = "bq27541", + .addr = 0x55, + .dev_name = "bq27541", + .swnode = &fg_bq24190_supply_node, + }, + .adapter_path = "\\_SB_.I2C1", + }, { + /* Synaptics RMI touchscreen */ + .board_info = { + .type = "rmi4_i2c", + .addr = 0x38, + .dev_name = "rmi4_i2c", + .platform_data = &lenovo_yoga_tab2_830_1050_rmi_pdata, + }, + .adapter_path = "\\_SB_.I2C6", + .irq_data = { + .type = X86_ACPI_IRQ_TYPE_APIC, + .index = 0x45, + .trigger = ACPI_EDGE_SENSITIVE, + .polarity = ACPI_ACTIVE_HIGH, + }, + }, { + /* LP8557 Backlight controller */ + .board_info = { + .type = "lp8557", + .addr = 0x2c, + .dev_name = "lp8557", + .platform_data = &lenovo_yoga_tab2_830_1050_lp8557_pdata, + }, + .adapter_path = "\\_SB_.I2C3", + }, +}; + +static struct gpiod_lookup_table lenovo_yoga_tab2_830_1050_int3496_gpios = { + .dev_id = "intel-int3496", + .table = { + GPIO_LOOKUP("INT33FC:02", 1, "mux", GPIO_ACTIVE_LOW), + GPIO_LOOKUP("INT33FC:02", 24, "id", GPIO_ACTIVE_HIGH), + { } + }, +}; + +static struct gpiod_lookup_table * const lenovo_yoga_tab2_830_1050_gpios[] = { + &lenovo_yoga_tab2_830_1050_int3496_gpios, + NULL +}; + +static int __init lenovo_yoga_tab2_830_1050_init(void); +static void lenovo_yoga_tab2_830_1050_exit(void); + +static struct x86_dev_info lenovo_yoga_tab2_830_1050_info __initdata = { + .i2c_client_info = lenovo_yoga_tab2_830_1050_i2c_clients, + /* i2c_client_count gets set by lenovo_yoga_tab2_830_1050_init() */ + .pdev_info = int3496_pdevs, + .pdev_count = ARRAY_SIZE(int3496_pdevs), + .gpiod_lookup_tables = lenovo_yoga_tab2_830_1050_gpios, + .bat_swnode = &generic_lipo_hv_4v35_battery_node, + .modules = bq24190_modules, + .invalid_aei_gpiochip = "INT33FC:02", + .init = lenovo_yoga_tab2_830_1050_init, +}; + +/* + * The Lenovo Yoga Tablet 2 830 and 1050 (8" vs 10") versions use the same + * mainboard, but they need some different treatment related to the display: + * 1. The 830 uses a portrait LCD panel with a landscape touchscreen, requiring + * the touchscreen driver to adjust the touch-coords to match the LCD. + * 2. Both use an TI LP8557 LED backlight controller. On the 1050 the LP8557's + * PWM input is connected to the PMIC's PWM output and everything works fine + * with the defaults programmed into the LP8557 by the BIOS. + * But on the 830 the LP8557's PWM input is connected to a PWM output coming + * from the LCD panel's controller. The Android code has a hack in the i915 + * driver to write the non-standard DSI reg 0x9f with the desired backlight + * level to set the duty-cycle of the LCD's PWM output. + * + * To avoid having to have a similar hack in the mainline kernel the LP8557 + * entry in lenovo_yoga_tab2_830_1050_i2c_clients instead just programs the + * LP8557 to directly set the level, ignoring the PWM input. This means that + * the LP8557 i2c_client should only be instantiated on the 830. + */ +static int __init lenovo_yoga_tab2_830_1050_init_display(void) +{ + struct gpio_desc *gpiod; + int ret; + + /* Use PMIC GPIO 10 bootstrap pin to differentiate 830 vs 1050 */ + ret = x86_android_tablet_get_gpiod("gpio_crystalcove", 10, &gpiod); + if (ret) + return ret; + + ret = gpiod_get_value_cansleep(gpiod); + if (ret) { + pr_info("detected Lenovo Yoga Tablet 2 1050F/L\n"); + lenovo_yoga_tab2_830_1050_info.i2c_client_count = + ARRAY_SIZE(lenovo_yoga_tab2_830_1050_i2c_clients) - 1; + } else { + pr_info("detected Lenovo Yoga Tablet 2 830F/L\n"); + lenovo_yoga_tab2_830_1050_rmi_pdata.sensor_pdata.axis_align.swap_axes = true; + lenovo_yoga_tab2_830_1050_rmi_pdata.sensor_pdata.axis_align.flip_y = true; + lenovo_yoga_tab2_830_1050_info.i2c_client_count = + ARRAY_SIZE(lenovo_yoga_tab2_830_1050_i2c_clients); + } + + return 0; +} + +static int __init lenovo_yoga_tab2_830_1050_init(void) +{ + int ret; + + ret = lenovo_yoga_tab2_830_1050_init_display(); + if (ret) + return ret; + + return 0; +} + /* Nextbook Ares 8 tablets have an Android factory img with everything hardcoded */ static const char * const nextbook_ares8_accel_mount_matrix[] = { "0", "-1", "0", @@ -948,6 +1118,20 @@ static const struct dmi_system_id x86_android_tablet_ids[] __initconst = { }, .driver_data = (void *)&lenovo_yogabook_x9x_info, }, + { + /* + * Lenovo Yoga Tablet 2 830F/L or 1050F/L (The 8" and 10" + * Lenovo Yoga Tablet 2 use the same mainboard) + */ + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Intel Corp."), + DMI_MATCH(DMI_PRODUCT_NAME, "VALLEYVIEW C0 PLATFORM"), + DMI_MATCH(DMI_BOARD_NAME, "BYT-T FFD8"), + /* Partial match on beginning of BIOS version */ + DMI_MATCH(DMI_BIOS_VERSION, "BLADE_21"), + }, + .driver_data = (void *)&lenovo_yoga_tab2_830_1050_info, + }, { /* Nextbook Ares 8 */ .matches = { From bf8fd1a9736eaff5a5b823415e5a0d165a42e5d6 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Wed, 23 Feb 2022 14:31:52 +0100 Subject: [PATCH 107/137] platform/x86: x86-android-tablets: Workaround Lenovo Yoga Tablet 2 830/1050 poweroff hang These tablets' DSDT does not set acpi_gbl_reduced_hardware, so acpi_power_off gets used as pm_power_off handler. Not setting acpi_gbl_reduced_hardware may very well be correct for these tablets, but acpi_power_off is broken on them. Using acpi_power_off causes "poweroff" to hang hard. Requiring pressing the powerbutton for 30 seconds *twice* followed by a normal 3 second press to recover. Avoid this by overriding the global pm_power_off handler to do an EFI poweroff, which does work, instead. Signed-off-by: Hans de Goede Link: https://lore.kernel.org/r/20220223133153.730337-5-hdegoede@redhat.com --- drivers/platform/x86/x86-android-tablets.c | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/drivers/platform/x86/x86-android-tablets.c b/drivers/platform/x86/x86-android-tablets.c index 89972723f546..c3d2b30dbe26 100644 --- a/drivers/platform/x86/x86-android-tablets.c +++ b/drivers/platform/x86/x86-android-tablets.c @@ -12,6 +12,7 @@ #include #include +#include #include #include #include @@ -24,6 +25,7 @@ #include #include #include +#include #include #include #include @@ -817,6 +819,7 @@ static struct x86_dev_info lenovo_yoga_tab2_830_1050_info __initdata = { .modules = bq24190_modules, .invalid_aei_gpiochip = "INT33FC:02", .init = lenovo_yoga_tab2_830_1050_init, + .exit = lenovo_yoga_tab2_830_1050_exit, }; /* @@ -863,6 +866,18 @@ static int __init lenovo_yoga_tab2_830_1050_init_display(void) return 0; } +/* + * These tablet's DSDT does not set acpi_gbl_reduced_hardware, so acpi_power_off + * gets used as pm_power_off handler. This causes "poweroff" on these tablets + * to hang hard. Requiring pressing the powerbutton for 30 seconds *twice* + * followed by a normal 3 second press to recover. Avoid this by doing an EFI + * poweroff instead. + */ +static void lenovo_yoga_tab2_830_1050_power_off(void) +{ + efi.reset_system(EFI_RESET_SHUTDOWN, EFI_SUCCESS, 0, NULL); +} + static int __init lenovo_yoga_tab2_830_1050_init(void) { int ret; @@ -871,9 +886,15 @@ static int __init lenovo_yoga_tab2_830_1050_init(void) if (ret) return ret; + pm_power_off = lenovo_yoga_tab2_830_1050_power_off; return 0; } +static void lenovo_yoga_tab2_830_1050_exit(void) +{ + pm_power_off = NULL; /* Just turn poweroff into halt on module unload */ +} + /* Nextbook Ares 8 tablets have an Android factory img with everything hardcoded */ static const char * const nextbook_ares8_accel_mount_matrix[] = { "0", "-1", "0", From cb18448bbf1c845aecaa09b04cfdcceb070d9236 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Wed, 23 Feb 2022 14:31:53 +0100 Subject: [PATCH 108/137] platform/x86: x86-android-tablets: Lenovo Yoga Tablet 2 830/1050 sound support The ACPI tables for the codec setup on the Lenovo Yoga Tablet 2 830/1050 miss 2 things compared to their Windows (Lenovo Yoga Tablet 2 1051) counterparts: 1. There is no CLKE ACPI method to enable output of the 32KHz PMU clock on pin 6 of the SUS GPIO controller 2. The GPIOs used by the codec are not listed in the fwnode for the codec Add pinctrl code to set the SUS6 pin mux manually and a gpio-lookup table for the GPIOs to work around both issues. Signed-off-by: Hans de Goede Reviewed-by: Andy Shevchenko Link: https://lore.kernel.org/r/20220223133153.730337-6-hdegoede@redhat.com --- drivers/platform/x86/x86-android-tablets.c | 69 ++++++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/drivers/platform/x86/x86-android-tablets.c b/drivers/platform/x86/x86-android-tablets.c index c3d2b30dbe26..f446be72e539 100644 --- a/drivers/platform/x86/x86-android-tablets.c +++ b/drivers/platform/x86/x86-android-tablets.c @@ -23,12 +23,15 @@ #include #include #include +#include +#include #include #include #include #include #include #include +#include #include /* For gpio_get_desc() which is EXPORT_SYMBOL_GPL() */ #include "../../gpio/gpiolib.h" @@ -801,8 +804,22 @@ static struct gpiod_lookup_table lenovo_yoga_tab2_830_1050_int3496_gpios = { }, }; +#define LENOVO_YOGA_TAB2_830_1050_CODEC_NAME "spi-10WM5102:00" + +static struct gpiod_lookup_table lenovo_yoga_tab2_830_1050_codec_gpios = { + .dev_id = LENOVO_YOGA_TAB2_830_1050_CODEC_NAME, + .table = { + GPIO_LOOKUP("gpio_crystalcove", 3, "reset", GPIO_ACTIVE_HIGH), + GPIO_LOOKUP("INT33FC:01", 23, "wlf,ldoena", GPIO_ACTIVE_HIGH), + GPIO_LOOKUP("arizona", 2, "wlf,spkvdd-ena", GPIO_ACTIVE_HIGH), + GPIO_LOOKUP("arizona", 4, "wlf,micd-pol", GPIO_ACTIVE_LOW), + { } + }, +}; + static struct gpiod_lookup_table * const lenovo_yoga_tab2_830_1050_gpios[] = { &lenovo_yoga_tab2_830_1050_int3496_gpios, + &lenovo_yoga_tab2_830_1050_codec_gpios, NULL }; @@ -866,6 +883,49 @@ static int __init lenovo_yoga_tab2_830_1050_init_display(void) return 0; } +/* SUS (INT33FC:02) pin 6 needs to be configured as pmu_clk for the audio codec */ +static const struct pinctrl_map lenovo_yoga_tab2_830_1050_codec_pinctrl_map = + PIN_MAP_MUX_GROUP(LENOVO_YOGA_TAB2_830_1050_CODEC_NAME, "codec_32khz_clk", + "INT33FC:02", "pmu_clk2_grp", "pmu_clk"); + +static struct pinctrl *lenovo_yoga_tab2_830_1050_codec_pinctrl; + +static int __init lenovo_yoga_tab2_830_1050_init_codec(void) +{ + struct device *codec_dev; + struct pinctrl *pinctrl; + int ret; + + codec_dev = bus_find_device_by_name(&spi_bus_type, NULL, + LENOVO_YOGA_TAB2_830_1050_CODEC_NAME); + if (!codec_dev) { + pr_err("error cannot find %s device\n", LENOVO_YOGA_TAB2_830_1050_CODEC_NAME); + return -ENODEV; + } + + ret = pinctrl_register_mappings(&lenovo_yoga_tab2_830_1050_codec_pinctrl_map, 1); + if (ret) + goto err_put_device; + + pinctrl = pinctrl_get_select(codec_dev, "codec_32khz_clk"); + if (IS_ERR(pinctrl)) { + ret = dev_err_probe(codec_dev, PTR_ERR(pinctrl), "selecting codec_32khz_clk\n"); + goto err_unregister_mappings; + } + + /* We're done with the codec_dev now */ + put_device(codec_dev); + + lenovo_yoga_tab2_830_1050_codec_pinctrl = pinctrl; + return 0; + +err_unregister_mappings: + pinctrl_unregister_mappings(&lenovo_yoga_tab2_830_1050_codec_pinctrl_map); +err_put_device: + put_device(codec_dev); + return ret; +} + /* * These tablet's DSDT does not set acpi_gbl_reduced_hardware, so acpi_power_off * gets used as pm_power_off handler. This causes "poweroff" on these tablets @@ -886,6 +946,10 @@ static int __init lenovo_yoga_tab2_830_1050_init(void) if (ret) return ret; + ret = lenovo_yoga_tab2_830_1050_init_codec(); + if (ret) + return ret; + pm_power_off = lenovo_yoga_tab2_830_1050_power_off; return 0; } @@ -893,6 +957,11 @@ static int __init lenovo_yoga_tab2_830_1050_init(void) static void lenovo_yoga_tab2_830_1050_exit(void) { pm_power_off = NULL; /* Just turn poweroff into halt on module unload */ + + if (lenovo_yoga_tab2_830_1050_codec_pinctrl) { + pinctrl_put(lenovo_yoga_tab2_830_1050_codec_pinctrl); + pinctrl_unregister_mappings(&lenovo_yoga_tab2_830_1050_codec_pinctrl_map); + } } /* Nextbook Ares 8 tablets have an Android factory img with everything hardcoded */ From 60c7353c6b23537448c7b24498f7bbf8973a81ef Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Thu, 24 Feb 2022 12:02:40 +0100 Subject: [PATCH 109/137] Input: soc_button_array - add support for Microsoft Surface 3 (MSHW0028) buttons The drivers/platform/surface/surface3_button.c code is alsmost a 1:1 copy of the soc_button_array code. The only big difference is that it binds to an i2c_client rather then to a platform_device. The cause of this is the ACPI resources for the MSHW0028 device containing a bogus I2cSerialBusV2 resource which causes the kernel to instantiate an i2c_client for it instead of a platform_device. Add "MSHW0028" to the ignore_serial_bus_ids[] list in drivers/apci/scan.c, so that a platform_device will be instantiated and add support for the MSHW0028 HID to soc_button_array. This fully replaces surface3_button, which will be removed in a separate commit (since it binds to the now no longer created i2c_client it no longer does anyyhing after this commit). Note the MSHW0028 id is used by Microsoft to describe the tablet buttons on both the Surface 3 and the Surface 3 Pro and the actual API/implementation for the Surface 3 Pro is quite different. The changes in this commit should not impact the separate surfacepro3_button driver: 1. Because of the bogus I2cSerialBusV2 resource problem that driver binds to the acpi_device itself, so instantiating a platform_device instead of an i2c_client does not matter. 2. The soc_button_array driver will not bind to the MSHW0028 device on the Surface 3 Pro, because it has no GPIO resources. Signed-off-by: Hans de Goede Reviewed-by: Maximilian Luz Acked-by: Rafael J. Wysocki Acked-by: Dmitry Torokhov Link: https://lore.kernel.org/r/20220224110241.9613-2-hdegoede@redhat.com --- drivers/acpi/scan.c | 5 +++++ drivers/input/misc/soc_button_array.c | 24 +++++++++++++++++++++++- 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/drivers/acpi/scan.c b/drivers/acpi/scan.c index 4463c2eda61e..e993c8b253f5 100644 --- a/drivers/acpi/scan.c +++ b/drivers/acpi/scan.c @@ -1749,6 +1749,11 @@ static bool acpi_device_enumeration_by_parent(struct acpi_device *device) {"INT3515", }, /* Non-conforming _HID for Cirrus Logic already released */ {"CLSA0100", }, + /* + * Some ACPI devs contain SerialBus resources even though they are not + * attached to a serial bus at all. + */ + {"MSHW0028", }, /* * HIDs of device with an UartSerialBusV2 resource for which userspace * expects a regular tty cdev to be created (instead of the in kernel diff --git a/drivers/input/misc/soc_button_array.c b/drivers/input/misc/soc_button_array.c index cb6ec59a045d..cbb1599a520e 100644 --- a/drivers/input/misc/soc_button_array.c +++ b/drivers/input/misc/soc_button_array.c @@ -469,6 +469,27 @@ static const struct soc_device_data soc_device_INT33D3 = { .button_info = soc_button_INT33D3, }; +/* + * Button info for Microsoft Surface 3 (non pro), this is indentical to + * the PNP0C40 info except that the home button is active-high. + * + * The Surface 3 Pro also has a MSHW0028 ACPI device, but that uses a custom + * version of the drivers/platform/x86/intel/hid.c 5 button array ACPI API + * instead. A check() callback is not necessary though as the Surface 3 Pro + * MSHW0028 ACPI device's resource table does not contain any GPIOs. + */ +static const struct soc_button_info soc_button_MSHW0028[] = { + { "power", 0, EV_KEY, KEY_POWER, false, true, true }, + { "home", 1, EV_KEY, KEY_LEFTMETA, false, true, false }, + { "volume_up", 2, EV_KEY, KEY_VOLUMEUP, true, false, true }, + { "volume_down", 3, EV_KEY, KEY_VOLUMEDOWN, true, false, true }, + { } +}; + +static const struct soc_device_data soc_device_MSHW0028 = { + .button_info = soc_button_MSHW0028, +}; + /* * Special device check for Surface Book 2 and Surface Pro (2017). * Both, the Surface Pro 4 (surfacepro3_button.c) and the above mentioned @@ -535,7 +556,8 @@ static const struct acpi_device_id soc_button_acpi_match[] = { { "ID9001", (unsigned long)&soc_device_INT33D3 }, { "ACPI0011", 0 }, - /* Microsoft Surface Devices (5th and 6th generation) */ + /* Microsoft Surface Devices (3th, 5th and 6th generation) */ + { "MSHW0028", (unsigned long)&soc_device_MSHW0028 }, { "MSHW0040", (unsigned long)&soc_device_MSHW0040 }, { } From faabb26838f30b5a49178a50c8286e900928400f Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Thu, 24 Feb 2022 12:02:41 +0100 Subject: [PATCH 110/137] platform/surface: Remove Surface 3 Button driver The Surface 3 buttons are now handled by the generic soc_button_array driver. As part of adding support to soc_button_array the ACPI code now instantiates a platform_device rather then an i2c_client so there no longer is an i2c_client for this driver to bind to. Signed-off-by: Hans de Goede Reviewed-by: Maximilian Luz Link: https://lore.kernel.org/r/20220224110241.9613-3-hdegoede@redhat.com --- drivers/platform/surface/Kconfig | 7 - drivers/platform/surface/Makefile | 1 - drivers/platform/surface/surface3_button.c | 247 --------------------- 3 files changed, 255 deletions(-) delete mode 100644 drivers/platform/surface/surface3_button.c diff --git a/drivers/platform/surface/Kconfig b/drivers/platform/surface/Kconfig index 463f1ec5c14e..eb79fbed8059 100644 --- a/drivers/platform/surface/Kconfig +++ b/drivers/platform/surface/Kconfig @@ -28,13 +28,6 @@ config SURFACE3_WMI To compile this driver as a module, choose M here: the module will be called surface3-wmi. -config SURFACE_3_BUTTON - tristate "Power/home/volume buttons driver for Microsoft Surface 3 tablet" - depends on ACPI - depends on KEYBOARD_GPIO && I2C - help - This driver handles the power/home/volume buttons on the Microsoft Surface 3 tablet. - config SURFACE_3_POWER_OPREGION tristate "Surface 3 battery platform operation region support" depends on ACPI diff --git a/drivers/platform/surface/Makefile b/drivers/platform/surface/Makefile index 32889482de55..0fc9cd3e4dd9 100644 --- a/drivers/platform/surface/Makefile +++ b/drivers/platform/surface/Makefile @@ -5,7 +5,6 @@ # obj-$(CONFIG_SURFACE3_WMI) += surface3-wmi.o -obj-$(CONFIG_SURFACE_3_BUTTON) += surface3_button.o obj-$(CONFIG_SURFACE_3_POWER_OPREGION) += surface3_power.o obj-$(CONFIG_SURFACE_ACPI_NOTIFY) += surface_acpi_notify.o obj-$(CONFIG_SURFACE_AGGREGATOR) += aggregator/ diff --git a/drivers/platform/surface/surface3_button.c b/drivers/platform/surface/surface3_button.c deleted file mode 100644 index 48d77e7aae76..000000000000 --- a/drivers/platform/surface/surface3_button.c +++ /dev/null @@ -1,247 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-only -/* - * Supports for the button array on the Surface tablets. - * - * (C) Copyright 2016 Red Hat, Inc - * - * Based on soc_button_array.c: - * - * {C} Copyright 2014 Intel Corporation - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - - -#define SURFACE_BUTTON_OBJ_NAME "TEV2" -#define MAX_NBUTTONS 4 - -/* - * Some of the buttons like volume up/down are auto repeat, while others - * are not. To support both, we register two platform devices, and put - * buttons into them based on whether the key should be auto repeat. - */ -#define BUTTON_TYPES 2 - -/* - * Power button, Home button, Volume buttons support is supposed to - * be covered by drivers/input/misc/soc_button_array.c, which is implemented - * according to "Windows ACPI Design Guide for SoC Platforms". - * However surface 3 seems not to obey the specs, instead it uses - * device TEV2(MSHW0028) for declaring the GPIOs. The gpios are also slightly - * different in which the Home button is active high. - * Compared to surfacepro3_button.c which also handles MSHW0028, the Surface 3 - * is a reduce platform and thus uses GPIOs, not ACPI events. - * We choose an I2C driver here because we need to access the resources - * declared under the device node, while surfacepro3_button.c only needs - * the ACPI companion node. - */ -static const struct acpi_device_id surface3_acpi_match[] = { - { "MSHW0028", 0 }, - { } -}; -MODULE_DEVICE_TABLE(acpi, surface3_acpi_match); - -struct surface3_button_info { - const char *name; - int acpi_index; - unsigned int event_type; - unsigned int event_code; - bool autorepeat; - bool wakeup; - bool active_low; -}; - -struct surface3_button_data { - struct platform_device *children[BUTTON_TYPES]; -}; - -/* - * Get the Nth GPIO number from the ACPI object. - */ -static int surface3_button_lookup_gpio(struct device *dev, int acpi_index) -{ - struct gpio_desc *desc; - int gpio; - - desc = gpiod_get_index(dev, NULL, acpi_index, GPIOD_ASIS); - if (IS_ERR(desc)) - return PTR_ERR(desc); - - gpio = desc_to_gpio(desc); - - gpiod_put(desc); - - return gpio; -} - -static struct platform_device * -surface3_button_device_create(struct i2c_client *client, - const struct surface3_button_info *button_info, - bool autorepeat) -{ - const struct surface3_button_info *info; - struct platform_device *pd; - struct gpio_keys_button *gpio_keys; - struct gpio_keys_platform_data *gpio_keys_pdata; - int n_buttons = 0; - int gpio; - int error; - - gpio_keys_pdata = devm_kzalloc(&client->dev, - sizeof(*gpio_keys_pdata) + - sizeof(*gpio_keys) * MAX_NBUTTONS, - GFP_KERNEL); - if (!gpio_keys_pdata) - return ERR_PTR(-ENOMEM); - - gpio_keys = (void *)(gpio_keys_pdata + 1); - - for (info = button_info; info->name; info++) { - if (info->autorepeat != autorepeat) - continue; - - gpio = surface3_button_lookup_gpio(&client->dev, - info->acpi_index); - if (!gpio_is_valid(gpio)) - continue; - - gpio_keys[n_buttons].type = info->event_type; - gpio_keys[n_buttons].code = info->event_code; - gpio_keys[n_buttons].gpio = gpio; - gpio_keys[n_buttons].active_low = info->active_low; - gpio_keys[n_buttons].desc = info->name; - gpio_keys[n_buttons].wakeup = info->wakeup; - n_buttons++; - } - - if (n_buttons == 0) { - error = -ENODEV; - goto err_free_mem; - } - - gpio_keys_pdata->buttons = gpio_keys; - gpio_keys_pdata->nbuttons = n_buttons; - gpio_keys_pdata->rep = autorepeat; - - pd = platform_device_alloc("gpio-keys", PLATFORM_DEVID_AUTO); - if (!pd) { - error = -ENOMEM; - goto err_free_mem; - } - - error = platform_device_add_data(pd, gpio_keys_pdata, - sizeof(*gpio_keys_pdata)); - if (error) - goto err_free_pdev; - - error = platform_device_add(pd); - if (error) - goto err_free_pdev; - - return pd; - -err_free_pdev: - platform_device_put(pd); -err_free_mem: - devm_kfree(&client->dev, gpio_keys_pdata); - return ERR_PTR(error); -} - -static int surface3_button_remove(struct i2c_client *client) -{ - struct surface3_button_data *priv = i2c_get_clientdata(client); - - int i; - - for (i = 0; i < BUTTON_TYPES; i++) - if (priv->children[i]) - platform_device_unregister(priv->children[i]); - - return 0; -} - -static struct surface3_button_info surface3_button_surface3[] = { - { "power", 0, EV_KEY, KEY_POWER, false, true, true }, - { "home", 1, EV_KEY, KEY_LEFTMETA, false, true, false }, - { "volume_up", 2, EV_KEY, KEY_VOLUMEUP, true, false, true }, - { "volume_down", 3, EV_KEY, KEY_VOLUMEDOWN, true, false, true }, - { } -}; - -static int surface3_button_probe(struct i2c_client *client, - const struct i2c_device_id *id) -{ - struct device *dev = &client->dev; - struct surface3_button_data *priv; - struct platform_device *pd; - int i; - int error; - - if (strncmp(acpi_device_bid(ACPI_COMPANION(&client->dev)), - SURFACE_BUTTON_OBJ_NAME, - strlen(SURFACE_BUTTON_OBJ_NAME))) - return -ENODEV; - - error = gpiod_count(dev, NULL); - if (error < 0) { - dev_dbg(dev, "no GPIO attached, ignoring...\n"); - return error; - } - - priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); - if (!priv) - return -ENOMEM; - - i2c_set_clientdata(client, priv); - - for (i = 0; i < BUTTON_TYPES; i++) { - pd = surface3_button_device_create(client, - surface3_button_surface3, - i == 0); - if (IS_ERR(pd)) { - error = PTR_ERR(pd); - if (error != -ENODEV) { - surface3_button_remove(client); - return error; - } - continue; - } - - priv->children[i] = pd; - } - - if (!priv->children[0] && !priv->children[1]) - return -ENODEV; - - return 0; -} - -static const struct i2c_device_id surface3_id[] = { - { } -}; -MODULE_DEVICE_TABLE(i2c, surface3_id); - -static struct i2c_driver surface3_driver = { - .probe = surface3_button_probe, - .remove = surface3_button_remove, - .id_table = surface3_id, - .driver = { - .name = "surface3", - .acpi_match_table = ACPI_PTR(surface3_acpi_match), - }, -}; -module_i2c_driver(surface3_driver); - -MODULE_AUTHOR("Benjamin Tissoires "); -MODULE_DESCRIPTION("surface3 button array driver"); -MODULE_LICENSE("GPL v2"); From f6d92cfc79e830d9976c69e80f97d80bae7c9c6c Mon Sep 17 00:00:00 2001 From: "David E. Box" Date: Thu, 24 Feb 2022 17:24:56 -0800 Subject: [PATCH 111/137] tools arch x86: Add Intel SDSi provisiong tool Add tool for key certificate and activation payload provisioning on Intel CPUs supporting Software Defined Silicon (SDSi). Signed-off-by: David E. Box Link: https://lore.kernel.org/r/20220225012457.1661574-1-david.e.box@linux.intel.com Reviewed-by: Hans de Goede Signed-off-by: Hans de Goede --- MAINTAINERS | 1 + tools/arch/x86/intel_sdsi/Makefile | 21 + tools/arch/x86/intel_sdsi/intel_sdsi.c | 558 +++++++++++++++++++++++++ 3 files changed, 580 insertions(+) create mode 100644 tools/arch/x86/intel_sdsi/Makefile create mode 100644 tools/arch/x86/intel_sdsi/intel_sdsi.c diff --git a/MAINTAINERS b/MAINTAINERS index a419a6938786..b1281f44a5a2 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -9881,6 +9881,7 @@ INTEL SDSI DRIVER M: David E. Box S: Supported F: drivers/platform/x86/intel/sdsi.c +F: tools/arch/x86/intel_sdsi/ INTEL SKYLAKE INT3472 ACPI DEVICE DRIVER M: Daniel Scally diff --git a/tools/arch/x86/intel_sdsi/Makefile b/tools/arch/x86/intel_sdsi/Makefile new file mode 100644 index 000000000000..5de2288cda79 --- /dev/null +++ b/tools/arch/x86/intel_sdsi/Makefile @@ -0,0 +1,21 @@ +# SPDX-License-Identifier: GPL-2.0 +# Makefile for Intel Software Defined Silicon provisioning tool + +intel_sdsi: intel_sdsi.c + +CFLAGS = -Wextra + +BINDIR ?= /usr/sbin + +override CFLAGS += -O2 -Wall + +%: %.c + $(CC) $(CFLAGS) -o $@ $< $(LDFLAGS) + +.PHONY : clean +clean : + @rm -f intel_sdsi + +install : intel_sdsi + install -d $(DESTDIR)$(BINDIR) + install -m 755 -p intel_sdsi $(DESTDIR)$(BINDIR)/intel_sdsi diff --git a/tools/arch/x86/intel_sdsi/intel_sdsi.c b/tools/arch/x86/intel_sdsi/intel_sdsi.c new file mode 100644 index 000000000000..c0e2f2349db4 --- /dev/null +++ b/tools/arch/x86/intel_sdsi/intel_sdsi.c @@ -0,0 +1,558 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * sdsi: Intel Software Defined Silicon tool for provisioning certificates + * and activation payloads on supported cpus. + * + * See https://github.com/intel/intel-sdsi/blob/master/os-interface.rst + * for register descriptions. + * + * Copyright (C) 2022 Intel Corporation. All rights reserved. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define SDSI_DEV "intel_vsec.sdsi" +#define AUX_DEV_PATH "/sys/bus/auxiliary/devices/" +#define SDSI_PATH (AUX_DEV_DIR SDSI_DEV) +#define GUID 0x6dd191 +#define REGISTERS_MIN_SIZE 72 + +#define __round_mask(x, y) ((__typeof__(x))((y) - 1)) +#define round_up(x, y) ((((x) - 1) | __round_mask(x, y)) + 1) + +struct enabled_features { + uint64_t reserved:3; + uint64_t sdsi:1; + uint64_t reserved1:60; +}; + +struct auth_fail_count { + uint64_t key_failure_count:3; + uint64_t key_failure_threshold:3; + uint64_t auth_failure_count:3; + uint64_t auth_failure_threshold:3; + uint64_t reserved:52; +}; + +struct availability { + uint64_t reserved:48; + uint64_t available:3; + uint64_t threshold:3; +}; + +struct sdsi_regs { + uint64_t ppin; + uint64_t reserved; + struct enabled_features en_features; + uint64_t reserved1; + struct auth_fail_count auth_fail_count; + struct availability prov_avail; + uint64_t reserved2; + uint64_t reserved3; + uint64_t socket_id; +}; + +struct sdsi_dev { + struct sdsi_regs regs; + char *dev_name; + char *dev_path; + int guid; +}; + +enum command { + CMD_NONE, + CMD_SOCKET_INFO, + CMD_DUMP_CERT, + CMD_PROV_AKC, + CMD_PROV_CAP, +}; + +static void sdsi_list_devices(void) +{ + struct dirent *entry; + DIR *aux_dir; + bool found = false; + + aux_dir = opendir(AUX_DEV_PATH); + if (!aux_dir) { + fprintf(stderr, "Cannot open directory %s\n", AUX_DEV_PATH); + return; + } + + while ((entry = readdir(aux_dir))) { + if (!strncmp(SDSI_DEV, entry->d_name, strlen(SDSI_DEV))) { + found = true; + printf("%s\n", entry->d_name); + } + } + + if (!found) + fprintf(stderr, "No sdsi devices found.\n"); +} + +static int sdsi_update_registers(struct sdsi_dev *s) +{ + FILE *regs_ptr; + int ret; + + memset(&s->regs, 0, sizeof(s->regs)); + + /* Open the registers file */ + ret = chdir(s->dev_path); + if (ret == -1) { + perror("chdir"); + return ret; + } + + regs_ptr = fopen("registers", "r"); + if (!regs_ptr) { + perror("Could not open 'registers' file"); + return -1; + } + + if (s->guid != GUID) { + fprintf(stderr, "Unrecognized guid, 0x%x\n", s->guid); + fclose(regs_ptr); + return -1; + } + + /* Update register info for this guid */ + ret = fread(&s->regs, sizeof(uint8_t), sizeof(s->regs), regs_ptr); + if (ret != sizeof(s->regs)) { + fprintf(stderr, "Could not read 'registers' file\n"); + fclose(regs_ptr); + return -1; + } + + fclose(regs_ptr); + + return 0; +} + +static int sdsi_read_reg(struct sdsi_dev *s) +{ + int ret; + + ret = sdsi_update_registers(s); + if (ret) + return ret; + + /* Print register info for this guid */ + printf("\n"); + printf("Socket information for device %s\n", s->dev_name); + printf("\n"); + printf("PPIN: 0x%lx\n", s->regs.ppin); + printf("Enabled Features\n"); + printf(" SDSi: %s\n", !!s->regs.en_features.sdsi ? "Enabled" : "Disabled"); + printf("Authorization Failure Count\n"); + printf(" AKC Failure Count: %d\n", s->regs.auth_fail_count.key_failure_count); + printf(" AKC Failure Threshold: %d\n", s->regs.auth_fail_count.key_failure_threshold); + printf(" CAP Failure Count: %d\n", s->regs.auth_fail_count.auth_failure_count); + printf(" CAP Failure Threshold: %d\n", s->regs.auth_fail_count.auth_failure_threshold); + printf("Provisioning Availability\n"); + printf(" Updates Available: %d\n", s->regs.prov_avail.available); + printf(" Updates Threshold: %d\n", s->regs.prov_avail.threshold); + printf("Socket ID: %ld\n", s->regs.socket_id & 0xF); + + return 0; +} + +static int sdsi_certificate_dump(struct sdsi_dev *s) +{ + uint64_t state_certificate[512] = {0}; + bool first_instance; + uint64_t previous; + FILE *cert_ptr; + int i, ret, size; + + ret = sdsi_update_registers(s); + if (ret) + return ret; + + if (!s->regs.en_features.sdsi) { + fprintf(stderr, "SDSi feature is present but not enabled."); + fprintf(stderr, " Unable to read state certificate"); + return -1; + } + + ret = chdir(s->dev_path); + if (ret == -1) { + perror("chdir"); + return ret; + } + + cert_ptr = fopen("state_certificate", "r"); + if (!cert_ptr) { + perror("Could not open 'state_certificate' file"); + return -1; + } + + size = fread(state_certificate, 1, sizeof(state_certificate), cert_ptr); + if (!size) { + fprintf(stderr, "Could not read 'state_certificate' file\n"); + fclose(cert_ptr); + return -1; + } + + printf("%3d: 0x%lx\n", 0, state_certificate[0]); + previous = state_certificate[0]; + first_instance = true; + + for (i = 1; i < (int)(round_up(size, sizeof(uint64_t))/sizeof(uint64_t)); i++) { + if (state_certificate[i] == previous) { + if (first_instance) { + puts("*"); + first_instance = false; + } + continue; + } + printf("%3d: 0x%lx\n", i, state_certificate[i]); + previous = state_certificate[i]; + first_instance = true; + } + printf("%3d\n", i); + + fclose(cert_ptr); + + return 0; +} + +static int sdsi_provision(struct sdsi_dev *s, char *bin_file, enum command command) +{ + int bin_fd, prov_fd, size, ret; + char buf[4096] = { 0 }; + char cap[] = "provision_cap"; + char akc[] = "provision_akc"; + char *prov_file; + + if (!bin_file) { + fprintf(stderr, "No binary file provided\n"); + return -1; + } + + /* Open the binary */ + bin_fd = open(bin_file, O_RDONLY); + if (bin_fd == -1) { + fprintf(stderr, "Could not open file %s: %s\n", bin_file, strerror(errno)); + return bin_fd; + } + + prov_file = (command == CMD_PROV_AKC) ? akc : cap; + + ret = chdir(s->dev_path); + if (ret == -1) { + perror("chdir"); + close(bin_fd); + return ret; + } + + /* Open the provision file */ + prov_fd = open(prov_file, O_WRONLY); + if (prov_fd == -1) { + fprintf(stderr, "Could not open file %s: %s\n", prov_file, strerror(errno)); + close(bin_fd); + return prov_fd; + } + + /* Read the binary file into the buffer */ + size = read(bin_fd, buf, 4096); + if (size == -1) { + close(bin_fd); + close(prov_fd); + return -1; + } + + ret = write(prov_fd, buf, size); + if (ret == -1) { + close(bin_fd); + close(prov_fd); + perror("Provisioning failed"); + return ret; + } + + printf("Provisioned %s file %s successfully\n", prov_file, bin_file); + + close(bin_fd); + close(prov_fd); + + return 0; +} + +static int sdsi_provision_akc(struct sdsi_dev *s, char *bin_file) +{ + int ret; + + ret = sdsi_update_registers(s); + if (ret) + return ret; + + if (!s->regs.en_features.sdsi) { + fprintf(stderr, "SDSi feature is present but not enabled. Unable to provision"); + return -1; + } + + if (!s->regs.prov_avail.available) { + fprintf(stderr, "Maximum number of updates (%d) has been reached.\n", + s->regs.prov_avail.threshold); + return -1; + } + + if (s->regs.auth_fail_count.key_failure_count == + s->regs.auth_fail_count.key_failure_threshold) { + fprintf(stderr, "Maximum number of AKC provision failures (%d) has been reached.\n", + s->regs.auth_fail_count.key_failure_threshold); + fprintf(stderr, "Power cycle the system to reset the counter\n"); + return -1; + } + + return sdsi_provision(s, bin_file, CMD_PROV_AKC); +} + +static int sdsi_provision_cap(struct sdsi_dev *s, char *bin_file) +{ + int ret; + + ret = sdsi_update_registers(s); + if (ret) + return ret; + + if (!s->regs.en_features.sdsi) { + fprintf(stderr, "SDSi feature is present but not enabled. Unable to provision"); + return -1; + } + + if (!s->regs.prov_avail.available) { + fprintf(stderr, "Maximum number of updates (%d) has been reached.\n", + s->regs.prov_avail.threshold); + return -1; + } + + if (s->regs.auth_fail_count.auth_failure_count == + s->regs.auth_fail_count.auth_failure_threshold) { + fprintf(stderr, "Maximum number of CAP provision failures (%d) has been reached.\n", + s->regs.auth_fail_count.auth_failure_threshold); + fprintf(stderr, "Power cycle the system to reset the counter\n"); + return -1; + } + + return sdsi_provision(s, bin_file, CMD_PROV_CAP); +} + +static int read_sysfs_data(const char *file, int *value) +{ + char buff[16]; + FILE *fp; + + fp = fopen(file, "r"); + if (!fp) { + perror(file); + return -1; + } + + if (!fgets(buff, 16, fp)) { + fprintf(stderr, "Failed to read file '%s'", file); + fclose(fp); + return -1; + } + + fclose(fp); + *value = strtol(buff, NULL, 0); + + return 0; +} + +static struct sdsi_dev *sdsi_create_dev(char *dev_no) +{ + int dev_name_len = sizeof(SDSI_DEV) + strlen(dev_no) + 1; + struct sdsi_dev *s; + int guid; + DIR *dir; + + s = (struct sdsi_dev *)malloc(sizeof(*s)); + if (!s) { + perror("malloc"); + return NULL; + } + + s->dev_name = (char *)malloc(sizeof(SDSI_DEV) + strlen(dev_no) + 1); + if (!s->dev_name) { + perror("malloc"); + free(s); + return NULL; + } + + snprintf(s->dev_name, dev_name_len, "%s.%s", SDSI_DEV, dev_no); + + s->dev_path = (char *)malloc(sizeof(AUX_DEV_PATH) + dev_name_len); + if (!s->dev_path) { + perror("malloc"); + free(s->dev_name); + free(s); + return NULL; + } + + snprintf(s->dev_path, sizeof(AUX_DEV_PATH) + dev_name_len, "%s%s", AUX_DEV_PATH, + s->dev_name); + dir = opendir(s->dev_path); + if (!dir) { + fprintf(stderr, "Could not open directory '%s': %s\n", s->dev_path, + strerror(errno)); + free(s->dev_path); + free(s->dev_name); + free(s); + return NULL; + } + + if (chdir(s->dev_path) == -1) { + perror("chdir"); + free(s->dev_path); + free(s->dev_name); + free(s); + return NULL; + } + + if (read_sysfs_data("guid", &guid)) { + free(s->dev_path); + free(s->dev_name); + free(s); + return NULL; + } + + s->guid = guid; + + return s; +} + +static void sdsi_free_dev(struct sdsi_dev *s) +{ + free(s->dev_path); + free(s->dev_name); + free(s); +} + +static void usage(char *prog) +{ + printf("Usage: %s [-l] [-d DEVNO [-iD] [-a FILE] [-c FILE]]\n", prog); +} + +static void show_help(void) +{ + printf("Commands:\n"); + printf(" %-18s\t%s\n", "-l, --list", "list available sdsi devices"); + printf(" %-18s\t%s\n", "-d, --devno DEVNO", "sdsi device number"); + printf(" %-18s\t%s\n", "-i --info", "show socket information"); + printf(" %-18s\t%s\n", "-D --dump", "dump state certificate data"); + printf(" %-18s\t%s\n", "-a --akc FILE", "provision socket with AKC FILE"); + printf(" %-18s\t%s\n", "-c --cap FILE>", "provision socket with CAP FILE"); +} + +int main(int argc, char *argv[]) +{ + char bin_file[PATH_MAX], *dev_no = NULL; + char *progname; + enum command command = CMD_NONE; + struct sdsi_dev *s; + int ret = 0, opt; + int option_index = 0; + + static struct option long_options[] = { + {"akc", required_argument, 0, 'a'}, + {"cap", required_argument, 0, 'c'}, + {"devno", required_argument, 0, 'd'}, + {"dump", no_argument, 0, 'D'}, + {"help", no_argument, 0, 'h'}, + {"info", no_argument, 0, 'i'}, + {"list", no_argument, 0, 'l'}, + {0, 0, 0, 0 } + }; + + + progname = argv[0]; + + while ((opt = getopt_long_only(argc, argv, "+a:c:d:Da:c:h", long_options, + &option_index)) != -1) { + switch (opt) { + case 'd': + dev_no = optarg; + break; + case 'l': + sdsi_list_devices(); + return 0; + case 'i': + command = CMD_SOCKET_INFO; + break; + case 'D': + command = CMD_DUMP_CERT; + break; + case 'a': + case 'c': + if (!access(optarg, F_OK) == 0) { + fprintf(stderr, "Could not open file '%s': %s\n", optarg, + strerror(errno)); + return -1; + } + + if (!realpath(optarg, bin_file)) { + perror("realpath"); + return -1; + } + + command = (opt == 'a') ? CMD_PROV_AKC : CMD_PROV_CAP; + break; + case 'h': + usage(progname); + show_help(); + return 0; + default: + usage(progname); + return -1; + } + } + + if (!dev_no) { + if (command != CMD_NONE) + fprintf(stderr, "Missing device number, DEVNO, for this command\n"); + usage(progname); + return -1; + } + + s = sdsi_create_dev(dev_no); + if (!s) + return -1; + + /* Run the command */ + switch (command) { + case CMD_NONE: + fprintf(stderr, "Missing command for device %s\n", dev_no); + usage(progname); + break; + case CMD_SOCKET_INFO: + ret = sdsi_read_reg(s); + break; + case CMD_DUMP_CERT: + ret = sdsi_certificate_dump(s); + break; + case CMD_PROV_AKC: + ret = sdsi_provision_akc(s, bin_file); + break; + case CMD_PROV_CAP: + ret = sdsi_provision_cap(s, bin_file); + break; + } + + + sdsi_free_dev(s); + + return ret; +} From a3d38af35d61a1e2045b73b4e43fa5ffb9d71008 Mon Sep 17 00:00:00 2001 From: "David E. Box" Date: Thu, 24 Feb 2022 17:24:57 -0800 Subject: [PATCH 112/137] selftests: sdsi: test sysfs setup Tests file configuration and error handling of the Intel Software Defined Silicon sysfs ABI. Signed-off-by: David E. Box Link: https://lore.kernel.org/r/20220225012457.1661574-2-david.e.box@linux.intel.com Reviewed-by: Hans de Goede Signed-off-by: Hans de Goede --- MAINTAINERS | 1 + tools/testing/selftests/drivers/sdsi/sdsi.sh | 25 ++ .../selftests/drivers/sdsi/sdsi_test.py | 226 ++++++++++++++++++ 3 files changed, 252 insertions(+) create mode 100755 tools/testing/selftests/drivers/sdsi/sdsi.sh create mode 100644 tools/testing/selftests/drivers/sdsi/sdsi_test.py diff --git a/MAINTAINERS b/MAINTAINERS index b1281f44a5a2..a09e383d9d11 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -9882,6 +9882,7 @@ M: David E. Box S: Supported F: drivers/platform/x86/intel/sdsi.c F: tools/arch/x86/intel_sdsi/ +F: tools/testing/selftests/drivers/sdsi/ INTEL SKYLAKE INT3472 ACPI DEVICE DRIVER M: Daniel Scally diff --git a/tools/testing/selftests/drivers/sdsi/sdsi.sh b/tools/testing/selftests/drivers/sdsi/sdsi.sh new file mode 100755 index 000000000000..9b84b9b82b49 --- /dev/null +++ b/tools/testing/selftests/drivers/sdsi/sdsi.sh @@ -0,0 +1,25 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-2.0 +# Runs tests for the intel_sdsi driver + +if ! command -v python3 > /dev/null 2>&1; then + echo "drivers/sdsi: [SKIP] python3 not installed" + exit 77 +fi + +if ! python3 -c "import pytest" > /dev/null 2>&1; then + echo "drivers/sdsi: [SKIP] pytest module not installed" + exit 77 +fi + +if ! /sbin/modprobe -q -r intel_sdsi; then + echo "drivers/sdsi: [SKIP]" + exit 77 +fi + +if /sbin/modprobe -q intel_sdsi && python3 -m pytest sdsi_test.py; then + echo "drivers/sdsi: [OK]" +else + echo "drivers/sdsi: [FAIL]" + exit 1 +fi diff --git a/tools/testing/selftests/drivers/sdsi/sdsi_test.py b/tools/testing/selftests/drivers/sdsi/sdsi_test.py new file mode 100644 index 000000000000..5efb29feee70 --- /dev/null +++ b/tools/testing/selftests/drivers/sdsi/sdsi_test.py @@ -0,0 +1,226 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0 + +from struct import pack +from time import sleep + +import errno +import glob +import os +import subprocess + +try: + import pytest +except ImportError: + print("Unable to import pytest python module.") + print("\nIf not already installed, you may do so with:") + print("\t\tpip3 install pytest") + exit(1) + +SOCKETS = glob.glob('/sys/bus/auxiliary/devices/intel_vsec.sdsi.*') +NUM_SOCKETS = len(SOCKETS) + +MODULE_NAME = 'intel_sdsi' +DEV_PREFIX = 'intel_vsec.sdsi' +CLASS_DIR = '/sys/bus/auxiliary/devices' +GUID = "0x6dd191" + +def read_bin_file(file): + with open(file, mode='rb') as f: + content = f.read() + return content + +def get_dev_file_path(socket, file): + return CLASS_DIR + '/' + DEV_PREFIX + '.' + str(socket) + '/' + file + +def kmemleak_enabled(): + kmemleak = "/sys/kernel/debug/kmemleak" + return os.path.isfile(kmemleak) + +class TestSDSiDriver: + def test_driver_loaded(self): + lsmod_p = subprocess.Popen(('lsmod'), stdout=subprocess.PIPE) + result = subprocess.check_output(('grep', '-q', MODULE_NAME), stdin=lsmod_p.stdout) + +@pytest.mark.parametrize('socket', range(0, NUM_SOCKETS)) +class TestSDSiFilesClass: + + def read_value(self, file): + f = open(file, "r") + value = f.read().strip("\n") + return value + + def get_dev_folder(self, socket): + return CLASS_DIR + '/' + DEV_PREFIX + '.' + str(socket) + '/' + + def test_sysfs_files_exist(self, socket): + folder = self.get_dev_folder(socket) + print (folder) + assert os.path.isfile(folder + "guid") == True + assert os.path.isfile(folder + "provision_akc") == True + assert os.path.isfile(folder + "provision_cap") == True + assert os.path.isfile(folder + "state_certificate") == True + assert os.path.isfile(folder + "registers") == True + + def test_sysfs_file_permissions(self, socket): + folder = self.get_dev_folder(socket) + mode = os.stat(folder + "guid").st_mode & 0o777 + assert mode == 0o444 # Read all + mode = os.stat(folder + "registers").st_mode & 0o777 + assert mode == 0o400 # Read owner + mode = os.stat(folder + "provision_akc").st_mode & 0o777 + assert mode == 0o200 # Read owner + mode = os.stat(folder + "provision_cap").st_mode & 0o777 + assert mode == 0o200 # Read owner + mode = os.stat(folder + "state_certificate").st_mode & 0o777 + assert mode == 0o400 # Read owner + + def test_sysfs_file_ownership(self, socket): + folder = self.get_dev_folder(socket) + + st = os.stat(folder + "guid") + assert st.st_uid == 0 + assert st.st_gid == 0 + + st = os.stat(folder + "registers") + assert st.st_uid == 0 + assert st.st_gid == 0 + + st = os.stat(folder + "provision_akc") + assert st.st_uid == 0 + assert st.st_gid == 0 + + st = os.stat(folder + "provision_cap") + assert st.st_uid == 0 + assert st.st_gid == 0 + + st = os.stat(folder + "state_certificate") + assert st.st_uid == 0 + assert st.st_gid == 0 + + def test_sysfs_file_sizes(self, socket): + folder = self.get_dev_folder(socket) + + if self.read_value(folder + "guid") == GUID: + st = os.stat(folder + "registers") + assert st.st_size == 72 + + st = os.stat(folder + "provision_akc") + assert st.st_size == 1024 + + st = os.stat(folder + "provision_cap") + assert st.st_size == 1024 + + st = os.stat(folder + "state_certificate") + assert st.st_size == 4096 + + def test_no_seek_allowed(self, socket): + folder = self.get_dev_folder(socket) + rand_file = bytes(os.urandom(8)) + + f = open(folder + "provision_cap", "wb", 0) + f.seek(1) + with pytest.raises(OSError) as error: + f.write(rand_file) + assert error.value.errno == errno.ESPIPE + f.close() + + f = open(folder + "provision_akc", "wb", 0) + f.seek(1) + with pytest.raises(OSError) as error: + f.write(rand_file) + assert error.value.errno == errno.ESPIPE + f.close() + + def test_registers_seek(self, socket): + folder = self.get_dev_folder(socket) + + # Check that the value read from an offset of the entire + # file is none-zero and the same as the value read + # from seeking to the same location + f = open(folder + "registers", "rb") + data = f.read() + f.seek(64) + id = f.read() + assert id != bytes(0) + assert data[64:] == id + f.close() + +@pytest.mark.parametrize('socket', range(0, NUM_SOCKETS)) +class TestSDSiMailboxCmdsClass: + def test_provision_akc_eoverflow_1017_bytes(self, socket): + + # The buffer for writes is 1k, of with 8 bytes must be + # reserved for the command, leaving 1016 bytes max. + # Check that we get an overflow error for 1017 bytes. + node = get_dev_file_path(socket, "provision_akc") + rand_file = bytes(os.urandom(1017)) + + f = open(node, 'wb', 0) + with pytest.raises(OSError) as error: + f.write(rand_file) + assert error.value.errno == errno.EOVERFLOW + f.close() + +@pytest.mark.parametrize('socket', range(0, NUM_SOCKETS)) +class TestSdsiDriverLocksClass: + def test_enodev_when_pci_device_removed(self, socket): + node = get_dev_file_path(socket, "provision_akc") + dev_name = DEV_PREFIX + '.' + str(socket) + driver_dir = CLASS_DIR + '/' + dev_name + "/driver/" + rand_file = bytes(os.urandom(8)) + + f = open(node, 'wb', 0) + g = open(node, 'wb', 0) + + with open(driver_dir + 'unbind', 'w') as k: + print(dev_name, file = k) + + with pytest.raises(OSError) as error: + f.write(rand_file) + assert error.value.errno == errno.ENODEV + + with pytest.raises(OSError) as error: + g.write(rand_file) + assert error.value.errno == errno.ENODEV + + f.close() + g.close() + + # Short wait needed to allow file to close before pulling driver + sleep(1) + + p = subprocess.Popen(('modprobe', '-r', 'intel_sdsi')) + p.wait() + p = subprocess.Popen(('modprobe', '-r', 'intel_vsec')) + p.wait() + p = subprocess.Popen(('modprobe', 'intel_vsec')) + p.wait() + + # Short wait needed to allow driver time to get inserted + # before continuing tests + sleep(1) + + def test_memory_leak(self, socket): + if not kmemleak_enabled(): + pytest.skip("kmemleak not enabled in kernel") + + dev_name = DEV_PREFIX + '.' + str(socket) + driver_dir = CLASS_DIR + '/' + dev_name + "/driver/" + + with open(driver_dir + 'unbind', 'w') as k: + print(dev_name, file = k) + + sleep(1) + + subprocess.check_output(('modprobe', '-r', 'intel_sdsi')) + subprocess.check_output(('modprobe', '-r', 'intel_vsec')) + + with open('/sys/kernel/debug/kmemleak', 'w') as f: + print('scan', file = f) + sleep(5) + + assert os.stat('/sys/kernel/debug/kmemleak').st_size == 0 + + subprocess.check_output(('modprobe', 'intel_vsec')) + sleep(1) From e1c21608e3cfc4b44ecdf04e12986b6564667095 Mon Sep 17 00:00:00 2001 From: Mark Pearson Date: Fri, 25 Feb 2022 13:25:05 -0500 Subject: [PATCH 113/137] platform/x86: thinkpad_acpi: Add PSC mode support The Lenovo AMD platforms use PSC mode for providing platform profile support. Detect if PSC mode is available and add support for setting the different profile modes appropriately. Note - if both MMC mode and PSC mode are available then MMC mode will be used in preference. Tested on T14 G1 AMD and T14s G2 AMD. Signed-off-by: Mark Pearson Link: https://lore.kernel.org/r/20220225182505.7234-1-markpearson@lenovo.com Reviewed-by: Hans de Goede Signed-off-by: Hans de Goede --- drivers/platform/x86/thinkpad_acpi.c | 172 ++++++++++++++++++--------- 1 file changed, 119 insertions(+), 53 deletions(-) diff --git a/drivers/platform/x86/thinkpad_acpi.c b/drivers/platform/x86/thinkpad_acpi.c index d0599e8a7b4d..d9117f824ce9 100644 --- a/drivers/platform/x86/thinkpad_acpi.c +++ b/drivers/platform/x86/thinkpad_acpi.c @@ -10130,6 +10130,7 @@ static struct ibm_struct proxsensor_driver_data = { #define DYTC_CMD_FUNC_CAP 3 /* To get DYTC capabilities */ #define DYTC_FC_MMC 27 /* MMC Mode supported */ +#define DYTC_FC_PSC 29 /* PSC Mode supported */ #define DYTC_GET_FUNCTION_BIT 8 /* Bits 8-11 - function setting */ #define DYTC_GET_MODE_BIT 12 /* Bits 12-15 - mode setting */ @@ -10140,12 +10141,17 @@ static struct ibm_struct proxsensor_driver_data = { #define DYTC_FUNCTION_STD 0 /* Function = 0, standard mode */ #define DYTC_FUNCTION_CQL 1 /* Function = 1, lap mode */ -#define DYTC_FUNCTION_MMC 11 /* Function = 11, desk mode */ +#define DYTC_FUNCTION_MMC 11 /* Function = 11, MMC mode */ +#define DYTC_FUNCTION_PSC 13 /* Function = 13, PSC mode */ -#define DYTC_MODE_PERFORM 2 /* High power mode aka performance */ -#define DYTC_MODE_LOWPOWER 3 /* Low power mode */ -#define DYTC_MODE_BALANCE 0xF /* Default mode aka balanced */ -#define DYTC_MODE_MMC_BALANCE 0 /* Default mode from MMC_GET, aka balanced */ +#define DYTC_MODE_MMC_PERFORM 2 /* High power mode aka performance */ +#define DYTC_MODE_MMC_LOWPOWER 3 /* Low power mode */ +#define DYTC_MODE_MMC_BALANCE 0xF /* Default mode aka balanced */ +#define DYTC_MODE_MMC_DEFAULT 0 /* Default mode from MMC_GET, aka balanced */ + +#define DYTC_MODE_PSC_LOWPOWER 3 /* Low power mode */ +#define DYTC_MODE_PSC_BALANCE 5 /* Default mode aka balanced */ +#define DYTC_MODE_PSC_PERFORM 7 /* High power mode aka performance */ #define DYTC_ERR_MASK 0xF /* Bits 0-3 in cmd result are the error result */ #define DYTC_ERR_SUCCESS 1 /* CMD completed successful */ @@ -10155,10 +10161,16 @@ static struct ibm_struct proxsensor_driver_data = { (mode) << DYTC_SET_MODE_BIT | \ (on) << DYTC_SET_VALID_BIT) -#define DYTC_DISABLE_CQL DYTC_SET_COMMAND(DYTC_FUNCTION_CQL, DYTC_MODE_BALANCE, 0) +#define DYTC_DISABLE_CQL DYTC_SET_COMMAND(DYTC_FUNCTION_CQL, DYTC_MODE_MMC_BALANCE, 0) +#define DYTC_ENABLE_CQL DYTC_SET_COMMAND(DYTC_FUNCTION_CQL, DYTC_MODE_MMC_BALANCE, 1) -#define DYTC_ENABLE_CQL DYTC_SET_COMMAND(DYTC_FUNCTION_CQL, DYTC_MODE_BALANCE, 1) +enum dytc_profile_funcmode { + DYTC_FUNCMODE_NONE = 0, + DYTC_FUNCMODE_MMC, + DYTC_FUNCMODE_PSC, +}; +static enum dytc_profile_funcmode dytc_profile_available; static enum platform_profile_option dytc_current_profile; static atomic_t dytc_ignore_event = ATOMIC_INIT(0); static DEFINE_MUTEX(dytc_mutex); @@ -10166,19 +10178,37 @@ static bool dytc_mmc_get_available; static int convert_dytc_to_profile(int dytcmode, enum platform_profile_option *profile) { - switch (dytcmode) { - case DYTC_MODE_LOWPOWER: - *profile = PLATFORM_PROFILE_LOW_POWER; - break; - case DYTC_MODE_BALANCE: - case DYTC_MODE_MMC_BALANCE: - *profile = PLATFORM_PROFILE_BALANCED; - break; - case DYTC_MODE_PERFORM: - *profile = PLATFORM_PROFILE_PERFORMANCE; - break; - default: /* Unknown mode */ - return -EINVAL; + if (dytc_profile_available == DYTC_FUNCMODE_MMC) { + switch (dytcmode) { + case DYTC_MODE_MMC_LOWPOWER: + *profile = PLATFORM_PROFILE_LOW_POWER; + break; + case DYTC_MODE_MMC_DEFAULT: + case DYTC_MODE_MMC_BALANCE: + *profile = PLATFORM_PROFILE_BALANCED; + break; + case DYTC_MODE_MMC_PERFORM: + *profile = PLATFORM_PROFILE_PERFORMANCE; + break; + default: /* Unknown mode */ + return -EINVAL; + } + return 0; + } + if (dytc_profile_available == DYTC_FUNCMODE_PSC) { + switch (dytcmode) { + case DYTC_MODE_PSC_LOWPOWER: + *profile = PLATFORM_PROFILE_LOW_POWER; + break; + case DYTC_MODE_PSC_BALANCE: + *profile = PLATFORM_PROFILE_BALANCED; + break; + case DYTC_MODE_PSC_PERFORM: + *profile = PLATFORM_PROFILE_PERFORMANCE; + break; + default: /* Unknown mode */ + return -EINVAL; + } } return 0; } @@ -10187,13 +10217,22 @@ static int convert_profile_to_dytc(enum platform_profile_option profile, int *pe { switch (profile) { case PLATFORM_PROFILE_LOW_POWER: - *perfmode = DYTC_MODE_LOWPOWER; + if (dytc_profile_available == DYTC_FUNCMODE_MMC) + *perfmode = DYTC_MODE_MMC_LOWPOWER; + else if (dytc_profile_available == DYTC_FUNCMODE_PSC) + *perfmode = DYTC_MODE_PSC_LOWPOWER; break; case PLATFORM_PROFILE_BALANCED: - *perfmode = DYTC_MODE_BALANCE; + if (dytc_profile_available == DYTC_FUNCMODE_MMC) + *perfmode = DYTC_MODE_MMC_BALANCE; + else if (dytc_profile_available == DYTC_FUNCMODE_PSC) + *perfmode = DYTC_MODE_PSC_BALANCE; break; case PLATFORM_PROFILE_PERFORMANCE: - *perfmode = DYTC_MODE_PERFORM; + if (dytc_profile_available == DYTC_FUNCMODE_MMC) + *perfmode = DYTC_MODE_MMC_PERFORM; + else if (dytc_profile_available == DYTC_FUNCMODE_PSC) + *perfmode = DYTC_MODE_PSC_PERFORM; break; default: /* Unknown profile */ return -EOPNOTSUPP; @@ -10266,25 +10305,39 @@ static int dytc_profile_set(struct platform_profile_handler *pprof, if (err) return err; - if (profile == PLATFORM_PROFILE_BALANCED) { - /* - * To get back to balanced mode we need to issue a reset command. - * Note we still need to disable CQL mode before hand and re-enable - * it afterwards, otherwise dytc_lapmode gets reset to 0 and stays - * stuck at 0 for aprox. 30 minutes. - */ - err = dytc_cql_command(DYTC_CMD_RESET, &output); - if (err) - goto unlock; - } else { + if (dytc_profile_available == DYTC_FUNCMODE_MMC) { + if (profile == PLATFORM_PROFILE_BALANCED) { + /* + * To get back to balanced mode we need to issue a reset command. + * Note we still need to disable CQL mode before hand and re-enable + * it afterwards, otherwise dytc_lapmode gets reset to 0 and stays + * stuck at 0 for aprox. 30 minutes. + */ + err = dytc_cql_command(DYTC_CMD_RESET, &output); + if (err) + goto unlock; + } else { + int perfmode; + + err = convert_profile_to_dytc(profile, &perfmode); + if (err) + goto unlock; + + /* Determine if we are in CQL mode. This alters the commands we do */ + err = dytc_cql_command(DYTC_SET_COMMAND(DYTC_FUNCTION_MMC, perfmode, 1), + &output); + if (err) + goto unlock; + } + } + if (dytc_profile_available == DYTC_FUNCMODE_PSC) { int perfmode; err = convert_profile_to_dytc(profile, &perfmode); if (err) goto unlock; - /* Determine if we are in CQL mode. This alters the commands we do */ - err = dytc_cql_command(DYTC_SET_COMMAND(DYTC_FUNCTION_MMC, perfmode, 1), &output); + err = dytc_command(DYTC_SET_COMMAND(DYTC_FUNCTION_PSC, perfmode, 1), &output); if (err) goto unlock; } @@ -10302,10 +10355,14 @@ static void dytc_profile_refresh(void) int perfmode; mutex_lock(&dytc_mutex); - if (dytc_mmc_get_available) - err = dytc_command(DYTC_CMD_MMC_GET, &output); - else - err = dytc_cql_command(DYTC_CMD_GET, &output); + if (dytc_profile_available == DYTC_FUNCMODE_MMC) { + if (dytc_mmc_get_available) + err = dytc_command(DYTC_CMD_MMC_GET, &output); + else + err = dytc_cql_command(DYTC_CMD_GET, &output); + } else if (dytc_profile_available == DYTC_FUNCMODE_PSC) + err = dytc_command(DYTC_CMD_GET, &output); + mutex_unlock(&dytc_mutex); if (err) return; @@ -10332,6 +10389,7 @@ static int tpacpi_dytc_profile_init(struct ibm_init_struct *iibm) set_bit(PLATFORM_PROFILE_BALANCED, dytc_profile.choices); set_bit(PLATFORM_PROFILE_PERFORMANCE, dytc_profile.choices); + dytc_profile_available = DYTC_FUNCMODE_NONE; err = dytc_command(DYTC_CMD_QUERY, &output); if (err) return err; @@ -10343,27 +10401,34 @@ static int tpacpi_dytc_profile_init(struct ibm_init_struct *iibm) if (dytc_version < 5) return -ENODEV; - /* Check what capabilities are supported. Currently MMC is needed */ + /* Check what capabilities are supported */ err = dytc_command(DYTC_CMD_FUNC_CAP, &output); if (err) return err; - if (!(output & BIT(DYTC_FC_MMC))) { - dbg_printk(TPACPI_DBG_INIT, " DYTC MMC mode not supported\n"); + + if (test_bit(DYTC_FC_MMC, (void *)&output)) { /* MMC MODE */ + dytc_profile_available = DYTC_FUNCMODE_MMC; + + /* + * Check if MMC_GET functionality available + * Version > 6 and return success from MMC_GET command + */ + dytc_mmc_get_available = false; + if (dytc_version >= 6) { + err = dytc_command(DYTC_CMD_MMC_GET, &output); + if (!err && ((output & DYTC_ERR_MASK) == DYTC_ERR_SUCCESS)) + dytc_mmc_get_available = true; + } + } else if (test_bit(DYTC_FC_PSC, (void *)&output)) { /*PSC MODE */ + dytc_profile_available = DYTC_FUNCMODE_PSC; + } else { + dbg_printk(TPACPI_DBG_INIT, "No DYTC support available\n"); return -ENODEV; } dbg_printk(TPACPI_DBG_INIT, "DYTC version %d: thermal mode available\n", dytc_version); - /* - * Check if MMC_GET functionality available - * Version > 6 and return success from MMC_GET command - */ - dytc_mmc_get_available = false; - if (dytc_version >= 6) { - err = dytc_command(DYTC_CMD_MMC_GET, &output); - if (!err && ((output & DYTC_ERR_MASK) == DYTC_ERR_SUCCESS)) - dytc_mmc_get_available = true; - } + /* Create platform_profile structure and register */ err = platform_profile_register(&dytc_profile); /* @@ -10381,6 +10446,7 @@ static int tpacpi_dytc_profile_init(struct ibm_init_struct *iibm) static void dytc_profile_exit(void) { + dytc_profile_available = DYTC_FUNCMODE_NONE; platform_profile_remove(); } From 243cfa6a6782809975a0002125514a57db7df752 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Mon, 7 Mar 2022 12:54:14 +0200 Subject: [PATCH 114/137] gpiolib: Use list_first_entry()/list_last_entry() Use list_first_entry()/list_last_entry() instead of open coded variants. Signed-off-by: Andy Shevchenko Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpiolib.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/gpio/gpiolib.c b/drivers/gpio/gpiolib.c index b4d8bf3a1121..9b2b3a2cd1d8 100644 --- a/drivers/gpio/gpiolib.c +++ b/drivers/gpio/gpiolib.c @@ -262,14 +262,14 @@ static int gpiodev_add_to_list(struct gpio_device *gdev) return 0; } - next = list_entry(gpio_devices.next, struct gpio_device, list); + next = list_first_entry(&gpio_devices, struct gpio_device, list); if (gdev->base + gdev->ngpio <= next->base) { /* add before first entry */ list_add(&gdev->list, &gpio_devices); return 0; } - prev = list_entry(gpio_devices.prev, struct gpio_device, list); + prev = list_last_entry(&gpio_devices, struct gpio_device, list); if (prev->base + prev->ngpio <= gdev->base) { /* add behind last entry */ list_add_tail(&gdev->list, &gpio_devices); @@ -4423,7 +4423,7 @@ static void *gpiolib_seq_next(struct seq_file *s, void *v, loff_t *pos) if (list_is_last(&gdev->list, &gpio_devices)) ret = NULL; else - ret = list_entry(gdev->list.next, struct gpio_device, list); + ret = list_first_entry(&gdev->list, struct gpio_device, list); spin_unlock_irqrestore(&gpio_lock, flags); s->private = "\n"; From c91a5b1c221a58d008485cf7d02ccce73108b119 Mon Sep 17 00:00:00 2001 From: Jia-Ju Bai Date: Wed, 2 Mar 2022 18:24:21 -0800 Subject: [PATCH 115/137] platform/x86: huawei-wmi: check the return value of device_create_file() The function device_create_file() in huawei_wmi_battery_add() can fail, so its return value should be checked. Fixes: 355a070b09ab ("platform/x86: huawei-wmi: Add battery charging thresholds") Reported-by: TOTE Robot Signed-off-by: Jia-Ju Bai Link: https://lore.kernel.org/r/20220303022421.313-1-baijiaju1990@gmail.com Reviewed-by: Hans de Goede Signed-off-by: Hans de Goede --- drivers/platform/x86/huawei-wmi.c | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/drivers/platform/x86/huawei-wmi.c b/drivers/platform/x86/huawei-wmi.c index a2d846c4a7ee..eac3e6b4ea11 100644 --- a/drivers/platform/x86/huawei-wmi.c +++ b/drivers/platform/x86/huawei-wmi.c @@ -470,10 +470,17 @@ static DEVICE_ATTR_RW(charge_control_thresholds); static int huawei_wmi_battery_add(struct power_supply *battery) { - device_create_file(&battery->dev, &dev_attr_charge_control_start_threshold); - device_create_file(&battery->dev, &dev_attr_charge_control_end_threshold); + int err = 0; - return 0; + err = device_create_file(&battery->dev, &dev_attr_charge_control_start_threshold); + if (err) + return err; + + err = device_create_file(&battery->dev, &dev_attr_charge_control_end_threshold); + if (err) + device_remove_file(&battery->dev, &dev_attr_charge_control_start_threshold); + + return err; } static int huawei_wmi_battery_remove(struct power_supply *battery) From f2a6c7e7474001842a4adaf042d12206c27fc391 Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Fri, 4 Mar 2022 16:19:25 +0300 Subject: [PATCH 116/137] platform/x86: intel-uncore-freq: fix uncore_freq_common_init() error codes Currently the uncore_freq_common_init() return one on success and zero on failure. There is only one caller and it has a "forgot to set the error code" bug. Change uncore_freq_common_init() to return negative error codes which makes the code simpler and avoids this kind of bug in the future. Fixes: dbce412a7733 ("platform/x86/intel-uncore-freq: Split common and enumeration part") Signed-off-by: Dan Carpenter Acked-by: Srinivas Pandruvada Link: https://lore.kernel.org/r/20220304131925.GG28739@kili Reviewed-by: Hans de Goede Signed-off-by: Hans de Goede --- .../x86/intel/uncore-frequency/uncore-frequency-common.c | 2 +- drivers/platform/x86/intel/uncore-frequency/uncore-frequency.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/platform/x86/intel/uncore-frequency/uncore-frequency-common.c b/drivers/platform/x86/intel/uncore-frequency/uncore-frequency-common.c index e4d5a7960234..84eabd6156bb 100644 --- a/drivers/platform/x86/intel/uncore-frequency/uncore-frequency-common.c +++ b/drivers/platform/x86/intel/uncore-frequency/uncore-frequency-common.c @@ -231,7 +231,7 @@ int uncore_freq_common_init(int (*read_control_freq)(struct uncore_data *data, u ++uncore_instance_count; mutex_unlock(&uncore_lock); - return (!!uncore_root_kobj); + return uncore_root_kobj ? 0 : -ENOMEM; } EXPORT_SYMBOL_NS_GPL(uncore_freq_common_init, INTEL_UNCORE_FREQUENCY); diff --git a/drivers/platform/x86/intel/uncore-frequency/uncore-frequency.c b/drivers/platform/x86/intel/uncore-frequency/uncore-frequency.c index 791af0e287e4..c61f804dd44e 100644 --- a/drivers/platform/x86/intel/uncore-frequency/uncore-frequency.c +++ b/drivers/platform/x86/intel/uncore-frequency/uncore-frequency.c @@ -225,7 +225,7 @@ static int __init intel_uncore_init(void) ret = uncore_freq_common_init(uncore_read_control_freq, uncore_write_control_freq, uncore_read_freq); - if (!ret) + if (ret) goto err_free; ret = cpuhp_setup_state(CPUHP_AP_ONLINE_DYN, From d713b8d2aa03454e676fbf453b1231c43033c33d Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Mon, 7 Mar 2022 17:18:32 +0300 Subject: [PATCH 117/137] platform/x86: amd-pmc: uninitialized variable in amd_pmc_s2d_init() The "size" variable can be uninitialized if amd_pmc_send_cmd() fails. Fixes: 3d7d407dfb05 ("platform/x86: amd-pmc: Add support for AMD Spill to DRAM STB feature") Signed-off-by: Dan Carpenter Link: https://lore.kernel.org/r/20220307141832.GA19660@kili Signed-off-by: Hans de Goede --- drivers/platform/x86/amd-pmc.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/platform/x86/amd-pmc.c b/drivers/platform/x86/amd-pmc.c index 425a86108f75..fc0b4d628dec 100644 --- a/drivers/platform/x86/amd-pmc.c +++ b/drivers/platform/x86/amd-pmc.c @@ -708,8 +708,9 @@ static const struct pci_device_id pmc_pci_ids[] = { static int amd_pmc_s2d_init(struct amd_pmc_dev *dev) { - u32 size, phys_addr_low, phys_addr_hi; + u32 phys_addr_low, phys_addr_hi; u64 stb_phys_addr; + u32 size = 0; /* Spill to DRAM feature uses separate SMU message port */ dev->msg_port = 1; From 1e8aa2aa1274953e8e595f0630436744597d0d64 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Tue, 8 Mar 2022 16:29:42 +0100 Subject: [PATCH 118/137] platform/x86: x86-android-tablets: Depend on EFI and SPI The recently added support for Lenovo Yoga Tablet 2 tablets uses symbols from EFI and SPI add "depends on EFI && SPI" to the X86_ANDROID_TABLETS Kconfig entry. Reported-by: Randy Dunlap Signed-off-by: Hans de Goede Acked-by: Randy Dunlap Tested-by: Randy Dunlap Link: https://lore.kernel.org/r/20220308152942.262130-1-hdegoede@redhat.com --- drivers/platform/x86/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index 0d51011d5d90..5d9dd70e4e0f 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -1041,7 +1041,7 @@ config TOUCHSCREEN_DMI config X86_ANDROID_TABLETS tristate "X86 Android tablet support" - depends on I2C && SERIAL_DEV_BUS && ACPI && GPIOLIB + depends on I2C && SPI && SERIAL_DEV_BUS && ACPI && EFI && GPIOLIB help X86 tablets which ship with Android as (part of) the factory image typically have various problems with their DSDTs. The factory kernels From d4b938abafc83c1e79694c5ff8d749cd23243a2b Mon Sep 17 00:00:00 2001 From: Mark Pearson Date: Mon, 7 Mar 2022 13:30:41 -0500 Subject: [PATCH 119/137] platform/x86: thinkpad_acpi: clean up dytc profile convert Minor update cleaning up the code around convert_profile_to_dytc as identified in the previous commit. Signed-off-by: Mark Pearson Link: https://lore.kernel.org/r/20220307183041.4467-1-markpearson@lenovo.com Reviewed-by: Hans de Goede Signed-off-by: Hans de Goede --- drivers/platform/x86/thinkpad_acpi.c | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/drivers/platform/x86/thinkpad_acpi.c b/drivers/platform/x86/thinkpad_acpi.c index d9117f824ce9..cb8f52be8253 100644 --- a/drivers/platform/x86/thinkpad_acpi.c +++ b/drivers/platform/x86/thinkpad_acpi.c @@ -10298,6 +10298,7 @@ static int dytc_cql_command(int command, int *output) static int dytc_profile_set(struct platform_profile_handler *pprof, enum platform_profile_option profile) { + int perfmode; int output; int err; @@ -10305,6 +10306,10 @@ static int dytc_profile_set(struct platform_profile_handler *pprof, if (err) return err; + err = convert_profile_to_dytc(profile, &perfmode); + if (err) + goto unlock; + if (dytc_profile_available == DYTC_FUNCMODE_MMC) { if (profile == PLATFORM_PROFILE_BALANCED) { /* @@ -10317,12 +10322,6 @@ static int dytc_profile_set(struct platform_profile_handler *pprof, if (err) goto unlock; } else { - int perfmode; - - err = convert_profile_to_dytc(profile, &perfmode); - if (err) - goto unlock; - /* Determine if we are in CQL mode. This alters the commands we do */ err = dytc_cql_command(DYTC_SET_COMMAND(DYTC_FUNCTION_MMC, perfmode, 1), &output); @@ -10331,12 +10330,6 @@ static int dytc_profile_set(struct platform_profile_handler *pprof, } } if (dytc_profile_available == DYTC_FUNCMODE_PSC) { - int perfmode; - - err = convert_profile_to_dytc(profile, &perfmode); - if (err) - goto unlock; - err = dytc_command(DYTC_SET_COMMAND(DYTC_FUNCTION_PSC, perfmode, 1), &output); if (err) goto unlock; From 6229ce9c36384c7adce09f06a3a1897faa5f2c1a Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Wed, 9 Mar 2022 18:05:31 +0100 Subject: [PATCH 120/137] platform/x86: thinkpad_acpi: Fix compiler warning about uninitialized err variable err is always set because if we get here then dytc_profile_available is always one of DYTC_FUNCMODE_MMC or DYTC_FUNCMODE_PSC, but the compiler cannot now that, so initialize err to 0 to avoid a compiler warning about err being uninitialized. Reported-by: kernel test robot Signed-off-by: Hans de Goede Link: https://lore.kernel.org/r/20220309170532.343384-1-hdegoede@redhat.com --- drivers/platform/x86/thinkpad_acpi.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/platform/x86/thinkpad_acpi.c b/drivers/platform/x86/thinkpad_acpi.c index cb8f52be8253..7016c7fc3440 100644 --- a/drivers/platform/x86/thinkpad_acpi.c +++ b/drivers/platform/x86/thinkpad_acpi.c @@ -10344,7 +10344,7 @@ static int dytc_profile_set(struct platform_profile_handler *pprof, static void dytc_profile_refresh(void) { enum platform_profile_option profile; - int output, err; + int output, err = 0; int perfmode; mutex_lock(&dytc_mutex); From 10b29dd7eafe1987b44866d3ee1d0d71bf622cb3 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Wed, 9 Mar 2022 18:05:32 +0100 Subject: [PATCH 121/137] platform/x86: thinkpad_acpi: Don't use test_bit on an integer test_bit can only be used on longs not on ints, fix this. Reported-by: Stephen Rothwell Signed-off-by: Hans de Goede Link: https://lore.kernel.org/r/20220309170532.343384-2-hdegoede@redhat.com --- drivers/platform/x86/thinkpad_acpi.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/platform/x86/thinkpad_acpi.c b/drivers/platform/x86/thinkpad_acpi.c index 7016c7fc3440..c476a78599d6 100644 --- a/drivers/platform/x86/thinkpad_acpi.c +++ b/drivers/platform/x86/thinkpad_acpi.c @@ -10399,7 +10399,7 @@ static int tpacpi_dytc_profile_init(struct ibm_init_struct *iibm) if (err) return err; - if (test_bit(DYTC_FC_MMC, (void *)&output)) { /* MMC MODE */ + if (output & BIT(DYTC_FC_MMC)) { /* MMC MODE */ dytc_profile_available = DYTC_FUNCMODE_MMC; /* @@ -10412,7 +10412,7 @@ static int tpacpi_dytc_profile_init(struct ibm_init_struct *iibm) if (!err && ((output & DYTC_ERR_MASK) == DYTC_ERR_SUCCESS)) dytc_mmc_get_available = true; } - } else if (test_bit(DYTC_FC_PSC, (void *)&output)) { /*PSC MODE */ + } else if (output & BIT(DYTC_FC_PSC)) { /* PSC MODE */ dytc_profile_available = DYTC_FUNCMODE_PSC; } else { dbg_printk(TPACPI_DBG_INIT, "No DYTC support available\n"); From 87ba5badc541a79bab2fa3243ee0008c0880c64a Mon Sep 17 00:00:00 2001 From: Kris Bahnsen Date: Thu, 10 Mar 2022 10:05:39 +0100 Subject: [PATCH 122/137] gpio: ts4900: Use SPDX header Remove boilerplate, use the SPDX license identifier. Signed-off-by: Kris Bahnsen Signed-off-by: Bartosz Golaszewski --- drivers/gpio/gpio-ts4900.c | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/drivers/gpio/gpio-ts4900.c b/drivers/gpio/gpio-ts4900.c index d885032cf814..c2affc54979d 100644 --- a/drivers/gpio/gpio-ts4900.c +++ b/drivers/gpio/gpio-ts4900.c @@ -1,17 +1,9 @@ +// SPDX-License-Identifier: GPL-2.0 /* * Digital I/O driver for Technologic Systems I2C FPGA Core * * Copyright (C) 2015 Technologic Systems * Copyright (C) 2016 Savoir-Faire Linux - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - * - * This program is distributed "as is" WITHOUT ANY WARRANTY of any - * kind, whether expressed or implied; without even the implied warranty - * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License version 2 for more details. */ #include From 854abe25ddb0d33532f15cca7eb5240b02f7db6b Mon Sep 17 00:00:00 2001 From: Mario Limonciello Date: Thu, 10 Mar 2022 09:09:20 -0600 Subject: [PATCH 123/137] platform/x86: amd-pmc: Validate entry into the deepest state on resume Currently the only way to discover if a system went into the deepest sleep state is to read from sysfs after you finished suspend. To better illustrate to users that problems have occurred, check as part of resume and display a warning. Signed-off-by: Mario Limonciello Link: https://lore.kernel.org/r/20220310150920.560583-1-mario.limonciello@amd.com Reviewed-by: Hans de Goede Signed-off-by: Hans de Goede --- drivers/platform/x86/amd-pmc.c | 29 ++++++++++++++++++++++++++--- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/drivers/platform/x86/amd-pmc.c b/drivers/platform/x86/amd-pmc.c index fc0b4d628dec..971aaabaa9c8 100644 --- a/drivers/platform/x86/amd-pmc.c +++ b/drivers/platform/x86/amd-pmc.c @@ -322,6 +322,28 @@ static int amd_pmc_idlemask_read(struct amd_pmc_dev *pdev, struct device *dev, return 0; } +static int get_metrics_table(struct amd_pmc_dev *pdev, struct smu_metrics *table) +{ + if (pdev->cpu_id == AMD_CPU_ID_PCO) + return -ENODEV; + memcpy_fromio(table, pdev->smu_virt_addr, sizeof(struct smu_metrics)); + return 0; +} + +static void amd_pmc_validate_deepest(struct amd_pmc_dev *pdev) +{ + struct smu_metrics table; + + if (get_metrics_table(pdev, &table)) + return; + + if (!table.s0i3_last_entry_status) + dev_warn(pdev->dev, "Last suspend didn't reach deepest state\n"); + else + dev_dbg(pdev->dev, "Last suspend in deepest state for %lluus\n", + table.timein_s0i3_lastcapture); +} + #ifdef CONFIG_DEBUG_FS static int smu_fw_info_show(struct seq_file *s, void *unused) { @@ -329,11 +351,9 @@ static int smu_fw_info_show(struct seq_file *s, void *unused) struct smu_metrics table; int idx; - if (dev->cpu_id == AMD_CPU_ID_PCO) + if (get_metrics_table(dev, &table)) return -EINVAL; - memcpy_fromio(&table, dev->smu_virt_addr, sizeof(struct smu_metrics)); - seq_puts(s, "\n=== SMU Statistics ===\n"); seq_printf(s, "Table Version: %d\n", table.table_version); seq_printf(s, "Hint Count: %d\n", table.hint_count); @@ -689,6 +709,9 @@ static int __maybe_unused amd_pmc_resume(struct device *dev) cpu_latency_qos_update_request(&pdev->amd_pmc_pm_qos_req, PM_QOS_DEFAULT_VALUE); + /* Notify on failed entry */ + amd_pmc_validate_deepest(pdev); + return rc; } From f97ec5d75e9261a5da78dc28a8955b7cc0c4468b Mon Sep 17 00:00:00 2001 From: Gwendal Grignou Date: Tue, 8 Feb 2022 21:09:47 -0800 Subject: [PATCH 124/137] HID: intel-ish-hid: Use dma_alloc_coherent for firmware update Allocating memory with kmalloc and GPF_DMA32 is not allowed, the allocator will ignore the attribute. Instead, use dma_alloc_coherent() API as we allocate a small amount of memory to transfer firmware fragment to the ISH. On Arcada chromebook, after the patch the warning: "Unexpected gfp: 0x4 (GFP_DMA32). Fixing up to gfp: 0xcc0 (GFP_KERNEL). Fix your code!" is gone. The ISH firmware is loaded properly and we can interact with the ISH: > ectool --name cros_ish version ... Build info: arcada_ish_v2.0.3661+3c1a1c1ae0 2022-02-08 05:37:47 @localhost Tool version: v2.0.12300-900b03ec7f 2022-02-08 10:01:48 @localhost Fixes: commit 91b228107da3 ("HID: intel-ish-hid: ISH firmware loader client driver") Signed-off-by: Gwendal Grignou Acked-by: Srinivas Pandruvada Cc: stable@vger.kernel.org Signed-off-by: Jiri Kosina --- drivers/hid/intel-ish-hid/ishtp-fw-loader.c | 29 +++------------------ 1 file changed, 3 insertions(+), 26 deletions(-) diff --git a/drivers/hid/intel-ish-hid/ishtp-fw-loader.c b/drivers/hid/intel-ish-hid/ishtp-fw-loader.c index e24988586710..16aa030af845 100644 --- a/drivers/hid/intel-ish-hid/ishtp-fw-loader.c +++ b/drivers/hid/intel-ish-hid/ishtp-fw-loader.c @@ -661,21 +661,12 @@ static int ish_fw_xfer_direct_dma(struct ishtp_cl_data *client_data, */ payload_max_size &= ~(L1_CACHE_BYTES - 1); - dma_buf = kmalloc(payload_max_size, GFP_KERNEL | GFP_DMA32); + dma_buf = dma_alloc_coherent(devc, payload_max_size, &dma_buf_phy, GFP_KERNEL); if (!dma_buf) { client_data->flag_retry = true; return -ENOMEM; } - dma_buf_phy = dma_map_single(devc, dma_buf, payload_max_size, - DMA_TO_DEVICE); - if (dma_mapping_error(devc, dma_buf_phy)) { - dev_err(cl_data_to_dev(client_data), "DMA map failed\n"); - client_data->flag_retry = true; - rv = -ENOMEM; - goto end_err_dma_buf_release; - } - ldr_xfer_dma_frag.fragment.hdr.command = LOADER_CMD_XFER_FRAGMENT; ldr_xfer_dma_frag.fragment.xfer_mode = LOADER_XFER_MODE_DIRECT_DMA; ldr_xfer_dma_frag.ddr_phys_addr = (u64)dma_buf_phy; @@ -695,14 +686,7 @@ static int ish_fw_xfer_direct_dma(struct ishtp_cl_data *client_data, ldr_xfer_dma_frag.fragment.size = fragment_size; memcpy(dma_buf, &fw->data[fragment_offset], fragment_size); - dma_sync_single_for_device(devc, dma_buf_phy, - payload_max_size, - DMA_TO_DEVICE); - - /* - * Flush cache here because the dma_sync_single_for_device() - * does not do for x86. - */ + /* Flush cache to be sure the data is in main memory. */ clflush_cache_range(dma_buf, payload_max_size); dev_dbg(cl_data_to_dev(client_data), @@ -725,15 +709,8 @@ static int ish_fw_xfer_direct_dma(struct ishtp_cl_data *client_data, fragment_offset += fragment_size; } - dma_unmap_single(devc, dma_buf_phy, payload_max_size, DMA_TO_DEVICE); - kfree(dma_buf); - return 0; - end_err_resp_buf_release: - /* Free ISH buffer if not done already, in error case */ - dma_unmap_single(devc, dma_buf_phy, payload_max_size, DMA_TO_DEVICE); -end_err_dma_buf_release: - kfree(dma_buf); + dma_free_coherent(devc, payload_max_size, dma_buf, dma_buf_phy); return rv; } From 12b19f14a21a2ee6348825d95b642ef2cd16794f Mon Sep 17 00:00:00 2001 From: Jorge Lopez Date: Thu, 10 Mar 2022 15:08:50 -0600 Subject: [PATCH 125/137] platform/x86: hp-wmi: Fix hp_wmi_read_int() reporting error (0x05) The purpose of this patch is to introduce a fix to hp_wmi_read_int() and eliminate failure error (0x05). Several WMI queries leverage hp_wmi_read_int() to read their data and were failing with error 0x05. HPWMI_DISPLAY_QUERY HPWMI_HDDTEMP_QUERY HPWMI_ALS_QUERY HPWMI_HARDWARE_QUERY HPWMI_WIRELESS_QUERY HPWMI_POSTCODEERROR_QUERY The failure occurs because hp_wmi_read_int() calls hp_wmi_perform_query() with input parameter of size greater than zero. Invoking those WMI commands with an input buffer size greater than zero causes the command to be rejected and error 0x05 be returned. All changes were validated on a HP ZBook Workstation notebook, HP EliteBook x360, and HP EliteBook 850 G8. Signed-off-by: Jorge Lopez Link: https://lore.kernel.org/r/20220310210853.28367-2-jorge.lopez2@hp.com Reviewed-by: Hans de Goede Signed-off-by: Hans de Goede --- drivers/platform/x86/hp-wmi.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/platform/x86/hp-wmi.c b/drivers/platform/x86/hp-wmi.c index 48a46466f086..103f56399ed0 100644 --- a/drivers/platform/x86/hp-wmi.c +++ b/drivers/platform/x86/hp-wmi.c @@ -337,7 +337,7 @@ static int hp_wmi_read_int(int query) int val = 0, ret; ret = hp_wmi_perform_query(query, HPWMI_READ, &val, - sizeof(val), sizeof(val)); + 0, sizeof(val)); if (ret) return ret < 0 ? ret : -EINVAL; From 520ee4ea1cc60251a6e3c911cf0336278aa52634 Mon Sep 17 00:00:00 2001 From: Jorge Lopez Date: Thu, 10 Mar 2022 15:08:51 -0600 Subject: [PATCH 126/137] platform/x86: hp-wmi: Fix SW_TABLET_MODE detection method The purpose of this patch is to introduce a fix and removal of the current hack when determining tablet mode status. Determining the tablet mode status requires reading Byte 0 bit 2 as reported by HPWMI_HARDWARE_QUERY. The investigation identified the failure was rooted in two areas: HPWMI_HARDWARE_QUERY failure (0x05) and reading Byte 0, bit 2 only to determine the table mode status. HPWMI_HARDWARE_QUERY WMI failure also rendered the dock state value invalid. The latest changes use SMBIOS Type 3 (chassis type) and WMI Command 0x40 (device_mode_status) information to determine if the device is in tablet mode or not. hp_wmi_hw_state function was split into two functions; hp_wmi_get_dock_state and hp_wmi_get_tablet_mode. The new functions separate how dock_state and tablet_mode is handled in a cleaner manner. All changes were validated on a HP ZBook Workstation notebook, HP EliteBook x360, and HP EliteBook 850 G8. Signed-off-by: Jorge Lopez Link: https://lore.kernel.org/r/20220310210853.28367-3-jorge.lopez2@hp.com Reviewed-by: Hans de Goede Signed-off-by: Hans de Goede --- drivers/platform/x86/hp-wmi.c | 71 +++++++++++++++++++++++++---------- 1 file changed, 52 insertions(+), 19 deletions(-) diff --git a/drivers/platform/x86/hp-wmi.c b/drivers/platform/x86/hp-wmi.c index 103f56399ed0..1605927e1b18 100644 --- a/drivers/platform/x86/hp-wmi.c +++ b/drivers/platform/x86/hp-wmi.c @@ -35,10 +35,6 @@ MODULE_LICENSE("GPL"); MODULE_ALIAS("wmi:95F24279-4D7B-4334-9387-ACCDC67EF61C"); MODULE_ALIAS("wmi:5FB7F034-2C63-45e9-BE91-3D44E2C707E4"); -static int enable_tablet_mode_sw = -1; -module_param(enable_tablet_mode_sw, int, 0444); -MODULE_PARM_DESC(enable_tablet_mode_sw, "Enable SW_TABLET_MODE reporting (-1=auto, 0=no, 1=yes)"); - #define HPWMI_EVENT_GUID "95F24279-4D7B-4334-9387-ACCDC67EF61C" #define HPWMI_BIOS_GUID "5FB7F034-2C63-45e9-BE91-3D44E2C707E4" #define HP_OMEN_EC_THERMAL_PROFILE_OFFSET 0x95 @@ -107,6 +103,7 @@ enum hp_wmi_commandtype { HPWMI_FEATURE2_QUERY = 0x0d, HPWMI_WIRELESS2_QUERY = 0x1b, HPWMI_POSTCODEERROR_QUERY = 0x2a, + HPWMI_SYSTEM_DEVICE_MODE = 0x40, HPWMI_THERMAL_PROFILE_QUERY = 0x4c, }; @@ -217,6 +214,19 @@ struct rfkill2_device { static int rfkill2_count; static struct rfkill2_device rfkill2[HPWMI_MAX_RFKILL2_DEVICES]; +/* + * Chassis Types values were obtained from SMBIOS reference + * specification version 3.00. A complete list of system enclosures + * and chassis types is available on Table 17. + */ +static const char * const tablet_chassis_types[] = { + "30", /* Tablet*/ + "31", /* Convertible */ + "32" /* Detachable */ +}; + +#define DEVICE_MODE_TABLET 0x06 + /* map output size to the corresponding WMI method id */ static inline int encode_outsize_for_pvsz(int outsize) { @@ -345,14 +355,39 @@ static int hp_wmi_read_int(int query) return val; } -static int hp_wmi_hw_state(int mask) +static int hp_wmi_get_dock_state(void) { int state = hp_wmi_read_int(HPWMI_HARDWARE_QUERY); if (state < 0) return state; - return !!(state & mask); + return !!(state & HPWMI_DOCK_MASK); +} + +static int hp_wmi_get_tablet_mode(void) +{ + char system_device_mode[4] = { 0 }; + const char *chassis_type; + bool tablet_found; + int ret; + + chassis_type = dmi_get_system_info(DMI_CHASSIS_TYPE); + if (!chassis_type) + return -ENODEV; + + tablet_found = match_string(tablet_chassis_types, + ARRAY_SIZE(tablet_chassis_types), + chassis_type) >= 0; + if (!tablet_found) + return -ENODEV; + + ret = hp_wmi_perform_query(HPWMI_SYSTEM_DEVICE_MODE, HPWMI_READ, + system_device_mode, 0, sizeof(system_device_mode)); + if (ret < 0) + return ret; + + return system_device_mode[0] == DEVICE_MODE_TABLET; } static int omen_thermal_profile_set(int mode) @@ -568,7 +603,7 @@ static ssize_t als_show(struct device *dev, struct device_attribute *attr, static ssize_t dock_show(struct device *dev, struct device_attribute *attr, char *buf) { - int value = hp_wmi_hw_state(HPWMI_DOCK_MASK); + int value = hp_wmi_get_dock_state(); if (value < 0) return value; return sprintf(buf, "%d\n", value); @@ -577,7 +612,7 @@ static ssize_t dock_show(struct device *dev, struct device_attribute *attr, static ssize_t tablet_show(struct device *dev, struct device_attribute *attr, char *buf) { - int value = hp_wmi_hw_state(HPWMI_TABLET_MASK); + int value = hp_wmi_get_tablet_mode(); if (value < 0) return value; return sprintf(buf, "%d\n", value); @@ -699,10 +734,10 @@ static void hp_wmi_notify(u32 value, void *context) case HPWMI_DOCK_EVENT: if (test_bit(SW_DOCK, hp_wmi_input_dev->swbit)) input_report_switch(hp_wmi_input_dev, SW_DOCK, - hp_wmi_hw_state(HPWMI_DOCK_MASK)); + hp_wmi_get_dock_state()); if (test_bit(SW_TABLET_MODE, hp_wmi_input_dev->swbit)) input_report_switch(hp_wmi_input_dev, SW_TABLET_MODE, - hp_wmi_hw_state(HPWMI_TABLET_MASK)); + hp_wmi_get_tablet_mode()); input_sync(hp_wmi_input_dev); break; case HPWMI_PARK_HDD: @@ -780,19 +815,17 @@ static int __init hp_wmi_input_setup(void) __set_bit(EV_SW, hp_wmi_input_dev->evbit); /* Dock */ - val = hp_wmi_hw_state(HPWMI_DOCK_MASK); + val = hp_wmi_get_dock_state(); if (!(val < 0)) { __set_bit(SW_DOCK, hp_wmi_input_dev->swbit); input_report_switch(hp_wmi_input_dev, SW_DOCK, val); } /* Tablet mode */ - if (enable_tablet_mode_sw > 0) { - val = hp_wmi_hw_state(HPWMI_TABLET_MASK); - if (val >= 0) { - __set_bit(SW_TABLET_MODE, hp_wmi_input_dev->swbit); - input_report_switch(hp_wmi_input_dev, SW_TABLET_MODE, val); - } + val = hp_wmi_get_tablet_mode(); + if (!(val < 0)) { + __set_bit(SW_TABLET_MODE, hp_wmi_input_dev->swbit); + input_report_switch(hp_wmi_input_dev, SW_TABLET_MODE, val); } err = sparse_keymap_setup(hp_wmi_input_dev, hp_wmi_keymap, NULL); @@ -1227,10 +1260,10 @@ static int hp_wmi_resume_handler(struct device *device) if (hp_wmi_input_dev) { if (test_bit(SW_DOCK, hp_wmi_input_dev->swbit)) input_report_switch(hp_wmi_input_dev, SW_DOCK, - hp_wmi_hw_state(HPWMI_DOCK_MASK)); + hp_wmi_get_dock_state()); if (test_bit(SW_TABLET_MODE, hp_wmi_input_dev->swbit)) input_report_switch(hp_wmi_input_dev, SW_TABLET_MODE, - hp_wmi_hw_state(HPWMI_TABLET_MASK)); + hp_wmi_get_tablet_mode()); input_sync(hp_wmi_input_dev); } From be9d73e64957bbd31ee9a0d11adc0f720974c558 Mon Sep 17 00:00:00 2001 From: Jorge Lopez Date: Thu, 10 Mar 2022 15:08:52 -0600 Subject: [PATCH 127/137] platform/x86: hp-wmi: Fix 0x05 error code reported by several WMI calls Several WMI queries leverage hp_wmi_read_int function to read their data. hp_wmi_read_int function was corrected in a previous patch. Now, this function invokes hp_wmi_perform_query with input parameter of size zero and the output buffer of size 4. WMI commands calling hp_wmi_perform_query with input buffer size value of zero are listed below. HPWMI_DISPLAY_QUERY HPWMI_HDDTEMP_QUERY HPWMI_ALS_QUERY HPWMI_HARDWARE_QUERY HPWMI_WIRELESS_QUERY HPWMI_BIOS_QUERY HPWMI_FEATURE_QUERY HPWMI_HOTKEY_QUERY HPWMI_FEATURE2_QUERY HPWMI_WIRELESS2_QUERY HPWMI_POSTCODEERROR_QUERY HPWMI_THERMAL_PROFILE_QUERY HPWMI_FAN_SPEED_MAX_GET_QUERY Invoking those WMI commands with an input buffer size greater than zero will cause error 0x05 to be returned. All WMI commands executed by the driver were reviewed and changes were made to ensure the expected input and output buffer size match the WMI specification. Changes were validated on a HP ZBook Workstation notebook, HP EliteBook x360, and HP EliteBook 850 G8. Additional validation was included in the test process to ensure no other commands were incorrectly handled. Signed-off-by: Jorge Lopez Link: https://lore.kernel.org/r/20220310210853.28367-4-jorge.lopez2@hp.com Reviewed-by: Hans de Goede Signed-off-by: Hans de Goede --- drivers/platform/x86/hp-wmi.c | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/drivers/platform/x86/hp-wmi.c b/drivers/platform/x86/hp-wmi.c index 1605927e1b18..29ae35d15a41 100644 --- a/drivers/platform/x86/hp-wmi.c +++ b/drivers/platform/x86/hp-wmi.c @@ -330,7 +330,7 @@ static int hp_wmi_get_fan_speed(int fan) char fan_data[4] = { fan, 0, 0, 0 }; int ret = hp_wmi_perform_query(HPWMI_FAN_SPEED_GET_QUERY, HPWMI_GM, - &fan_data, sizeof(fan_data), + &fan_data, sizeof(char), sizeof(fan_data)); if (ret != 0) @@ -399,7 +399,7 @@ static int omen_thermal_profile_set(int mode) return -EINVAL; ret = hp_wmi_perform_query(HPWMI_SET_PERFORMANCE_MODE, HPWMI_GM, - &buffer, sizeof(buffer), sizeof(buffer)); + &buffer, sizeof(buffer), 0); if (ret) return ret < 0 ? ret : -EINVAL; @@ -436,7 +436,7 @@ static int hp_wmi_fan_speed_max_set(int enabled) int ret; ret = hp_wmi_perform_query(HPWMI_FAN_SPEED_MAX_SET_QUERY, HPWMI_GM, - &enabled, sizeof(enabled), sizeof(enabled)); + &enabled, sizeof(enabled), 0); if (ret) return ret < 0 ? ret : -EINVAL; @@ -449,7 +449,7 @@ static int hp_wmi_fan_speed_max_get(void) int val = 0, ret; ret = hp_wmi_perform_query(HPWMI_FAN_SPEED_MAX_GET_QUERY, HPWMI_GM, - &val, sizeof(val), sizeof(val)); + &val, 0, sizeof(val)); if (ret) return ret < 0 ? ret : -EINVAL; @@ -461,7 +461,7 @@ static int __init hp_wmi_bios_2008_later(void) { int state = 0; int ret = hp_wmi_perform_query(HPWMI_FEATURE_QUERY, HPWMI_READ, &state, - sizeof(state), sizeof(state)); + 0, sizeof(state)); if (!ret) return 1; @@ -472,7 +472,7 @@ static int __init hp_wmi_bios_2009_later(void) { u8 state[128]; int ret = hp_wmi_perform_query(HPWMI_FEATURE2_QUERY, HPWMI_READ, &state, - sizeof(state), sizeof(state)); + 0, sizeof(state)); if (!ret) return 1; @@ -550,7 +550,7 @@ static int hp_wmi_rfkill2_refresh(void) int err, i; err = hp_wmi_perform_query(HPWMI_WIRELESS2_QUERY, HPWMI_READ, &state, - sizeof(state), sizeof(state)); + 0, sizeof(state)); if (err) return err; @@ -639,7 +639,7 @@ static ssize_t als_store(struct device *dev, struct device_attribute *attr, return ret; ret = hp_wmi_perform_query(HPWMI_ALS_QUERY, HPWMI_WRITE, &tmp, - sizeof(tmp), sizeof(tmp)); + sizeof(tmp), 0); if (ret) return ret < 0 ? ret : -EINVAL; @@ -660,9 +660,9 @@ static ssize_t postcode_store(struct device *dev, struct device_attribute *attr, if (clear == false) return -EINVAL; - /* Clear the POST error code. It is kept until until cleared. */ + /* Clear the POST error code. It is kept until cleared. */ ret = hp_wmi_perform_query(HPWMI_POSTCODEERROR_QUERY, HPWMI_WRITE, &tmp, - sizeof(tmp), sizeof(tmp)); + sizeof(tmp), 0); if (ret) return ret < 0 ? ret : -EINVAL; @@ -952,7 +952,7 @@ static int __init hp_wmi_rfkill2_setup(struct platform_device *device) int err, i; err = hp_wmi_perform_query(HPWMI_WIRELESS2_QUERY, HPWMI_READ, &state, - sizeof(state), sizeof(state)); + 0, sizeof(state)); if (err) return err < 0 ? err : -EINVAL; From 4b4967cbd2685f313411e6facf915fb2ae01d796 Mon Sep 17 00:00:00 2001 From: Jorge Lopez Date: Thu, 10 Mar 2022 15:08:53 -0600 Subject: [PATCH 128/137] platform/x86: hp-wmi: Changing bios_args.data to be dynamically allocated The purpose of this patch is to remove 128 bytes buffer limitation imposed in bios_args structure. A limiting factor discovered during this investigation was the struct bios_args.data size restriction. The data member size limits all possible WMI commands to those requiring buffer size of 128 bytes or less. Several WMI commands and queries require a buffer size larger than 128 bytes hence limiting current and feature supported by the driver. It is for this reason, struct bios_args.data changed and is dynamically allocated. hp_wmi_perform_query function changed to handle the memory allocation and release of any required buffer size. All changes were validated on a HP ZBook Workstation notebook, HP EliteBook x360, and HP EliteBook 850 G8. Additional validation was included in the test process to ensure no other commands were incorrectly handled. Signed-off-by: Jorge Lopez Link: https://lore.kernel.org/r/20220310210853.28367-5-jorge.lopez2@hp.com Reviewed-by: Hans de Goede Signed-off-by: Hans de Goede --- drivers/platform/x86/hp-wmi.c | 54 +++++++++++++++++++++-------------- 1 file changed, 33 insertions(+), 21 deletions(-) diff --git a/drivers/platform/x86/hp-wmi.c b/drivers/platform/x86/hp-wmi.c index 29ae35d15a41..1f9d6e1de5af 100644 --- a/drivers/platform/x86/hp-wmi.c +++ b/drivers/platform/x86/hp-wmi.c @@ -82,12 +82,17 @@ enum hp_wmi_event_ids { HPWMI_BATTERY_CHARGE_PERIOD = 0x10, }; +/* + * struct bios_args buffer is dynamically allocated. New WMI command types + * were introduced that exceeds 128-byte data size. Changes to handle + * the data size allocation scheme were kept in hp_wmi_perform_qurey function. + */ struct bios_args { u32 signature; u32 command; u32 commandtype; u32 datasize; - u8 data[128]; + u8 data[]; }; enum hp_wmi_commandtype { @@ -266,37 +271,43 @@ static inline int encode_outsize_for_pvsz(int outsize) static int hp_wmi_perform_query(int query, enum hp_wmi_command command, void *buffer, int insize, int outsize) { - int mid; + struct acpi_buffer input, output = { ACPI_ALLOCATE_BUFFER, NULL }; struct bios_return *bios_return; - int actual_outsize; - union acpi_object *obj; - struct bios_args args = { - .signature = 0x55434553, - .command = command, - .commandtype = query, - .datasize = insize, - .data = { 0 }, - }; - struct acpi_buffer input = { sizeof(struct bios_args), &args }; - struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL }; - int ret = 0; + union acpi_object *obj = NULL; + struct bios_args *args = NULL; + int mid, actual_outsize, ret; + size_t bios_args_size; mid = encode_outsize_for_pvsz(outsize); if (WARN_ON(mid < 0)) return mid; - if (WARN_ON(insize > sizeof(args.data))) - return -EINVAL; - memcpy(&args.data[0], buffer, insize); + bios_args_size = struct_size(args, data, insize); + args = kmalloc(bios_args_size, GFP_KERNEL); + if (!args) + return -ENOMEM; - wmi_evaluate_method(HPWMI_BIOS_GUID, 0, mid, &input, &output); + input.length = bios_args_size; + input.pointer = args; + + args->signature = 0x55434553; + args->command = command; + args->commandtype = query; + args->datasize = insize; + memcpy(args->data, buffer, flex_array_size(args, data, insize)); + + ret = wmi_evaluate_method(HPWMI_BIOS_GUID, 0, mid, &input, &output); + if (ret) + goto out_free; obj = output.pointer; - - if (!obj) - return -EINVAL; + if (!obj) { + ret = -EINVAL; + goto out_free; + } if (obj->type != ACPI_TYPE_BUFFER) { + pr_warn("query 0x%x returned an invalid object 0x%x\n", query, ret); ret = -EINVAL; goto out_free; } @@ -321,6 +332,7 @@ static int hp_wmi_perform_query(int query, enum hp_wmi_command command, out_free: kfree(obj); + kfree(args); return ret; } From 286e937efbc7177c114e80aae9b402131e3886c1 Mon Sep 17 00:00:00 2001 From: Enver Balalic Date: Mon, 14 Mar 2022 13:14:53 +0100 Subject: [PATCH 129/137] platform/x86: hp-wmi: support omen thermal profile policy v1 As it turns out, these laptops have 2 thermal profile versions. A previous patch added support for v0, this patch adds support for v1 thermal policies that are in use on some devices. We obtain the thermal policy version by querying the get system design data WMI call and looking at the fourth byte it returns, except if the system board DMI Board ID is in a specific array that the windows command center app overrides to thermal policy v0 for some reason. Signed-off-by: Enver Balalic Link: https://lore.kernel.org/r/20220314121453.kjszdciymtg6ctbq@omen Reviewed-by: Hans de Goede Signed-off-by: Hans de Goede --- drivers/platform/x86/hp-wmi.c | 81 +++++++++++++++++++++++++++++------ 1 file changed, 67 insertions(+), 14 deletions(-) diff --git a/drivers/platform/x86/hp-wmi.c b/drivers/platform/x86/hp-wmi.c index 1f9d6e1de5af..0e9a25b56e0e 100644 --- a/drivers/platform/x86/hp-wmi.c +++ b/drivers/platform/x86/hp-wmi.c @@ -57,6 +57,14 @@ static const char * const omen_thermal_profile_boards[] = { "8917", "8918", "8949", "894A", "89EB" }; +/* DMI Board names of Omen laptops that are specifically set to be thermal + * profile version 0 by the Omen Command Center app, regardless of what + * the get system design information WMI call returns + */ +static const char *const omen_thermal_profile_force_v0_boards[] = { + "8607", "8746", "8747", "8749", "874A", "8748" +}; + enum hp_wmi_radio { HPWMI_WIFI = 0x0, HPWMI_BLUETOOTH = 0x1, @@ -117,6 +125,7 @@ enum hp_wmi_gm_commandtype { HPWMI_SET_PERFORMANCE_MODE = 0x1A, HPWMI_FAN_SPEED_MAX_GET_QUERY = 0x26, HPWMI_FAN_SPEED_MAX_SET_QUERY = 0x27, + HPWMI_GET_SYSTEM_DESIGN_DATA = 0x28, }; enum hp_wmi_command { @@ -151,10 +160,16 @@ enum hp_wireless2_bits { HPWMI_POWER_FW_OR_HW = HPWMI_POWER_BIOS | HPWMI_POWER_HARD, }; -enum hp_thermal_profile_omen { - HP_OMEN_THERMAL_PROFILE_DEFAULT = 0x00, - HP_OMEN_THERMAL_PROFILE_PERFORMANCE = 0x01, - HP_OMEN_THERMAL_PROFILE_COOL = 0x02, +enum hp_thermal_profile_omen_v0 { + HP_OMEN_V0_THERMAL_PROFILE_DEFAULT = 0x00, + HP_OMEN_V0_THERMAL_PROFILE_PERFORMANCE = 0x01, + HP_OMEN_V0_THERMAL_PROFILE_COOL = 0x02, +}; + +enum hp_thermal_profile_omen_v1 { + HP_OMEN_V1_THERMAL_PROFILE_DEFAULT = 0x30, + HP_OMEN_V1_THERMAL_PROFILE_PERFORMANCE = 0x31, + HP_OMEN_V1_THERMAL_PROFILE_COOL = 0x50, }; enum hp_thermal_profile { @@ -407,9 +422,6 @@ static int omen_thermal_profile_set(int mode) char buffer[2] = {0, mode}; int ret; - if (mode < 0 || mode > 2) - return -EINVAL; - ret = hp_wmi_perform_query(HPWMI_SET_PERFORMANCE_MODE, HPWMI_GM, &buffer, sizeof(buffer), 0); @@ -431,6 +443,30 @@ static bool is_omen_thermal_profile(void) board_name) >= 0; } +static int omen_get_thermal_policy_version(void) +{ + unsigned char buffer[8] = { 0 }; + int ret; + + const char *board_name = dmi_get_system_info(DMI_BOARD_NAME); + + if (board_name) { + int matches = match_string(omen_thermal_profile_force_v0_boards, + ARRAY_SIZE(omen_thermal_profile_force_v0_boards), + board_name); + if (matches >= 0) + return 0; + } + + ret = hp_wmi_perform_query(HPWMI_GET_SYSTEM_DESIGN_DATA, HPWMI_GM, + &buffer, sizeof(buffer), sizeof(buffer)); + + if (ret) + return ret < 0 ? ret : -EINVAL; + + return buffer[3]; +} + static int omen_thermal_profile_get(void) { u8 data; @@ -1053,13 +1089,16 @@ static int platform_profile_omen_get(struct platform_profile_handler *pprof, return tp; switch (tp) { - case HP_OMEN_THERMAL_PROFILE_PERFORMANCE: + case HP_OMEN_V0_THERMAL_PROFILE_PERFORMANCE: + case HP_OMEN_V1_THERMAL_PROFILE_PERFORMANCE: *profile = PLATFORM_PROFILE_PERFORMANCE; break; - case HP_OMEN_THERMAL_PROFILE_DEFAULT: + case HP_OMEN_V0_THERMAL_PROFILE_DEFAULT: + case HP_OMEN_V1_THERMAL_PROFILE_DEFAULT: *profile = PLATFORM_PROFILE_BALANCED; break; - case HP_OMEN_THERMAL_PROFILE_COOL: + case HP_OMEN_V0_THERMAL_PROFILE_COOL: + case HP_OMEN_V1_THERMAL_PROFILE_COOL: *profile = PLATFORM_PROFILE_COOL; break; default: @@ -1072,17 +1111,31 @@ static int platform_profile_omen_get(struct platform_profile_handler *pprof, static int platform_profile_omen_set(struct platform_profile_handler *pprof, enum platform_profile_option profile) { - int err, tp; + int err, tp, tp_version; + + tp_version = omen_get_thermal_policy_version(); + + if (tp_version < 0 || tp_version > 1) + return -EOPNOTSUPP; switch (profile) { case PLATFORM_PROFILE_PERFORMANCE: - tp = HP_OMEN_THERMAL_PROFILE_PERFORMANCE; + if (tp_version == 0) + tp = HP_OMEN_V0_THERMAL_PROFILE_PERFORMANCE; + else + tp = HP_OMEN_V1_THERMAL_PROFILE_PERFORMANCE; break; case PLATFORM_PROFILE_BALANCED: - tp = HP_OMEN_THERMAL_PROFILE_DEFAULT; + if (tp_version == 0) + tp = HP_OMEN_V0_THERMAL_PROFILE_DEFAULT; + else + tp = HP_OMEN_V1_THERMAL_PROFILE_DEFAULT; break; case PLATFORM_PROFILE_COOL: - tp = HP_OMEN_THERMAL_PROFILE_COOL; + if (tp_version == 0) + tp = HP_OMEN_V0_THERMAL_PROFILE_COOL; + else + tp = HP_OMEN_V1_THERMAL_PROFILE_COOL; break; default: return -EOPNOTSUPP; From 6060a75e77fb05c9d54ed41cccc496dd98054b57 Mon Sep 17 00:00:00 2001 From: Tom Rix Date: Sat, 12 Mar 2022 06:53:27 -0800 Subject: [PATCH 130/137] platform/x86: thinkpad_acpi: consistently check fan_get_status return. Clang static analysis returns this false positive thinkpad_acpi.c:8926:19: warning: The left operand of '!=' is a garbage value (status != 0) ? "enabled" : "disabled", status); ~~~~~~ ^ The return of fan_get_status* is checked inconsistenly. Sometime ret < 0 is an error, sometimes !ret. Both fan_get_status() and fan_get_status_safe() return 0 on success and return negative otherwise. Change the checks for error, ret < 0, into checks for not success, !ret. Signed-off-by: Tom Rix Link: https://lore.kernel.org/r/20220312145327.1398510-1-trix@redhat.com Reviewed-by: Hans de Goede Signed-off-by: Hans de Goede --- drivers/platform/x86/thinkpad_acpi.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/platform/x86/thinkpad_acpi.c b/drivers/platform/x86/thinkpad_acpi.c index c476a78599d6..515a592c9f02 100644 --- a/drivers/platform/x86/thinkpad_acpi.c +++ b/drivers/platform/x86/thinkpad_acpi.c @@ -8285,7 +8285,7 @@ static int fan_set_enable(void) case TPACPI_FAN_WR_ACPI_FANS: case TPACPI_FAN_WR_TPEC: rc = fan_get_status(&s); - if (rc < 0) + if (rc) break; /* Don't go out of emergency fan mode */ @@ -8304,7 +8304,7 @@ static int fan_set_enable(void) case TPACPI_FAN_WR_ACPI_SFAN: rc = fan_get_status(&s); - if (rc < 0) + if (rc) break; s &= 0x07; @@ -8843,7 +8843,7 @@ static void fan_suspend(void) /* Store fan status in cache */ fan_control_resume_level = 0; rc = fan_get_status_safe(&fan_control_resume_level); - if (rc < 0) + if (rc) pr_notice("failed to read fan level for later restore during resume: %d\n", rc); @@ -8864,7 +8864,7 @@ static void fan_resume(void) if (!fan_control_allowed || !fan_control_resume_level || - (fan_get_status_safe(¤t_level) < 0)) + fan_get_status_safe(¤t_level)) return; switch (fan_control_access_mode) { @@ -8918,7 +8918,7 @@ static int fan_read(struct seq_file *m) case TPACPI_FAN_RD_ACPI_GFAN: /* 570, 600e/x, 770e, 770x */ rc = fan_get_status_safe(&status); - if (rc < 0) + if (rc) return rc; seq_printf(m, "status:\t\t%s\n" @@ -8929,7 +8929,7 @@ static int fan_read(struct seq_file *m) case TPACPI_FAN_RD_TPEC: /* all except 570, 600e/x, 770e, 770x */ rc = fan_get_status_safe(&status); - if (rc < 0) + if (rc) return rc; seq_printf(m, "status:\t\t%s\n", From 20e1d6402a71dba7ad2b81f332a3c14c7d3b939b Mon Sep 17 00:00:00 2001 From: Mario Limonciello Date: Thu, 17 Mar 2022 09:14:42 -0500 Subject: [PATCH 131/137] ACPI / x86: Add support for LPS0 callback handler Currenty the latest thing run during a suspend to idle attempt is the LPS0 `prepare_late` callback and the earliest thing is the `resume_early` callback. There is a desire for the `amd-pmc` driver to suspend later in the suspend process (ideally the very last thing), so create a callback that it or any other driver can hook into to do this. Signed-off-by: Mario Limonciello Acked-by: Rafael J. Wysocki Link: https://lore.kernel.org/r/20220317141445.6498-1-mario.limonciello@amd.com Reviewed-by: Hans de Goede Signed-off-by: Hans de Goede --- drivers/acpi/x86/s2idle.c | 40 +++++++++++++++++++++++++++++++++++++++ include/linux/acpi.h | 10 +++++++++- 2 files changed, 49 insertions(+), 1 deletion(-) diff --git a/drivers/acpi/x86/s2idle.c b/drivers/acpi/x86/s2idle.c index abc06e7f89d8..031b20a547f9 100644 --- a/drivers/acpi/x86/s2idle.c +++ b/drivers/acpi/x86/s2idle.c @@ -86,6 +86,8 @@ struct lpi_device_constraint_amd { int min_dstate; }; +static LIST_HEAD(lps0_s2idle_devops_head); + static struct lpi_constraints *lpi_constraints_table; static int lpi_constraints_table_size; static int rev_id; @@ -444,6 +446,8 @@ static struct acpi_scan_handler lps0_handler = { int acpi_s2idle_prepare_late(void) { + struct acpi_s2idle_dev_ops *handler; + if (!lps0_device_handle || sleep_no_lps0) return 0; @@ -474,14 +478,26 @@ int acpi_s2idle_prepare_late(void) acpi_sleep_run_lps0_dsm(ACPI_LPS0_MS_ENTRY, lps0_dsm_func_mask_microsoft, lps0_dsm_guid_microsoft); } + + list_for_each_entry(handler, &lps0_s2idle_devops_head, list_node) { + if (handler->prepare) + handler->prepare(); + } + return 0; } void acpi_s2idle_restore_early(void) { + struct acpi_s2idle_dev_ops *handler; + if (!lps0_device_handle || sleep_no_lps0) return; + list_for_each_entry(handler, &lps0_s2idle_devops_head, list_node) + if (handler->restore) + handler->restore(); + /* Modern standby exit */ if (lps0_dsm_func_mask_microsoft > 0) acpi_sleep_run_lps0_dsm(ACPI_LPS0_MS_EXIT, @@ -524,4 +540,28 @@ void acpi_s2idle_setup(void) s2idle_set_ops(&acpi_s2idle_ops_lps0); } +int acpi_register_lps0_dev(struct acpi_s2idle_dev_ops *arg) +{ + if (!lps0_device_handle || sleep_no_lps0) + return -ENODEV; + + lock_system_sleep(); + list_add(&arg->list_node, &lps0_s2idle_devops_head); + unlock_system_sleep(); + + return 0; +} +EXPORT_SYMBOL_GPL(acpi_register_lps0_dev); + +void acpi_unregister_lps0_dev(struct acpi_s2idle_dev_ops *arg) +{ + if (!lps0_device_handle || sleep_no_lps0) + return; + + lock_system_sleep(); + list_del(&arg->list_node); + unlock_system_sleep(); +} +EXPORT_SYMBOL_GPL(acpi_unregister_lps0_dev); + #endif /* CONFIG_SUSPEND */ diff --git a/include/linux/acpi.h b/include/linux/acpi.h index 6274758648e3..47c16cdc8e0e 100644 --- a/include/linux/acpi.h +++ b/include/linux/acpi.h @@ -1023,7 +1023,15 @@ void acpi_os_set_prepare_extended_sleep(int (*func)(u8 sleep_state, acpi_status acpi_os_prepare_extended_sleep(u8 sleep_state, u32 val_a, u32 val_b); - +#ifdef CONFIG_X86 +struct acpi_s2idle_dev_ops { + struct list_head list_node; + void (*prepare)(void); + void (*restore)(void); +}; +int acpi_register_lps0_dev(struct acpi_s2idle_dev_ops *arg); +void acpi_unregister_lps0_dev(struct acpi_s2idle_dev_ops *arg); +#endif /* CONFIG_X86 */ #ifndef CONFIG_IA64 void arch_reserve_mem_area(acpi_physical_address addr, size_t size); #else From b1f66033cd4e9ce8cbe2c74c98d4e04c0b2d7b40 Mon Sep 17 00:00:00 2001 From: Mario Limonciello Date: Thu, 17 Mar 2022 09:14:43 -0500 Subject: [PATCH 132/137] platform/x86: amd-pmc: Move to later in the suspend process The `OS_HINT` message is supposed to indicate that everything else that is supposed to go into the deepest state has done so. This assumption is invalid as: 1) The CPUs will still go in and out of the deepest state 2) Other devices may still run their `noirq` suspend routines 3) The LPS0 ACPI device will still run To more closely mirror how this works on other operating systems, move the `amd-pmc` suspend to the very last thing before the s2idle loop via an lps0 callback. Fixes: 8d89835b0467 ("PM: suspend: Do not pause cpuidle in the suspend-to-idle path") Signed-off-by: Mario Limonciello Link: https://lore.kernel.org/r/20220317141445.6498-2-mario.limonciello@amd.com Reviewed-by: Hans de Goede Signed-off-by: Hans de Goede --- drivers/platform/x86/amd-pmc.c | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/drivers/platform/x86/amd-pmc.c b/drivers/platform/x86/amd-pmc.c index 971aaabaa9c8..2736ab587f2a 100644 --- a/drivers/platform/x86/amd-pmc.c +++ b/drivers/platform/x86/amd-pmc.c @@ -639,9 +639,9 @@ static int amd_pmc_verify_czn_rtc(struct amd_pmc_dev *pdev, u32 *arg) return rc; } -static int __maybe_unused amd_pmc_suspend(struct device *dev) +static void amd_pmc_s2idle_prepare(void) { - struct amd_pmc_dev *pdev = dev_get_drvdata(dev); + struct amd_pmc_dev *pdev = &pmc; int rc; u8 msg; u32 arg = 1; @@ -658,7 +658,7 @@ static int __maybe_unused amd_pmc_suspend(struct device *dev) } /* Dump the IdleMask before we send hint to SMU */ - amd_pmc_idlemask_read(pdev, dev, NULL); + amd_pmc_idlemask_read(pdev, pdev->dev, NULL); msg = amd_pmc_get_os_hint(pdev); rc = amd_pmc_send_cmd(pdev, arg, NULL, msg, 0); if (rc) { @@ -672,18 +672,16 @@ static int __maybe_unused amd_pmc_suspend(struct device *dev) dev_err(pdev->dev, "error writing to STB\n"); goto fail; } - - return 0; + return; fail: if (pdev->cpu_id == AMD_CPU_ID_CZN) cpu_latency_qos_update_request(&pdev->amd_pmc_pm_qos_req, PM_QOS_DEFAULT_VALUE); - return rc; } -static int __maybe_unused amd_pmc_resume(struct device *dev) +static void amd_pmc_s2idle_restore(void) { - struct amd_pmc_dev *pdev = dev_get_drvdata(dev); + struct amd_pmc_dev *pdev = &pmc; int rc; u8 msg; @@ -696,7 +694,7 @@ static int __maybe_unused amd_pmc_resume(struct device *dev) amd_pmc_send_cmd(pdev, 0, NULL, SMU_MSG_LOG_DUMP_DATA, 0); /* Dump the IdleMask to see the blockers */ - amd_pmc_idlemask_read(pdev, dev, NULL); + amd_pmc_idlemask_read(pdev, pdev->dev, NULL); /* Write data incremented by 1 to distinguish in stb_read */ if (enable_stb) @@ -711,13 +709,11 @@ static int __maybe_unused amd_pmc_resume(struct device *dev) /* Notify on failed entry */ amd_pmc_validate_deepest(pdev); - - return rc; } -static const struct dev_pm_ops amd_pmc_pm_ops = { - .suspend_noirq = amd_pmc_suspend, - .resume_noirq = amd_pmc_resume, +static struct acpi_s2idle_dev_ops amd_pmc_s2idle_dev_ops = { + .prepare = amd_pmc_s2idle_prepare, + .restore = amd_pmc_s2idle_restore, }; static const struct pci_device_id pmc_pci_ids[] = { @@ -884,6 +880,10 @@ static int amd_pmc_probe(struct platform_device *pdev) amd_pmc_get_smu_version(dev); platform_set_drvdata(pdev, dev); + err = acpi_register_lps0_dev(&amd_pmc_s2idle_dev_ops); + if (err) + dev_warn(dev->dev, "failed to register LPS0 sleep handler, expect increased power consumption\n"); + amd_pmc_dbgfs_register(dev); cpu_latency_qos_add_request(&dev->amd_pmc_pm_qos_req, PM_QOS_DEFAULT_VALUE); return 0; @@ -897,6 +897,7 @@ static int amd_pmc_remove(struct platform_device *pdev) { struct amd_pmc_dev *dev = platform_get_drvdata(pdev); + acpi_unregister_lps0_dev(&amd_pmc_s2idle_dev_ops); amd_pmc_dbgfs_unregister(dev); pci_dev_put(dev->rdev); mutex_destroy(&dev->lock); @@ -917,7 +918,6 @@ static struct platform_driver amd_pmc_driver = { .driver = { .name = "amd_pmc", .acpi_match_table = amd_pmc_acpi_ids, - .pm = &amd_pmc_pm_ops, }, .probe = amd_pmc_probe, .remove = amd_pmc_remove, From 23f5f7007ab3eafdb764e25527d8267783deb358 Mon Sep 17 00:00:00 2001 From: Mario Limonciello Date: Thu, 17 Mar 2022 09:14:44 -0500 Subject: [PATCH 133/137] platform/x86: amd-pmc: Output error codes in messages The return type for the s2idle callbacks is 'void', so any errors that occur will no longer be displayed. Output these error codes in the messages in case of any failures. Signed-off-by: Mario Limonciello Link: https://lore.kernel.org/r/20220317141445.6498-3-mario.limonciello@amd.com Reviewed-by: Hans de Goede Signed-off-by: Hans de Goede --- drivers/platform/x86/amd-pmc.c | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/drivers/platform/x86/amd-pmc.c b/drivers/platform/x86/amd-pmc.c index 2736ab587f2a..f36cf125b284 100644 --- a/drivers/platform/x86/amd-pmc.c +++ b/drivers/platform/x86/amd-pmc.c @@ -653,8 +653,10 @@ static void amd_pmc_s2idle_prepare(void) /* Activate CZN specific RTC functionality */ if (pdev->cpu_id == AMD_CPU_ID_CZN) { rc = amd_pmc_verify_czn_rtc(pdev, &arg); - if (rc) + if (rc) { + dev_err(pdev->dev, "failed to set RTC: %d\n", rc); goto fail; + } } /* Dump the IdleMask before we send hint to SMU */ @@ -662,14 +664,14 @@ static void amd_pmc_s2idle_prepare(void) msg = amd_pmc_get_os_hint(pdev); rc = amd_pmc_send_cmd(pdev, arg, NULL, msg, 0); if (rc) { - dev_err(pdev->dev, "suspend failed\n"); + dev_err(pdev->dev, "suspend failed: %d\n", rc); goto fail; } if (enable_stb) rc = amd_pmc_write_stb(pdev, AMD_PMC_STB_PREDEF); if (rc) { - dev_err(pdev->dev, "error writing to STB\n"); + dev_err(pdev->dev, "error writing to STB: %d\n", rc); goto fail; } return; @@ -688,7 +690,7 @@ static void amd_pmc_s2idle_restore(void) msg = amd_pmc_get_os_hint(pdev); rc = amd_pmc_send_cmd(pdev, 0, NULL, msg, 0); if (rc) - dev_err(pdev->dev, "resume failed\n"); + dev_err(pdev->dev, "resume failed: %d\n", rc); /* Let SMU know that we are looking for stats */ amd_pmc_send_cmd(pdev, 0, NULL, SMU_MSG_LOG_DUMP_DATA, 0); @@ -700,7 +702,7 @@ static void amd_pmc_s2idle_restore(void) if (enable_stb) rc = amd_pmc_write_stb(pdev, AMD_PMC_STB_PREDEF + 1); if (rc) - dev_err(pdev->dev, "error writing to STB\n"); + dev_err(pdev->dev, "error writing to STB: %d\n", rc); /* Restore the QoS request back to defaults if it was set */ if (pdev->cpu_id == AMD_CPU_ID_CZN) From 0d64787e24c6926b60e7d4f07138b9d937925792 Mon Sep 17 00:00:00 2001 From: Mario Limonciello Date: Thu, 17 Mar 2022 09:14:45 -0500 Subject: [PATCH 134/137] platform/x86: amd-pmc: Drop CPU QoS workaround A workaround was previously introduced as part of commit 646f429ec2de ("platform/x86: amd-pmc: Set QOS during suspend on CZN w/ timer wakeup") to prevent CPUs from going into the deepest state during the execution of the `noirq` stage of `amd_pmc`. As `amd_pmc` has been pushed to the last step for suspend on AMD platforms, this workaround is no longer necessary. Signed-off-by: Mario Limonciello Link: https://lore.kernel.org/r/20220317141445.6498-4-mario.limonciello@amd.com Reviewed-by: Hans de Goede Signed-off-by: Hans de Goede --- drivers/platform/x86/amd-pmc.c | 32 +++----------------------------- 1 file changed, 3 insertions(+), 29 deletions(-) diff --git a/drivers/platform/x86/amd-pmc.c b/drivers/platform/x86/amd-pmc.c index f36cf125b284..7317993cd91b 100644 --- a/drivers/platform/x86/amd-pmc.c +++ b/drivers/platform/x86/amd-pmc.c @@ -21,7 +21,6 @@ #include #include #include -#include #include #include #include @@ -96,9 +95,6 @@ #define PMC_MSG_DELAY_MIN_US 50 #define RESPONSE_REGISTER_LOOP_MAX 20000 -/* QoS request for letting CPUs in idle states, but not the deepest */ -#define AMD_PMC_MAX_IDLE_STATE_LATENCY 3 - #define SOC_SUBSYSTEM_IP_MAX 12 #define DELAY_MIN_US 2000 #define DELAY_MAX_US 3000 @@ -153,7 +149,6 @@ struct amd_pmc_dev { struct device *dev; struct pci_dev *rdev; struct mutex lock; /* generic mutex lock */ - struct pm_qos_request amd_pmc_pm_qos_req; #if IS_ENABLED(CONFIG_DEBUG_FS) struct dentry *dbgfs_dir; #endif /* CONFIG_DEBUG_FS */ @@ -628,14 +623,6 @@ static int amd_pmc_verify_czn_rtc(struct amd_pmc_dev *pdev, u32 *arg) rc = rtc_alarm_irq_enable(rtc_device, 0); dev_dbg(pdev->dev, "wakeup timer programmed for %lld seconds\n", duration); - /* - * Prevent CPUs from getting into deep idle states while sending OS_HINT - * which is otherwise generally safe to send when at least one of the CPUs - * is not in deep idle states. - */ - cpu_latency_qos_update_request(&pdev->amd_pmc_pm_qos_req, AMD_PMC_MAX_IDLE_STATE_LATENCY); - wake_up_all_idle_cpus(); - return rc; } @@ -655,7 +642,7 @@ static void amd_pmc_s2idle_prepare(void) rc = amd_pmc_verify_czn_rtc(pdev, &arg); if (rc) { dev_err(pdev->dev, "failed to set RTC: %d\n", rc); - goto fail; + return; } } @@ -665,20 +652,13 @@ static void amd_pmc_s2idle_prepare(void) rc = amd_pmc_send_cmd(pdev, arg, NULL, msg, 0); if (rc) { dev_err(pdev->dev, "suspend failed: %d\n", rc); - goto fail; + return; } if (enable_stb) rc = amd_pmc_write_stb(pdev, AMD_PMC_STB_PREDEF); - if (rc) { + if (rc) dev_err(pdev->dev, "error writing to STB: %d\n", rc); - goto fail; - } - return; -fail: - if (pdev->cpu_id == AMD_CPU_ID_CZN) - cpu_latency_qos_update_request(&pdev->amd_pmc_pm_qos_req, - PM_QOS_DEFAULT_VALUE); } static void amd_pmc_s2idle_restore(void) @@ -704,11 +684,6 @@ static void amd_pmc_s2idle_restore(void) if (rc) dev_err(pdev->dev, "error writing to STB: %d\n", rc); - /* Restore the QoS request back to defaults if it was set */ - if (pdev->cpu_id == AMD_CPU_ID_CZN) - cpu_latency_qos_update_request(&pdev->amd_pmc_pm_qos_req, - PM_QOS_DEFAULT_VALUE); - /* Notify on failed entry */ amd_pmc_validate_deepest(pdev); } @@ -887,7 +862,6 @@ static int amd_pmc_probe(struct platform_device *pdev) dev_warn(dev->dev, "failed to register LPS0 sleep handler, expect increased power consumption\n"); amd_pmc_dbgfs_register(dev); - cpu_latency_qos_add_request(&dev->amd_pmc_pm_qos_req, PM_QOS_DEFAULT_VALUE); return 0; err_pci_dev_put: From 0c2c21a1fa5b7612fa874b08252e06b34aa4e14a Mon Sep 17 00:00:00 2001 From: Mario Limonciello Date: Thu, 17 Mar 2022 14:03:01 -0500 Subject: [PATCH 135/137] platform/x86: amd-pmc: Only report STB errors when STB enabled Currently if STB is disabled but an earlier function reported an error an incorrect error will be emitted about failing to write to STB. Correct this logic error by only showing errors when STB is enabled. Suggested-by: Hans de Goede Signed-off-by: Mario Limonciello Link: https://lore.kernel.org/r/20220317190301.6818-1-mario.limonciello@amd.com Reviewed-by: Hans de Goede Signed-off-by: Hans de Goede --- drivers/platform/x86/amd-pmc.c | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/drivers/platform/x86/amd-pmc.c b/drivers/platform/x86/amd-pmc.c index 7317993cd91b..e9d0dbbb2887 100644 --- a/drivers/platform/x86/amd-pmc.c +++ b/drivers/platform/x86/amd-pmc.c @@ -655,10 +655,11 @@ static void amd_pmc_s2idle_prepare(void) return; } - if (enable_stb) + if (enable_stb) { rc = amd_pmc_write_stb(pdev, AMD_PMC_STB_PREDEF); - if (rc) - dev_err(pdev->dev, "error writing to STB: %d\n", rc); + if (rc) + dev_err(pdev->dev, "error writing to STB: %d\n", rc); + } } static void amd_pmc_s2idle_restore(void) @@ -679,10 +680,11 @@ static void amd_pmc_s2idle_restore(void) amd_pmc_idlemask_read(pdev, pdev->dev, NULL); /* Write data incremented by 1 to distinguish in stb_read */ - if (enable_stb) + if (enable_stb) { rc = amd_pmc_write_stb(pdev, AMD_PMC_STB_PREDEF + 1); - if (rc) - dev_err(pdev->dev, "error writing to STB: %d\n", rc); + if (rc) + dev_err(pdev->dev, "error writing to STB: %d\n", rc); + } /* Notify on failed entry */ amd_pmc_validate_deepest(pdev); From 06384573a3e8335ac6797577e545c33dbf91b490 Mon Sep 17 00:00:00 2001 From: Mark Pearson Date: Thu, 17 Mar 2022 17:40:07 -0400 Subject: [PATCH 136/137] Documentation: syfs-class-firmware-attributes: Lenovo Certificate support Certificate based authentication is available as an alternative to password based authentication. The WMI commands are cryptographically signed using a separate signing server and will be verified by the BIOS before being accepted. This commit details the fields that are needed to support that implementation. At present the changes are intended for Lenovo platforms, but have been designed to keep them as flexible as possible for future implementations from other vendors. Signed-off-by: Mark Pearson Link: https://lore.kernel.org/r/20220317214008.3459-1-markpearson@lenovo.com Reviewed-by: Hans de Goede Signed-off-by: Hans de Goede --- .../testing/sysfs-class-firmware-attributes | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/Documentation/ABI/testing/sysfs-class-firmware-attributes b/Documentation/ABI/testing/sysfs-class-firmware-attributes index 13e31c6a0e9c..05820365f1ec 100644 --- a/Documentation/ABI/testing/sysfs-class-firmware-attributes +++ b/Documentation/ABI/testing/sysfs-class-firmware-attributes @@ -246,6 +246,51 @@ Description: that is being referenced (e.g hdd0, hdd1 etc) This attribute defaults to device 0. + certificate: + signature: + save_signature: + These attributes are used for certificate based authentication. This is + used in conjunction with a signing server as an alternative to password + based authentication. + The user writes to the attribute(s) with a BASE64 encoded string obtained + from the signing server. + The attributes can be displayed to check the stored value. + + Some usage examples: + Installing a certificate to enable feature: + echo authentication/Admin/current_password + echo > authentication/Admin/certificate + + Updating the installed certificate: + echo > authentication/Admin/signature + echo > authentication/Admin/certificate + + Removing the installed certificate: + echo > authentication/Admin/signature + echo '' > authentication/Admin/certificate + + Changing a BIOS setting: + echo > authentication/Admin/signature + echo > authentication/Admin/save_signature + echo Enable > attribute/PasswordBeep/current_value + + You cannot enable certificate authentication if a supervisor password + has not been set. + Clearing the certificate results in no bios-admin authentication method + being configured allowing anyone to make changes. + After any of these operations the system must reboot for the changes to + take effect. + + certificate_thumbprint: + Read only attribute used to display the MD5, SHA1 and SHA256 thumbprints + for the certificate installed in the BIOS. + + certificate_to_password: + Write only attribute used to switch from certificate based authentication + back to password based. + Usage: + echo > authentication/Admin/signature + echo > authentication/Admin/certificate_to_password What: /sys/class/firmware-attributes/*/attributes/pending_reboot From b49f72e7f96d4ed147447428f2ae5b4cea598ca7 Mon Sep 17 00:00:00 2001 From: Mark Pearson Date: Thu, 17 Mar 2022 17:40:08 -0400 Subject: [PATCH 137/137] platform/x86: think-lmi: Certificate authentication support Implementation of certificate authentication feature for Lenovo platforms. This allows for signed updates of BIOS settings. Functionality supported: - Cert support available check. At initialisation check if BIOS supports certification authentication and if a certificate is installed. Enable the sysfs nodes appropriately - certificate and signature authentication attributes to enable a user to install, update and delete a certificate using signed signatures - certificate_thumbprint to confirm installed certificate details - support to go from certificate to password based authentication - signature and save_signature attributes needed for setting BIOS attributes using certificate authentication. Tested on X1 Carbon G10 and X1 Yoga G7. This feature is not generally available yet but will be released later this year. Note, I also cleaned up the formating of the GUIDs when I was adding the new defines. Hope that's OK to combine in this commit. Signed-off-by: Mark Pearson Link: https://lore.kernel.org/r/20220317214008.3459-2-markpearson@lenovo.com Reviewed-by: Hans de Goede Signed-off-by: Hans de Goede --- drivers/platform/x86/think-lmi.c | 526 ++++++++++++++++++++++++------- drivers/platform/x86/think-lmi.h | 5 + 2 files changed, 424 insertions(+), 107 deletions(-) diff --git a/drivers/platform/x86/think-lmi.c b/drivers/platform/x86/think-lmi.c index 0b73e16cccea..bce17ca97947 100644 --- a/drivers/platform/x86/think-lmi.c +++ b/drivers/platform/x86/think-lmi.c @@ -16,6 +16,7 @@ #include #include #include +#include #include #include "firmware_attributes_class.h" #include "think-lmi.h" @@ -25,95 +26,66 @@ module_param(debug_support, bool, 0444); MODULE_PARM_DESC(debug_support, "Enable debug command support"); /* - * Name: - * Lenovo_BiosSetting - * Description: - * Get item name and settings for current LMI instance. - * Type: - * Query - * Returns: - * "Item,Value" - * Example: - * "WakeOnLAN,Enable" + * Name: BiosSetting + * Description: Get item name and settings for current LMI instance. + * Type: Query + * Returns: "Item,Value" + * Example: "WakeOnLAN,Enable" */ #define LENOVO_BIOS_SETTING_GUID "51F5230E-9677-46CD-A1CF-C0B23EE34DB7" /* - * Name: - * Lenovo_SetBiosSetting - * Description: - * Change the BIOS setting to the desired value using the Lenovo_SetBiosSetting - * class. To save the settings, use the Lenovo_SaveBiosSetting class. + * Name: SetBiosSetting + * Description: Change the BIOS setting to the desired value using the SetBiosSetting + * class. To save the settings, use the SaveBiosSetting class. * BIOS settings and values are case sensitive. * After making changes to the BIOS settings, you must reboot the computer * before the changes will take effect. - * Type: - * Method - * Arguments: - * "Item,Value,Password,Encoding,KbdLang;" - * Example: - * "WakeOnLAN,Disable,pa55w0rd,ascii,us;" + * Type: Method + * Arguments: "Item,Value,Password,Encoding,KbdLang;" + * Example: "WakeOnLAN,Disable,pa55w0rd,ascii,us;" */ #define LENOVO_SET_BIOS_SETTINGS_GUID "98479A64-33F5-4E33-A707-8E251EBBC3A1" /* - * Name: - * Lenovo_SaveBiosSettings - * Description: - * Save any pending changes in settings. - * Type: - * Method - * Arguments: - * "Password,Encoding,KbdLang;" - * Example: - * "pa55w0rd,ascii,us;" + * Name: SaveBiosSettings + * Description: Save any pending changes in settings. + * Type: Method + * Arguments: "Password,Encoding,KbdLang;" + * Example: "pa55w0rd,ascii,us;" */ #define LENOVO_SAVE_BIOS_SETTINGS_GUID "6A4B54EF-A5ED-4D33-9455-B0D9B48DF4B3" /* - * Name: - * Lenovo_BiosPasswordSettings - * Description: - * Return BIOS Password settings - * Type: - * Query - * Returns: - * PasswordMode, PasswordState, MinLength, MaxLength, + * Name: BiosPasswordSettings + * Description: Return BIOS Password settings + * Type: Query + * Returns: PasswordMode, PasswordState, MinLength, MaxLength, * SupportedEncoding, SupportedKeyboard */ #define LENOVO_BIOS_PASSWORD_SETTINGS_GUID "8ADB159E-1E32-455C-BC93-308A7ED98246" /* - * Name: - * Lenovo_SetBiosPassword - * Description: - * Change a specific password. + * Name: SetBiosPassword + * Description: Change a specific password. * - BIOS settings cannot be changed at the same boot as power-on * passwords (POP) and hard disk passwords (HDP). If you want to change * BIOS settings and POP or HDP, you must reboot the system after changing * one of them. * - A password cannot be set using this method when one does not already * exist. Passwords can only be updated or cleared. - * Type: - * Method - * Arguments: - * "PasswordType,CurrentPassword,NewPassword,Encoding,KbdLang;" - * Example: - * "pop,pa55w0rd,newpa55w0rd,ascii,us;” + * Type: Method + * Arguments: "PasswordType,CurrentPassword,NewPassword,Encoding,KbdLang;" + * Example: "pop,pa55w0rd,newpa55w0rd,ascii,us;” */ #define LENOVO_SET_BIOS_PASSWORD_GUID "2651D9FD-911C-4B69-B94E-D0DED5963BD7" /* - * Name: - * Lenovo_GetBiosSelections - * Description: - * Return a list of valid settings for a given item. - * Type: - * Method - * Arguments: - * "Item" - * Returns: - * "Value1,Value2,Value3,..." + * Name: GetBiosSelections + * Description: Return a list of valid settings for a given item. + * Type: Method + * Arguments: "Item" + * Returns: "Value1,Value2,Value3,..." * Example: * -> "FlashOverLAN" * <- "Enabled,Disabled" @@ -121,18 +93,14 @@ MODULE_PARM_DESC(debug_support, "Enable debug command support"); #define LENOVO_GET_BIOS_SELECTIONS_GUID "7364651A-132F-4FE7-ADAA-40C6C7EE2E3B" /* - * Name: - * Lenovo_DebugCmdGUID - * Description - * Debug entry GUID method for entering debug commands to the BIOS + * Name: DebugCmd + * Description: Debug entry method for entering debug commands to the BIOS */ #define LENOVO_DEBUG_CMD_GUID "7FF47003-3B6C-4E5E-A227-E979824A85D1" /* - * Name: - * Lenovo_OpcodeIF - * Description: - * Opcode interface which provides the ability to set multiple + * Name: OpcodeIF + * Description: Opcode interface which provides the ability to set multiple * parameters and then trigger an action with a final command. * This is particularly useful for simplifying setting passwords. * With this support comes the ability to set System, HDD and NVMe @@ -141,10 +109,71 @@ MODULE_PARM_DESC(debug_support, "Enable debug command support"); */ #define LENOVO_OPCODE_IF_GUID "DFDDEF2C-57D4-48ce-B196-0FB787D90836" +/* + * Name: SetBiosCert + * Description: Install BIOS certificate. + * Type: Method + * Arguments: "Certificate,Password" + * You must reboot the computer before the changes will take effect. + */ +#define LENOVO_SET_BIOS_CERT_GUID "26861C9F-47E9-44C4-BD8B-DFE7FA2610FE" + +/* + * Name: UpdateBiosCert + * Description: Update BIOS certificate. + * Type: Method + * Format: "Certificate,Signature" + * You must reboot the computer before the changes will take effect. + */ +#define LENOVO_UPDATE_BIOS_CERT_GUID "9AA3180A-9750-41F7-B9F7-D5D3B1BAC3CE" + +/* + * Name: ClearBiosCert + * Description: Uninstall BIOS certificate. + * Type: Method + * Format: "Serial,Signature" + * You must reboot the computer before the changes will take effect. + */ +#define LENOVO_CLEAR_BIOS_CERT_GUID "B2BC39A7-78DD-4D71-B059-A510DEC44890" +/* + * Name: CertToPassword + * Description: Switch from certificate to password authentication. + * Type: Method + * Format: "Password,Signature" + * You must reboot the computer before the changes will take effect. + */ +#define LENOVO_CERT_TO_PASSWORD_GUID "0DE8590D-5510-4044-9621-77C227F5A70D" + +/* + * Name: SetBiosSettingCert + * Description: Set attribute using certificate authentication. + * Type: Method + * Format: "Item,Value,Signature" + */ +#define LENOVO_SET_BIOS_SETTING_CERT_GUID "34A008CC-D205-4B62-9E67-31DFA8B90003" + +/* + * Name: SaveBiosSettingCert + * Description: Save any pending changes in settings. + * Type: Method + * Format: "Signature" + */ +#define LENOVO_SAVE_BIOS_SETTING_CERT_GUID "C050FB9D-DF5F-4606-B066-9EFC401B2551" + +/* + * Name: CertThumbprint + * Description: Display Certificate thumbprints + * Type: Query + * Returns: MD5, SHA1 & SHA256 thumbprints + */ +#define LENOVO_CERT_THUMBPRINT_GUID "C59119ED-1C0D-4806-A8E9-59AA318176C4" + #define TLMI_POP_PWD (1 << 0) #define TLMI_PAP_PWD (1 << 1) #define TLMI_HDD_PWD (1 << 2) #define TLMI_SYS_PWD (1 << 3) +#define TLMI_CERT (1 << 7) + #define to_tlmi_pwd_setting(kobj) container_of(kobj, struct tlmi_pwd_setting, kobj) #define to_tlmi_attr_setting(kobj) container_of(kobj, struct tlmi_attr_setting, kobj) @@ -168,6 +197,13 @@ static struct think_lmi tlmi_priv; static struct class *fw_attr_class; /* ------ Utility functions ------------*/ +/* Strip out CR if one is present */ +static void strip_cr(char *str) +{ + char *p = strchrnul(str, '\n'); + *p = '\0'; +} + /* Convert BIOS WMI error string to suitable error code */ static int tlmi_errstr_to_err(const char *errstr) { @@ -365,7 +401,6 @@ static ssize_t current_password_store(struct kobject *kobj, { struct tlmi_pwd_setting *setting = to_tlmi_pwd_setting(kobj); size_t pwdlen; - char *p; pwdlen = strlen(buf); /* pwdlen == 0 is allowed to clear the password */ @@ -374,8 +409,7 @@ static ssize_t current_password_store(struct kobject *kobj, strscpy(setting->password, buf, setting->maxlen); /* Strip out CR if one is present, setting password won't work if it is present */ - p = strchrnul(setting->password, '\n'); - *p = '\0'; + strip_cr(setting->password); return count; } @@ -386,7 +420,7 @@ static ssize_t new_password_store(struct kobject *kobj, const char *buf, size_t count) { struct tlmi_pwd_setting *setting = to_tlmi_pwd_setting(kobj); - char *auth_str, *new_pwd, *p; + char *auth_str, *new_pwd; size_t pwdlen; int ret; @@ -401,8 +435,7 @@ static ssize_t new_password_store(struct kobject *kobj, return -ENOMEM; /* Strip out CR if one is present, setting password won't work if it is present */ - p = strchrnul(new_pwd, '\n'); - *p = '\0'; + strip_cr(new_pwd); pwdlen = strlen(new_pwd); /* pwdlen == 0 is allowed to clear the password */ @@ -608,18 +641,258 @@ static ssize_t level_store(struct kobject *kobj, static struct kobj_attribute auth_level = __ATTR_RW(level); +static ssize_t cert_thumbprint(char *buf, const char *arg, int count) +{ + const struct acpi_buffer input = { strlen(arg), (char *)arg }; + struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL }; + const union acpi_object *obj; + acpi_status status; + + status = wmi_evaluate_method(LENOVO_CERT_THUMBPRINT_GUID, 0, 0, &input, &output); + if (ACPI_FAILURE(status)) { + kfree(output.pointer); + return -EIO; + } + obj = output.pointer; + if (!obj) + return -ENOMEM; + if (obj->type != ACPI_TYPE_STRING || !obj->string.pointer) { + kfree(output.pointer); + return -EIO; + } + count += sysfs_emit_at(buf, count, "%s : %s\n", arg, (char *)obj->string.pointer); + kfree(output.pointer); + + return count; +} + +static ssize_t certificate_thumbprint_show(struct kobject *kobj, struct kobj_attribute *attr, + char *buf) +{ + struct tlmi_pwd_setting *setting = to_tlmi_pwd_setting(kobj); + int count = 0; + + if (!tlmi_priv.certificate_support || !setting->cert_installed) + return -EOPNOTSUPP; + + count += cert_thumbprint(buf, "Md5", count); + count += cert_thumbprint(buf, "Sha1", count); + count += cert_thumbprint(buf, "Sha256", count); + return count; +} + +static struct kobj_attribute auth_cert_thumb = __ATTR_RO(certificate_thumbprint); + +static ssize_t cert_to_password_store(struct kobject *kobj, + struct kobj_attribute *attr, + const char *buf, size_t count) +{ + struct tlmi_pwd_setting *setting = to_tlmi_pwd_setting(kobj); + char *auth_str, *passwd; + int ret; + + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + + if (!tlmi_priv.certificate_support) + return -EOPNOTSUPP; + + if (!setting->cert_installed) + return -EINVAL; + + if (!setting->signature || !setting->signature[0]) + return -EACCES; + + passwd = kstrdup(buf, GFP_KERNEL); + if (!passwd) + return -ENOMEM; + + /* Strip out CR if one is present */ + strip_cr(passwd); + + /* Format: 'Password,Signature' */ + auth_str = kasprintf(GFP_KERNEL, "%s,%s", passwd, setting->signature); + if (!auth_str) { + kfree(passwd); + return -ENOMEM; + } + ret = tlmi_simple_call(LENOVO_CERT_TO_PASSWORD_GUID, auth_str); + kfree(auth_str); + kfree(passwd); + + return ret ?: count; +} + +static struct kobj_attribute auth_cert_to_password = __ATTR_WO(cert_to_password); + +static ssize_t certificate_store(struct kobject *kobj, + struct kobj_attribute *attr, + const char *buf, size_t count) +{ + struct tlmi_pwd_setting *setting = to_tlmi_pwd_setting(kobj); + char *auth_str, *new_cert; + char *guid; + int ret; + + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + + if (!tlmi_priv.certificate_support) + return -EOPNOTSUPP; + + new_cert = kstrdup(buf, GFP_KERNEL); + if (!new_cert) + return -ENOMEM; + /* Strip out CR if one is present */ + strip_cr(new_cert); + + /* If empty then clear installed certificate */ + if (new_cert[0] == '\0') { /* Clear installed certificate */ + kfree(new_cert); + + /* Check that signature is set */ + if (!setting->signature || !setting->signature[0]) + return -EACCES; + + /* Format: 'serial#, signature' */ + auth_str = kasprintf(GFP_KERNEL, "%s,%s", + dmi_get_system_info(DMI_PRODUCT_SERIAL), + setting->signature); + if (!auth_str) + return -ENOMEM; + + ret = tlmi_simple_call(LENOVO_CLEAR_BIOS_CERT_GUID, auth_str); + kfree(auth_str); + if (ret) + return ret; + + kfree(setting->certificate); + setting->certificate = NULL; + return count; + } + + if (setting->cert_installed) { + /* Certificate is installed so this is an update */ + if (!setting->signature || !setting->signature[0]) { + kfree(new_cert); + return -EACCES; + } + guid = LENOVO_UPDATE_BIOS_CERT_GUID; + /* Format: 'Certificate,Signature' */ + auth_str = kasprintf(GFP_KERNEL, "%s,%s", + new_cert, setting->signature); + } else { + /* This is a fresh install */ + if (!setting->valid || !setting->password[0]) { + kfree(new_cert); + return -EACCES; + } + guid = LENOVO_SET_BIOS_CERT_GUID; + /* Format: 'Certificate,Admin-password' */ + auth_str = kasprintf(GFP_KERNEL, "%s,%s", + new_cert, setting->password); + } + if (!auth_str) { + kfree(new_cert); + return -ENOMEM; + } + + ret = tlmi_simple_call(guid, auth_str); + kfree(auth_str); + if (ret) { + kfree(new_cert); + return ret; + } + + kfree(setting->certificate); + setting->certificate = new_cert; + return count; +} + +static struct kobj_attribute auth_certificate = __ATTR_WO(certificate); + +static ssize_t signature_store(struct kobject *kobj, + struct kobj_attribute *attr, + const char *buf, size_t count) +{ + struct tlmi_pwd_setting *setting = to_tlmi_pwd_setting(kobj); + char *new_signature; + + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + + if (!tlmi_priv.certificate_support) + return -EOPNOTSUPP; + + new_signature = kstrdup(buf, GFP_KERNEL); + if (!new_signature) + return -ENOMEM; + + /* Strip out CR if one is present */ + strip_cr(new_signature); + + /* Free any previous signature */ + kfree(setting->signature); + setting->signature = new_signature; + + return count; +} + +static struct kobj_attribute auth_signature = __ATTR_WO(signature); + +static ssize_t save_signature_store(struct kobject *kobj, + struct kobj_attribute *attr, + const char *buf, size_t count) +{ + struct tlmi_pwd_setting *setting = to_tlmi_pwd_setting(kobj); + char *new_signature; + + if (!capable(CAP_SYS_ADMIN)) + return -EPERM; + + if (!tlmi_priv.certificate_support) + return -EOPNOTSUPP; + + new_signature = kstrdup(buf, GFP_KERNEL); + if (!new_signature) + return -ENOMEM; + + /* Strip out CR if one is present */ + strip_cr(new_signature); + + /* Free any previous signature */ + kfree(setting->save_signature); + setting->save_signature = new_signature; + + return count; +} + +static struct kobj_attribute auth_save_signature = __ATTR_WO(save_signature); + static umode_t auth_attr_is_visible(struct kobject *kobj, struct attribute *attr, int n) { struct tlmi_pwd_setting *setting = to_tlmi_pwd_setting(kobj); - /*We only want to display level and index settings on HDD/NVMe */ + /* We only want to display level and index settings on HDD/NVMe */ if ((attr == (struct attribute *)&auth_index) || (attr == (struct attribute *)&auth_level)) { if ((setting == tlmi_priv.pwd_hdd) || (setting == tlmi_priv.pwd_nvme)) return attr->mode; return 0; } + + /* We only display certificates on Admin account, if supported */ + if ((attr == (struct attribute *)&auth_certificate) || + (attr == (struct attribute *)&auth_signature) || + (attr == (struct attribute *)&auth_save_signature) || + (attr == (struct attribute *)&auth_cert_thumb) || + (attr == (struct attribute *)&auth_cert_to_password)) { + if ((setting == tlmi_priv.pwd_admin) && tlmi_priv.certificate_support) + return attr->mode; + return 0; + } + return attr->mode; } @@ -635,6 +908,11 @@ static struct attribute *auth_attrs[] = { &auth_kbdlang.attr, &auth_index.attr, &auth_level.attr, + &auth_certificate.attr, + &auth_signature.attr, + &auth_save_signature.attr, + &auth_cert_thumb.attr, + &auth_cert_to_password.attr, NULL }; @@ -689,7 +967,6 @@ static ssize_t current_value_store(struct kobject *kobj, struct tlmi_attr_setting *setting = to_tlmi_attr_setting(kobj); char *set_str = NULL, *new_setting = NULL; char *auth_str = NULL; - char *p; int ret; if (!tlmi_priv.can_set_bios_settings) @@ -700,40 +977,60 @@ static ssize_t current_value_store(struct kobject *kobj, return -ENOMEM; /* Strip out CR if one is present */ - p = strchrnul(new_setting, '\n'); - *p = '\0'; + strip_cr(new_setting); - if (tlmi_priv.pwd_admin->valid && tlmi_priv.pwd_admin->password[0]) { - auth_str = kasprintf(GFP_KERNEL, "%s,%s,%s;", - tlmi_priv.pwd_admin->password, - encoding_options[tlmi_priv.pwd_admin->encoding], - tlmi_priv.pwd_admin->kbdlang); - if (!auth_str) { + /* Check if certificate authentication is enabled and active */ + if (tlmi_priv.certificate_support && tlmi_priv.pwd_admin->cert_installed) { + if (!tlmi_priv.pwd_admin->signature || !tlmi_priv.pwd_admin->save_signature) { + ret = -EINVAL; + goto out; + } + set_str = kasprintf(GFP_KERNEL, "%s,%s,%s", setting->display_name, + new_setting, tlmi_priv.pwd_admin->signature); + if (!set_str) { ret = -ENOMEM; goto out; } + + ret = tlmi_simple_call(LENOVO_SET_BIOS_SETTING_CERT_GUID, set_str); + if (ret) + goto out; + ret = tlmi_simple_call(LENOVO_SAVE_BIOS_SETTING_CERT_GUID, + tlmi_priv.pwd_admin->save_signature); + if (ret) + goto out; + } else { /* Non certiifcate based authentication */ + if (tlmi_priv.pwd_admin->valid && tlmi_priv.pwd_admin->password[0]) { + auth_str = kasprintf(GFP_KERNEL, "%s,%s,%s;", + tlmi_priv.pwd_admin->password, + encoding_options[tlmi_priv.pwd_admin->encoding], + tlmi_priv.pwd_admin->kbdlang); + if (!auth_str) { + ret = -ENOMEM; + goto out; + } + } + + if (auth_str) + set_str = kasprintf(GFP_KERNEL, "%s,%s,%s", setting->display_name, + new_setting, auth_str); + else + set_str = kasprintf(GFP_KERNEL, "%s,%s;", setting->display_name, + new_setting); + if (!set_str) { + ret = -ENOMEM; + goto out; + } + + ret = tlmi_simple_call(LENOVO_SET_BIOS_SETTINGS_GUID, set_str); + if (ret) + goto out; + + if (auth_str) + ret = tlmi_save_bios_settings(auth_str); + else + ret = tlmi_save_bios_settings(""); } - - if (auth_str) - set_str = kasprintf(GFP_KERNEL, "%s,%s,%s", setting->display_name, - new_setting, auth_str); - else - set_str = kasprintf(GFP_KERNEL, "%s,%s;", setting->display_name, - new_setting); - if (!set_str) { - ret = -ENOMEM; - goto out; - } - - ret = tlmi_simple_call(LENOVO_SET_BIOS_SETTINGS_GUID, set_str); - if (ret) - goto out; - - if (auth_str) - ret = tlmi_save_bios_settings(auth_str); - else - ret = tlmi_save_bios_settings(""); - if (!ret && !tlmi_priv.pending_changes) { tlmi_priv.pending_changes = true; /* let userland know it may need to check reboot pending again */ @@ -829,7 +1126,6 @@ static ssize_t debug_cmd_store(struct kobject *kobj, struct kobj_attribute *attr { char *set_str = NULL, *new_setting = NULL; char *auth_str = NULL; - char *p; int ret; if (!tlmi_priv.can_debug_cmd) @@ -840,8 +1136,7 @@ static ssize_t debug_cmd_store(struct kobject *kobj, struct kobj_attribute *attr return -ENOMEM; /* Strip out CR if one is present */ - p = strchrnul(new_setting, '\n'); - *p = '\0'; + strip_cr(new_setting); if (tlmi_priv.pwd_admin->valid && tlmi_priv.pwd_admin->password[0]) { auth_str = kasprintf(GFP_KERNEL, "%s,%s,%s;", @@ -896,6 +1191,7 @@ static void tlmi_release_attr(void) sysfs_remove_file(&tlmi_priv.attribute_kset->kobj, &pending_reboot.attr); if (tlmi_priv.can_debug_cmd && debug_support) sysfs_remove_file(&tlmi_priv.attribute_kset->kobj, &debug_cmd.attr); + kset_unregister(tlmi_priv.attribute_kset); /* Authentication structures */ @@ -914,6 +1210,11 @@ static void tlmi_release_attr(void) } kset_unregister(tlmi_priv.authentication_kset); + + /* Free up any saved certificates/signatures */ + kfree(tlmi_priv.pwd_admin->certificate); + kfree(tlmi_priv.pwd_admin->signature); + kfree(tlmi_priv.pwd_admin->save_signature); } static int tlmi_sysfs_init(void) @@ -975,6 +1276,7 @@ static int tlmi_sysfs_init(void) if (ret) goto fail_create_attr; } + /* Create authentication entries */ tlmi_priv.authentication_kset = kset_create_and_add("authentication", NULL, &tlmi_priv.class_dev->kobj); @@ -1087,6 +1389,11 @@ static int tlmi_analyze(void) if (wmi_has_guid(LENOVO_OPCODE_IF_GUID)) tlmi_priv.opcode_support = true; + if (wmi_has_guid(LENOVO_SET_BIOS_CERT_GUID) && + wmi_has_guid(LENOVO_SET_BIOS_SETTING_CERT_GUID) && + wmi_has_guid(LENOVO_SAVE_BIOS_SETTING_CERT_GUID)) + tlmi_priv.certificate_support = true; + /* * Try to find the number of valid settings of this machine * and use it to create sysfs attributes. @@ -1198,6 +1505,11 @@ static int tlmi_analyze(void) } } } + + if (tlmi_priv.certificate_support && + (tlmi_priv.pwdcfg.core.password_state & TLMI_CERT)) + tlmi_priv.pwd_admin->cert_installed = true; + return 0; fail_clear_attr: diff --git a/drivers/platform/x86/think-lmi.h b/drivers/platform/x86/think-lmi.h index e46c7f383353..4f69df6eed07 100644 --- a/drivers/platform/x86/think-lmi.h +++ b/drivers/platform/x86/think-lmi.h @@ -62,6 +62,10 @@ struct tlmi_pwd_setting { char kbdlang[TLMI_LANG_MAXLEN]; int index; /*Used for HDD and NVME auth */ enum level_option level; + bool cert_installed; + char *certificate; + char *signature; + char *save_signature; }; /* Attribute setting details */ @@ -82,6 +86,7 @@ struct think_lmi { bool pending_changes; bool can_debug_cmd; bool opcode_support; + bool certificate_support; struct tlmi_attr_setting *setting[TLMI_SETTINGS_COUNT]; struct device *class_dev;