diff --git a/VERSION b/VERSION index 4074fe2..10abd6a 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -6.6 +6.6.6 diff --git a/patches/asuslinux-rebased/v2-0002-ALSA-hda-cs35l41-Support-ASUS-2023-laptops-with-m.patch b/patches/asuslinux-rebased/v2-0002-ALSA-hda-cs35l41-Support-ASUS-2023-laptops-with-m.patch deleted file mode 100644 index efc2c31..0000000 --- a/patches/asuslinux-rebased/v2-0002-ALSA-hda-cs35l41-Support-ASUS-2023-laptops-with-m.patch +++ /dev/null @@ -1,109 +0,0 @@ -From 34713cb8d4e13bf9e3b1403cdea9551e0532ec5c Mon Sep 17 00:00:00 2001 -From: "Luke D. Jones" -Date: Wed, 23 Aug 2023 11:05:59 +1200 -Subject: [PATCH v2 2/2] ALSA: hda: cs35l41: Support ASUS 2023 laptops with - missing DSD - -Support adding the missing DSD properties required for ASUS ROG 2023 -laptops and other ASUS laptops to properly utilise the cs35l41. - -The currently added laptops are: -- ASUS GS650P, i2c -- ASUS GA402X, i2c -- ASUS GU604V, spi -- ASUS GU603V, spi -- ASUS GV601V, spi -- ASUS GZ301V, spi -- ASUS ROG ALLY, i2c -- ASUS G614J, spi -- ASUS G634J, spi -- ASUS G614JI, spi -- ASUS G713P, i2c -- ASUS H7604JV, spi - -The SPI connected amps may be required to use an external DSD patch -to fix or add the "cs-gpios" property. - -Co-developed-by: Jonathan LoBue -Signed-off-by: Jonathan LoBue -Co-developed-by: Luke D. Jones -Signed-off-by: Luke D. Jones ---- - sound/pci/hda/cs35l41_hda_property.c | 53 ++++++++++++++++++++++++++++ - 1 file changed, 53 insertions(+) - -diff --git a/sound/pci/hda/cs35l41_hda_property.c b/sound/pci/hda/cs35l41_hda_property.c -index 673f23257a09..b06e8ca5f4b4 100644 ---- a/sound/pci/hda/cs35l41_hda_property.c -+++ b/sound/pci/hda/cs35l41_hda_property.c -@@ -43,6 +43,47 @@ static int lenovo_legion_no_acpi(struct cs35l41_hda *cs35l41, struct device *phy - return 0; - } - -+/* -+ * The CSC3551 is used in almost the entire ASUS ROG laptop range in 2023, this is likely to -+ * also include many non ROG labelled laptops. It is also used with either I2C connection or -+ * SPI connection. The SPI connected versions may be missing a chip select GPIO and require -+ * an DSD table patch. -+ */ -+static int asus_rog_2023_spkr_id2(struct cs35l41_hda *cs35l41, struct device *physdev, int id, -+ const char *hid) -+{ -+ struct cs35l41_hw_cfg *hw_cfg = &cs35l41->hw_cfg; -+ int reset_gpio = 0; -+ int spkr_gpio = 2; -+ -+ /* check SPI or I2C address to assign the index */ -+ cs35l41->index = (id == 0 || id == 0x40) ? 0 : 1; -+ cs35l41->channel_index = 0; -+ hw_cfg->spk_pos = cs35l41->index; -+ hw_cfg->bst_type = CS35L41_EXT_BOOST; -+ hw_cfg->gpio1.func = CS35l41_VSPK_SWITCH; -+ hw_cfg->gpio1.valid = true; -+ hw_cfg->gpio2.func = CS35L41_INTERRUPT; -+ hw_cfg->gpio2.valid = true; -+ -+ if (strcmp(cs35l41->acpi_subsystem_id, "10431483") == 0) -+ spkr_gpio = 1; -+ cs35l41->speaker_id = cs35l41_get_speaker_id(physdev, 0, 0, spkr_gpio); -+ -+ if (strcmp(cs35l41->acpi_subsystem_id, "10431463") == 0) -+ reset_gpio = 0; -+ else if (strcmp(cs35l41->acpi_subsystem_id, "10431473") == 0 -+ || strcmp(cs35l41->acpi_subsystem_id, "10431483") == 0 -+ || strcmp(cs35l41->acpi_subsystem_id, "10431493") == 0) { -+ reset_gpio = 1; -+ } -+ cs35l41->reset_gpio = gpiod_get_index(physdev, NULL, reset_gpio, GPIOD_OUT_HIGH); -+ -+ hw_cfg->valid = true; -+ -+ return 0; -+} -+ - struct cs35l41_prop_model { - const char *hid; - const char *ssid; -@@ -53,6 +94,18 @@ struct cs35l41_prop_model { - const struct cs35l41_prop_model cs35l41_prop_model_table[] = { - { "CLSA0100", NULL, lenovo_legion_no_acpi }, - { "CLSA0101", NULL, lenovo_legion_no_acpi }, - { "CSC3551", "103C89C6", hp_vision_acpi_fix }, -+ { "CSC3551", "10431433", asus_rog_2023_spkr_id2 }, // ASUS GS650P - i2c -+ { "CSC3551", "10431463", asus_rog_2023_spkr_id2 }, // ASUS GA402X/N - i2c, reset gpio 0 -+ { "CSC3551", "10431473", asus_rog_2023_spkr_id2 }, // ASUS GU604V - spi, reset gpio 1 -+ { "CSC3551", "10431483", asus_rog_2023_spkr_id2 }, // ASUS GU603V - spi, reset 1, spkr 1 -+ { "CSC3551", "10431493", asus_rog_2023_spkr_id2 }, // ASUS GV601V - spi, reset gpio 1 -+ { "CSC3551", "10431573", asus_rog_2023_spkr_id2 }, // ASUS GZ301V - spi, reset gpio 0 -+ { "CSC3551", "104317F3", asus_rog_2023_spkr_id2 }, // ASUS ROG ALLY - i2c -+ { "CSC3551", "10431B93", asus_rog_2023_spkr_id2 }, // ASUS G614J - spi, reset gpio 0 -+ { "CSC3551", "10431CAF", asus_rog_2023_spkr_id2 }, // ASUS G634J - spi, reset gpio 0 -+ { "CSC3551", "10431C9F", asus_rog_2023_spkr_id2 }, // ASUS G614JI -spi, reset gpio 0 -+ { "CSC3551", "10431D1F", asus_rog_2023_spkr_id2 }, // ASUS G713P - i2c -+ { "CSC3551", "10431F1F", asus_rog_2023_spkr_id2 }, // ASUS H7604JV - spi, reset gpio 0 - {} - }; - --- -2.41.0 - diff --git a/patches/asuslinux/ROG-ALLY-NCT6775-PLATFORM.patch b/patches/asuslinux/ROG-ALLY-NCT6775-PLATFORM.patch new file mode 100644 index 0000000..806ad79 --- /dev/null +++ b/patches/asuslinux/ROG-ALLY-NCT6775-PLATFORM.patch @@ -0,0 +1,12 @@ +diff --git a/drivers/hwmon/nct6775-platform.c b/drivers/hwmon/nct6775-platform.c +index 81bf03d..96d875b 100644 +--- a/drivers/hwmon/nct6775-platform.c ++++ b/drivers/hwmon/nct6775-platform.c +@@ -1359,6 +1359,7 @@ static const char * const asus_msi_boards[] = { + "ProArt X670E-CREATOR WIFI", + "ProArt Z690-CREATOR WIFI", + "ProArt Z790-CREATOR WIFI", ++ "RC71L", + "ROG CROSSHAIR X670E EXTREME", + "ROG CROSSHAIR X670E GENE", + "ROG CROSSHAIR X670E HERO", diff --git a/patches/asuslinux/amd-tablet-sfh.patch b/patches/asuslinux/amd-tablet-sfh.patch deleted file mode 100644 index febf878..0000000 --- a/patches/asuslinux/amd-tablet-sfh.patch +++ /dev/null @@ -1,181 +0,0 @@ -diff --git a/drivers/hid/amd-sfh-hid/amd_sfh_client.c b/drivers/hid/amd-sfh-hid/amd_sfh_client.c -index bdb578e0899f..f98a02eee783 100644 ---- a/drivers/hid/amd-sfh-hid/amd_sfh_client.c -+++ b/drivers/hid/amd-sfh-hid/amd_sfh_client.c -@@ -146,6 +146,8 @@ static const char *get_sensor_name(int idx) - return "gyroscope"; - case mag_idx: - return "magnetometer"; -+ case tms_idx: -+ return "tablet-mode-switch"; - case als_idx: - case ACS_IDX: /* ambient color sensor */ - return "ALS"; -diff --git a/drivers/hid/amd-sfh-hid/amd_sfh_hid.h b/drivers/hid/amd-sfh-hid/amd_sfh_hid.h -index 97296f587bc7..cea7ec6f6288 100644 ---- a/drivers/hid/amd-sfh-hid/amd_sfh_hid.h -+++ b/drivers/hid/amd-sfh-hid/amd_sfh_hid.h -@@ -11,7 +11,7 @@ - #ifndef AMDSFH_HID_H - #define AMDSFH_HID_H - --#define MAX_HID_DEVICES 6 -+#define MAX_HID_DEVICES 7 - #define AMD_SFH_HID_VENDOR 0x1022 - #define AMD_SFH_HID_PRODUCT 0x0001 - -diff --git a/drivers/hid/amd-sfh-hid/amd_sfh_pcie.c b/drivers/hid/amd-sfh-hid/amd_sfh_pcie.c -index 2530fa98b568..af5b37a62b10 100644 ---- a/drivers/hid/amd-sfh-hid/amd_sfh_pcie.c -+++ b/drivers/hid/amd-sfh-hid/amd_sfh_pcie.c -@@ -27,6 +27,7 @@ - #define ACEL_EN BIT(0) - #define GYRO_EN BIT(1) - #define MAGNO_EN BIT(2) -+#define TMS_EN BIT(15) - #define HPD_EN BIT(16) - #define ALS_EN BIT(19) - #define ACS_EN BIT(22) -@@ -228,6 +229,9 @@ int amd_mp2_get_sensor_num(struct amd_mp2_dev *privdata, u8 *sensor_id) - if (MAGNO_EN & activestatus) - sensor_id[num_of_sensors++] = mag_idx; - -+ if (TMS_EN & activestatus) -+ sensor_id[num_of_sensors++] = tms_idx; -+ - if (ALS_EN & activestatus) - sensor_id[num_of_sensors++] = als_idx; - -diff --git a/drivers/hid/amd-sfh-hid/amd_sfh_pcie.h b/drivers/hid/amd-sfh-hid/amd_sfh_pcie.h -index 70add75fc506..60130ad846a4 100644 ---- a/drivers/hid/amd-sfh-hid/amd_sfh_pcie.h -+++ b/drivers/hid/amd-sfh-hid/amd_sfh_pcie.h -@@ -79,6 +79,7 @@ enum sensor_idx { - accel_idx = 0, - gyro_idx = 1, - mag_idx = 2, -+ tms_idx = 15, - als_idx = 19 - }; - -diff --git a/drivers/hid/amd-sfh-hid/hid_descriptor/amd_sfh_hid_desc.c b/drivers/hid/amd-sfh-hid/hid_descriptor/amd_sfh_hid_desc.c -index 8716a05950c8..b6725e8daf0c 100644 ---- a/drivers/hid/amd-sfh-hid/hid_descriptor/amd_sfh_hid_desc.c -+++ b/drivers/hid/amd-sfh-hid/hid_descriptor/amd_sfh_hid_desc.c -@@ -47,6 +47,11 @@ static int get_report_descriptor(int sensor_idx, u8 *rep_desc) - memcpy(rep_desc, comp3_report_descriptor, - sizeof(comp3_report_descriptor)); - break; -+ case tms_idx: /* tablet mode switch */ -+ memset(rep_desc, 0, sizeof(tms_report_descriptor)); -+ memcpy(rep_desc, tms_report_descriptor, -+ sizeof(tms_report_descriptor)); -+ break; - case als_idx: /* ambient light sensor */ - case ACS_IDX: /* ambient color sensor */ - memset(rep_desc, 0, sizeof(als_report_descriptor)); -@@ -97,6 +102,16 @@ static u32 get_descr_sz(int sensor_idx, int descriptor_name) - return sizeof(struct magno_feature_report); - } - break; -+ case tms_idx: -+ switch (descriptor_name) { -+ case descr_size: -+ return sizeof(tms_report_descriptor); -+ case input_size: -+ return sizeof(struct tms_input_report); -+ case feature_size: -+ return sizeof(struct tms_feature_report); -+ } -+ break; - case als_idx: - case ACS_IDX: /* ambient color sensor */ - switch (descriptor_name) { -@@ -140,6 +155,7 @@ static u8 get_feature_report(int sensor_idx, int report_id, u8 *feature_report) - struct accel3_feature_report acc_feature; - struct gyro_feature_report gyro_feature; - struct magno_feature_report magno_feature; -+ struct tms_feature_report tms_feature; - struct hpd_feature_report hpd_feature; - struct als_feature_report als_feature; - u8 report_size = 0; -@@ -175,6 +191,11 @@ static u8 get_feature_report(int sensor_idx, int report_id, u8 *feature_report) - memcpy(feature_report, &magno_feature, sizeof(magno_feature)); - report_size = sizeof(magno_feature); - break; -+ case tms_idx: /* tablet mode switch */ -+ get_common_features(&tms_feature.common_property, report_id); -+ memcpy(feature_report, &tms_feature, sizeof(tms_feature)); -+ report_size = sizeof(tms_feature); -+ break; - case als_idx: /* ambient light sensor */ - case ACS_IDX: /* ambient color sensor */ - get_common_features(&als_feature.common_property, report_id); -@@ -214,6 +235,7 @@ static u8 get_input_report(u8 current_index, int sensor_idx, int report_id, - struct accel3_input_report acc_input; - struct gyro_input_report gyro_input; - struct hpd_input_report hpd_input; -+ struct tms_input_report tms_input; - struct als_input_report als_input; - struct hpd_status hpdstatus; - u8 report_size = 0; -@@ -247,6 +269,11 @@ static u8 get_input_report(u8 current_index, int sensor_idx, int report_id, - memcpy(input_report, &magno_input, sizeof(magno_input)); - report_size = sizeof(magno_input); - break; -+ case tms_idx: /* tablet mode switch */ -+ get_common_inputs(&tms_input.common_property, report_id); -+ report_size = sizeof(tms_input); -+ memcpy(input_report, &tms_input, sizeof(tms_input)); -+ break; - case als_idx: /* Als */ - case ACS_IDX: /* ambient color sensor */ - get_common_inputs(&als_input.common_property, report_id); -diff --git a/drivers/hid/amd-sfh-hid/hid_descriptor/amd_sfh_hid_desc.h b/drivers/hid/amd-sfh-hid/hid_descriptor/amd_sfh_hid_desc.h -index ebd55675eb62..b22068a47429 100644 ---- a/drivers/hid/amd-sfh-hid/hid_descriptor/amd_sfh_hid_desc.h -+++ b/drivers/hid/amd-sfh-hid/hid_descriptor/amd_sfh_hid_desc.h -@@ -111,4 +111,11 @@ struct hpd_input_report { - u8 human_presence; - } __packed; - -+struct tms_feature_report { -+ struct common_feature_property common_property; -+} __packed; -+ -+struct tms_input_report { -+ struct common_input_property common_property; -+} __packed; - #endif -diff --git a/drivers/hid/amd-sfh-hid/hid_descriptor/amd_sfh_hid_report_desc.h b/drivers/hid/amd-sfh-hid/hid_descriptor/amd_sfh_hid_report_desc.h -index 697f2791ea9c..96cbc1e5b9a7 100644 ---- a/drivers/hid/amd-sfh-hid/hid_descriptor/amd_sfh_hid_report_desc.h -+++ b/drivers/hid/amd-sfh-hid/hid_descriptor/amd_sfh_hid_report_desc.h -@@ -644,6 +644,27 @@ static const u8 als_report_descriptor[] = { - 0xC0 /* HID end collection */ - }; - -+ -+/* TABLET MODE SWITCH */ -+__maybe_unused // Used by sfh1.0, but not yet implemented in sfh1.1 -+static const u8 tms_report_descriptor[] = { -+0x06, 0x43, 0xFF, // Usage Page (Vendor Defined 0xFF43) -+0x0A, 0x02, 0x02, // Usage (0x0202) -+0xA1, 0x01, // Collection (Application) -+0x85, 0x11, // Report ID (17) -+0x15, 0x00, // Logical Minimum (0) -+0x25, 0x01, // Logical Maximum (1) -+0x35, 0x00, // Physical Minimum (0) -+0x45, 0x01, // Physical Maximum (1) -+0x65, 0x00, // Unit (None) -+0x55, 0x00, // Unit Exponent (0) -+0x75, 0x01, // Report Size (1) -+0x95, 0x98, // Report Count (-104) -+0x81, 0x03, // Input (Const,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) -+0x91, 0x03, // Output (Const,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) -+0xC1, 0x00, // End Collection -+}; -+ - /* BIOMETRIC PRESENCE*/ - static const u8 hpd_report_descriptor[] = { - 0x05, 0x20, /* Usage page */ diff --git a/patches/asuslinux/v6-0001-platform-x86-asus-wmi-add-support-for-ASUS-screen.patch b/patches/asuslinux/asus-linux.patch similarity index 59% rename from patches/asuslinux/v6-0001-platform-x86-asus-wmi-add-support-for-ASUS-screen.patch rename to patches/asuslinux/asus-linux.patch index 0e4280b..a2bcfcc 100644 --- a/patches/asuslinux/v6-0001-platform-x86-asus-wmi-add-support-for-ASUS-screen.patch +++ b/patches/asuslinux/asus-linux.patch @@ -1,3 +1,115 @@ +From 76556b655f7b50afe5c58006f44221900e5711a9 Mon Sep 17 00:00:00 2001 +From: "Luke D. Jones" +Date: Wed, 23 Aug 2023 11:05:59 +1200 +Subject: [PATCH v2] ALSA: hda: cs35l41: Support ASUS 2023 laptops with missing + DSD + +Support adding the missing DSD properties required for ASUS ROG 2023 +laptops and other ASUS laptops to properly utilise the cs35l41. + +The currently added laptops are: +- ASUS GS650P, i2c +- ASUS GA402X, i2c +- ASUS GU604V, spi +- ASUS GU603V, spi +- ASUS GV601V, spi +- ASUS GZ301V, spi +- ASUS ROG ALLY, i2c +- ASUS G614J, spi +- ASUS G634J, spi +- ASUS G614JI, spi +- ASUS G713P, i2c +- ASUS H7604JV, spi + +The SPI connected amps may be required to use an external DSD patch +to fix or add the "cs-gpios" property. + +Co-developed-by: Jonathan LoBue +Signed-off-by: Jonathan LoBue +Co-developed-by: Luke D. Jones +Signed-off-by: Luke D. Jones +--- + sound/pci/hda/cs35l41_hda_property.c | 57 ++++++++++++++++++++++++++++ + 1 file changed, 57 insertions(+) + +diff --git a/sound/pci/hda/cs35l41_hda_property.c b/sound/pci/hda/cs35l41_hda_property.c +index c83328971728..de0802859849 100644 +--- a/sound/pci/hda/cs35l41_hda_property.c ++++ b/sound/pci/hda/cs35l41_hda_property.c +@@ -76,6 +76,49 @@ static int hp_vision_acpi_fix(struct cs35l41_hda *cs35l41, struct device *physde + hw_cfg->bst_ind = 1000; + hw_cfg->bst_ipk = 4500; + hw_cfg->bst_cap = 24; ++ ++ hw_cfg->valid = true; ++ ++ return 0; ++} ++ ++/* ++ * The CSC3551 is used in almost the entire ROG laptop range in 2023, this is likely to ++ * also include many non ROG labelled laptops. It is also used with either I2C connection or ++ * SPI connection. The SPI connected versions may be missing a chip select GPIO and require ++ * an DSD table patch. ++ */ ++static int asus_rog_2023_no_acpi(struct cs35l41_hda *cs35l41, struct device *physdev, int id, ++ const char *hid) ++{ ++ struct cs35l41_hw_cfg *hw_cfg = &cs35l41->hw_cfg; ++ int reset_gpio = 0; ++ int spkr_gpio = 2; ++ ++ /* check SPI or I2C address to assign the index */ ++ cs35l41->index = (id == 0 || id == 0x40) ? 0 : 1; ++ cs35l41->channel_index = 0; ++ hw_cfg->spk_pos = cs35l41->index; ++ hw_cfg->bst_type = CS35L41_EXT_BOOST; ++ hw_cfg->gpio1.func = CS35l41_VSPK_SWITCH; ++ hw_cfg->gpio1.valid = true; ++ hw_cfg->gpio2.func = CS35L41_INTERRUPT; ++ hw_cfg->gpio2.valid = true; ++ ++ if (strcmp(cs35l41->acpi_subsystem_id, "10431483") == 0) ++ spkr_gpio = 1; ++ cs35l41->speaker_id = cs35l41_get_speaker_id(physdev, 0, 0, spkr_gpio); ++ ++ if (strcmp(cs35l41->acpi_subsystem_id, "10431473") == 0 ++ || strcmp(cs35l41->acpi_subsystem_id, "10431483") == 0 ++ || strcmp(cs35l41->acpi_subsystem_id, "10431493") == 0 ++ || strcmp(cs35l41->acpi_subsystem_id, "10431CAF") == 0 ++ || strcmp(cs35l41->acpi_subsystem_id, "10431CCF") == 0 ++ || strcmp(cs35l41->acpi_subsystem_id, "10431E02") == 0) { ++ reset_gpio = 1; ++ } ++ cs35l41->reset_gpio = gpiod_get_index(physdev, NULL, reset_gpio, GPIOD_OUT_HIGH); ++ + hw_cfg->valid = true; + + return 0; +@@ -92,6 +135,20 @@ static const struct cs35l41_prop_model cs35l41_prop_model_table[] = { + { "CLSA0100", NULL, lenovo_legion_no_acpi }, + { "CLSA0101", NULL, lenovo_legion_no_acpi }, + { "CSC3551", "103C89C6", hp_vision_acpi_fix }, ++ { "CSC3551", "10431433", asus_rog_2023_no_acpi }, // GS650P i2c ++ { "CSC3551", "10431463", asus_rog_2023_no_acpi }, // GA402X/N i2c, rst=0 ++ { "CSC3551", "10431473", asus_rog_2023_no_acpi }, // GU604V spi, rst=1 ++ { "CSC3551", "10431483", asus_rog_2023_no_acpi }, // GU603V spi, rst=1, spkr=1 ++ { "CSC3551", "10431493", asus_rog_2023_no_acpi }, // GV601V spi, rst=1 ++ { "CSC3551", "10431573", asus_rog_2023_no_acpi }, // GZ301V spi, rst=0 ++ { "CSC3551", "104317F3", asus_rog_2023_no_acpi }, // ROG ALLY i2c, rst=0 ++ { "CSC3551", "10431B93", asus_rog_2023_no_acpi }, // G614J spi, rst=0 ++ { "CSC3551", "10431C9F", asus_rog_2023_no_acpi }, // G614JI spi, rst=0 ++ { "CSC3551", "10431CAF", asus_rog_2023_no_acpi }, // G634J spi, rst=1 ++ { "CSC3551", "10431CCF", asus_rog_2023_no_acpi }, // G814J spi, rst=1 ++ { "CSC3551", "10431D1F", asus_rog_2023_no_acpi }, // G713P i2c, rst=0 ++ { "CSC3551", "10431E02", asus_rog_2023_no_acpi }, // UX3042Z spi, rst=1 ++ { "CSC3551", "10431F1F", asus_rog_2023_no_acpi }, // H7604JV spi, rst=0 + {} + }; + +-- +2.41.0 + From b35a4c957b3f0e5b4c7c73dec4fe3a5b9dbc4873 Mon Sep 17 00:00:00 2001 From: "Luke D. Jones" Date: Sun, 30 Apr 2023 10:56:34 +1200 @@ -45,26 +157,26 @@ index f54178d6f780..0b13be703856 100644 @@ -127,6 +128,10 @@ module_param(fnlock_default, bool, 0444); #define NVIDIA_TEMP_MIN 75 #define NVIDIA_TEMP_MAX 87 - + +#define ASUS_SCREENPAD_BRIGHT_MIN 20 +#define ASUS_SCREENPAD_BRIGHT_MAX 255 +#define ASUS_SCREENPAD_BRIGHT_DEFAULT 60 + static const char * const ashs_ids[] = { "ATK4001", "ATK4002", NULL }; - + static int throttle_thermal_policy_write(struct asus_wmi *); @@ -212,6 +217,7 @@ struct asus_wmi { - + struct input_dev *inputdev; struct backlight_device *backlight_device; + struct backlight_device *screenpad_backlight_device; struct platform_device *platform_device; - + struct led_classdev wlan_led; @@ -3776,6 +3782,124 @@ static int is_display_toggle(int code) return 0; } - + +/* Screenpad backlight *******************************************************/ + +static int read_screenpad_backlight_power(struct asus_wmi *asus) @@ -184,12 +296,12 @@ index f54178d6f780..0b13be703856 100644 +} + /* Fn-lock ********************************************************************/ - + static bool asus_wmi_has_fnlock_key(struct asus_wmi *asus) @@ -4431,6 +4555,12 @@ static int asus_wmi_add(struct platform_device *pdev) } else if (asus->driver->quirks->wmi_backlight_set_devstate) err = asus_wmi_set_devstate(ASUS_WMI_DEVID_BACKLIGHT, 2, NULL); - + + if (asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_SCREENPAD_LIGHT)) { + err = asus_screenpad_init(asus); + if (err && err != -ENODEV) @@ -226,7 +338,7 @@ index a478ebfd34df..5fbdd0eafa02 100644 int panel_power; + int screenpad_brightness; int wlan_ctrl_by_user; - + const char *name; diff --git a/include/linux/platform_data/x86/asus-wmi.h b/include/linux/platform_data/x86/asus-wmi.h index 16e99a1c37fc..63e630276499 100644 @@ -242,7 +354,37 @@ index 16e99a1c37fc..63e630276499 100644 +#define ASUS_WMI_DEVID_SCREENPAD_LIGHT 0x00050032 #define ASUS_WMI_DEVID_FAN_BOOST_MODE 0x00110018 #define ASUS_WMI_DEVID_THROTTLE_THERMAL_POLICY 0x00120075 - --- + +-- 2.41.0 +From 7760e10674dbb9127450629308c6ee1c35d5fc19 Mon Sep 17 00:00:00 2001 +From: "Luke D. Jones" +Date: Thu, 9 Nov 2023 09:41:13 +1300 +Subject: [PATCH] ALSA: hda/realtek: Add quirk for ASUS ROG G814Jx + +Adds the required quirk to enable the Cirrus amp and correct pins +on the ASUS ROG G814J series which uses an SPI connected Cirrus amp. + +While this works if the related _DSD properties are made available, these +aren't included in the ACPI of these laptops (yet). + +Signed-off-by: Luke D. Jones +--- + sound/pci/hda/patch_realtek.c | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/sound/pci/hda/patch_realtek.c b/sound/pci/hda/patch_realtek.c +index 58006c8bcfb9..a690baa202c5 100644 +--- a/sound/pci/hda/patch_realtek.c ++++ b/sound/pci/hda/patch_realtek.c +@@ -9924,6 +9924,7 @@ static const struct snd_pci_quirk alc269_fixup_tbl[] = { + SND_PCI_QUIRK(0x1043, 0x1c9f, "ASUS G614JI", ALC285_FIXUP_ASUS_HEADSET_MIC), + SND_PCI_QUIRK(0x1043, 0x1caf, "ASUS G634JYR/JZR", ALC285_FIXUP_ASUS_SPI_REAR_SPEAKERS), + SND_PCI_QUIRK(0x1043, 0x1ccd, "ASUS X555UB", ALC256_FIXUP_ASUS_MIC), ++ SND_PCI_QUIRK(0x1043, 0x1ccf, "ASUS G814JI", ALC285_FIXUP_ASUS_SPI_REAR_SPEAKERS), + SND_PCI_QUIRK(0x1043, 0x1d1f, "ASUS ROG Strix G17 2023 (G713PV)", ALC287_FIXUP_CS35L41_I2C_2), + SND_PCI_QUIRK(0x1043, 0x1d42, "ASUS Zephyrus G14 2022", ALC289_FIXUP_ASUS_GA401), + SND_PCI_QUIRK(0x1043, 0x1d4e, "ASUS TM420", ALC256_FIXUP_ASUS_HPE), +-- +2.41.0 diff --git a/patches/asuslinux/rog-ally-audio-fix.patch b/patches/asuslinux/rog-ally-audio-fix.patch new file mode 100644 index 0000000..9626c61 --- /dev/null +++ b/patches/asuslinux/rog-ally-audio-fix.patch @@ -0,0 +1,64 @@ +diff --git a/sound/pci/hda/cd35l41_hda_property.c b/sound/pci/hda/cs35l41_hda_property.c +index 6b704fd..d63617f 100644 +--- a/sound/pci/hda/cs35l41_hda_property.c ++++ b/sound/pci/hda/cs35l41_hda_property.c +@@ -6,7 +6,9 @@ + // + // Author: Stefan Binding + ++#include + #include ++#include + #include + #include "cs35l41_hda_property.h" + +@@ -117,6 +119,40 @@ static int asus_rog_2023_no_acpi(struct cs35l41_hda *cs35l41, struct device *phy + return 0; + } + ++static int asus_rog_2023_ally_fix(struct cs35l41_hda *cs35l41, struct device *physdev, int id, ++ const char *hid) ++{ ++ const char *rog_ally_bios_ver = dmi_get_system_info(DMI_BIOS_VERSION); ++ const char *rog_ally_bios_num = rog_ally_bios_ver + 6; // Dropping the RC71L. part before the number ++ int rog_ally_bios_int; ++ kstrtoint(rog_ally_bios_num, 10, &rog_ally_bios_int); ++ if(rog_ally_bios_int >= 330){ ++ printk(KERN_INFO "DSD properties exist in the %d BIOS. Not applying DSD override...\n", rog_ally_bios_int); ++ return -ENOENT; //Patch not applicable. Exiting... ++ } ++ ++ struct cs35l41_hw_cfg *hw_cfg = &cs35l41->hw_cfg; ++ ++ dev_info(cs35l41->dev, "Adding DSD properties for %s\n", cs35l41->acpi_subsystem_id); ++ ++ cs35l41->index = id == 0x40 ? 0 : 1; ++ cs35l41->channel_index = 0; ++ cs35l41->reset_gpio = gpiod_get_index(physdev, NULL, 0, GPIOD_OUT_HIGH); ++ cs35l41->speaker_id = cs35l41_get_speaker_id(physdev, 0, 0, 2); ++ hw_cfg->spk_pos = cs35l41->index; ++ hw_cfg->gpio1.func = CS35L41_NOT_USED; ++ hw_cfg->gpio1.valid = true; ++ hw_cfg->gpio2.func = CS35L41_INTERRUPT; ++ hw_cfg->gpio2.valid = true; ++ hw_cfg->bst_type = CS35L41_INT_BOOST; ++ hw_cfg->bst_ind = 1000; /* 1,000nH Inductance value */ ++ hw_cfg->bst_ipk = 4500; /* 4,500mA peak current */ ++ hw_cfg->bst_cap = 24; /* 24 microFarad cap value */ ++ hw_cfg->valid = true; ++ ++ return 0; ++} ++ + struct cs35l41_prop_model { + const char *hid; + const char *ssid; +@@ -134,7 +170,7 @@ static const struct cs35l41_prop_model cs35l41_prop_model_table[] = { + { "CSC3551", "10431483", asus_rog_2023_no_acpi }, // GU603V spi, rst=1, spkr=1 + { "CSC3551", "10431493", asus_rog_2023_no_acpi }, // GV601V spi, rst=1 + { "CSC3551", "10431573", asus_rog_2023_no_acpi }, // GZ301V spi, rst=0 +- { "CSC3551", "104317F3", asus_rog_2023_no_acpi }, // ROG ALLY i2c, rst=0 ++ { "CSC3551", "104317F3", asus_rog_2023_ally_fix }, // ASUS ROG ALLY - i2c, rst=0 + { "CSC3551", "10431B93", asus_rog_2023_no_acpi }, // G614J spi, rst=0 + { "CSC3551", "10431C9F", asus_rog_2023_no_acpi }, // G614JI spi, rst=0 + { "CSC3551", "10431CAF", asus_rog_2023_no_acpi }, // G634J spi, rst=1 diff --git a/patches/nobara/rog-ally-bmc150.patch b/patches/asuslinux/rog-ally-bmc150.patch similarity index 100% rename from patches/nobara/rog-ally-bmc150.patch rename to patches/asuslinux/rog-ally-bmc150.patch diff --git a/patches/asuslinux/v2-0001-platform-x86-asus-wmi-disable-USB0-hub-on-ROG-All.patch b/patches/asuslinux/v2-0001-platform-x86-asus-wmi-disable-USB0-hub-on-ROG-All.patch new file mode 100644 index 0000000..3c89787 --- /dev/null +++ b/patches/asuslinux/v2-0001-platform-x86-asus-wmi-disable-USB0-hub-on-ROG-All.patch @@ -0,0 +1,113 @@ +From 0a399a37fbe6f6b2b9062e1c076df28608b628c9 Mon Sep 17 00:00:00 2001 +From: "Luke D. Jones" +Date: Fri, 24 Nov 2023 21:13:11 +1300 +Subject: [PATCH v2] platform/x86: asus-wmi: disable USB0 hub on ROG Ally + before suspend + +ASUS have worked around an issue in XInput where it doesn't support USB +selective suspend, whcih causes suspend issues in Windows. They worked +around this by adjusting the MCU firmware to disable the USB0 hub when +the screen is switched off during the Microsoft DSM suspend path in ACPI. + +The issue we have with this however is one of timing - the call the tells +the MCU to this isn't able to complete before suspend is done so we call +this in a prepare() and add a small msleep() to ensure it is done. This +must be done before the screen is switched off to prevent a variety of +possible races. + +Without this the MCU is unable to initialise itself correctly on resume. + +Signed-off-by: Luke D. Jones +--- + drivers/platform/x86/asus-wmi.c | 40 +++++++++++++++++++++++++++++++++ + 1 file changed, 40 insertions(+) + +diff --git a/drivers/platform/x86/asus-wmi.c b/drivers/platform/x86/asus-wmi.c +index 6a79f16233ab..563c9ab31bc7 100644 +--- a/drivers/platform/x86/asus-wmi.c ++++ b/drivers/platform/x86/asus-wmi.c +@@ -16,6 +16,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -132,6 +133,9 @@ module_param(fnlock_default, bool, 0444); + #define ASUS_SCREENPAD_BRIGHT_MAX 255 + #define ASUS_SCREENPAD_BRIGHT_DEFAULT 60 + ++/* Controls the power state of the USB0 hub on ROG Ally which input is on */ ++#define ASUS_USB0_PWR_EC0_CSEE "\\_SB.PCI0.SBRG.EC0.CSEE" ++ + static const char * const ashs_ids[] = { "ATK4001", "ATK4002", NULL }; + + static int throttle_thermal_policy_write(struct asus_wmi *); +@@ -300,6 +304,9 @@ struct asus_wmi { + + bool fnlock_locked; + ++ /* The ROG Ally device requires the USB hub to be disabled before suspend */ ++ bool pre_suspend_ec0_csee_disable; ++ + struct asus_wmi_debug debug; + + struct asus_wmi_driver *driver; +@@ -4488,6 +4495,8 @@ static int asus_wmi_add(struct platform_device *pdev) + asus->nv_temp_tgt_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_NV_THERM_TARGET); + asus->panel_overdrive_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_PANEL_OD); + asus->mini_led_mode_available = asus_wmi_dev_is_present(asus, ASUS_WMI_DEVID_MINI_LED_MODE); ++ asus->pre_suspend_ec0_csee_disable = acpi_has_method(NULL, ASUS_USB0_PWR_EC0_CSEE) ++ && dmi_match(DMI_BOARD_NAME, "RC71L"); + + err = fan_boost_mode_check_present(asus); + if (err) +@@ -4654,6 +4663,35 @@ static int asus_hotk_resume(struct device *device) + asus_wmi_fnlock_update(asus); + + asus_wmi_tablet_mode_get_state(asus); ++ ++ return 0; ++} ++ ++static int asus_hotk_resume_early(struct device *device) ++{ ++ struct asus_wmi *asus = dev_get_drvdata(device); ++ ++ if (asus->pre_suspend_ec0_csee_disable) { ++ /* sleep required to ensure USB0 is enabled before drivers notice */ ++ if (ACPI_FAILURE(acpi_execute_simple_method(NULL, ASUS_USB0_PWR_EC0_CSEE, 0xB8))) ++ pr_warn("ASUS ROG Ally failed to set USB hub power on\n"); ++ } ++ ++ return 0; ++} ++ ++static int asus_hotk_prepare(struct device *device) ++{ ++ struct asus_wmi *asus = dev_get_drvdata(device); ++ ++ if (asus->pre_suspend_ec0_csee_disable) { ++ /* sleep required to ensure USB0 is disabled before sleep continues */ ++ if (ACPI_FAILURE(acpi_execute_simple_method(NULL, ASUS_USB0_PWR_EC0_CSEE, 0xB7))) ++ pr_warn("ASUS ROG Ally failed to set USB hub power off\n"); ++ else ++ msleep(100); ++ } ++ + return 0; + } + +@@ -4701,6 +4739,8 @@ static const struct dev_pm_ops asus_pm_ops = { + .thaw = asus_hotk_thaw, + .restore = asus_hotk_restore, + .resume = asus_hotk_resume, ++ .resume_early = asus_hotk_resume_early, ++ .prepare = asus_hotk_prepare, + }; + + /* Registration ***************************************************************/ +-- +2.43.0 + diff --git a/patches/cachyos/0001-bore-cachy.patch b/patches/cachyos/0001-bore-cachy.patch index 1b88ad1..d92f986 100644 --- a/patches/cachyos/0001-bore-cachy.patch +++ b/patches/cachyos/0001-bore-cachy.patch @@ -1,77 +1,41 @@ -From 7ec1504c16d02fa3f543be2b3bdd6346f353dde0 Mon Sep 17 00:00:00 2001 -From: Peter Jung -Date: Sat, 28 Oct 2023 20:49:14 +0200 +From b6a7058a13f345d5aa5426466f9104da43d47ce4 Mon Sep 17 00:00:00 2001 +From: Piotr Gorski +Date: Tue, 5 Dec 2023 15:49:10 +0100 Subject: [PATCH] bore-cachy -Signed-off-by: Peter Jung +Signed-off-by: Piotr Gorski --- - include/linux/sched.h | 31 +++++++ - init/Kconfig | 19 ++++ - kernel/sched/autogroup.c | 4 + - kernel/sched/core.c | 163 +++++++++++++++++++++++++++++++++ - kernel/sched/debug.c | 3 + - kernel/sched/fair.c | 189 +++++++++++++++++++++++++++++++++++++-- - kernel/sched/features.h | 4 + - 7 files changed, 406 insertions(+), 7 deletions(-) + include/linux/sched.h | 10 ++ + init/Kconfig | 19 ++++ + kernel/sched/core.c | 128 ++++++++++++++++++++++++ + kernel/sched/debug.c | 3 + + kernel/sched/fair.c | 216 +++++++++++++++++++++++++++++++++++++--- + kernel/sched/features.h | 4 + + 6 files changed, 366 insertions(+), 14 deletions(-) diff --git a/include/linux/sched.h b/include/linux/sched.h -index 016610587645..14d0f15160c8 100644 +index 77f01ac38..01f2839ad 100644 --- a/include/linux/sched.h +++ b/include/linux/sched.h -@@ -545,6 +545,24 @@ struct sched_statistics { - #endif /* CONFIG_SCHEDSTATS */ - } ____cacheline_aligned; - -+#ifdef CONFIG_SCHED_BORE -+typedef union { -+ u16 u16; -+ s16 s16; -+ u8 u8[2]; -+ s8 s8[2]; -+} x16; -+ -+typedef union { -+ u32 u32; -+ s32 s32; -+ u16 u16[2]; -+ s16 s16[2]; -+ u8 u8[4]; -+ s8 s8[4]; -+} x32; -+#endif // CONFIG_SCHED_BORE -+ - struct sched_entity { - /* For load-balancing: */ - struct load_weight load; -@@ -559,6 +577,12 @@ struct sched_entity { +@@ -559,6 +559,16 @@ struct sched_entity { u64 sum_exec_runtime; u64 prev_sum_exec_runtime; u64 vruntime; +#ifdef CONFIG_SCHED_BORE + u64 burst_time; -+ u16 prev_burst_penalty; -+ u16 curr_burst_penalty; -+ u16 burst_penalty; ++ u8 prev_burst_penalty; ++ u8 curr_burst_penalty; ++ u8 burst_penalty; ++ u8 slice_score; ++ u8 child_burst; ++ u16 child_burst_cnt; ++ u64 child_burst_last_cached; +#endif // CONFIG_SCHED_BORE s64 vlag; u64 slice; -@@ -990,6 +1014,13 @@ struct task_struct { - struct list_head children; - struct list_head sibling; - struct task_struct *group_leader; -+#ifdef CONFIG_SCHED_BORE -+ u16 child_burst_cache; -+ u16 child_burst_count_cache; -+ u64 child_burst_last_cached; -+ u16 group_burst_cache; -+ u64 group_burst_last_cached; -+#endif // CONFIG_SCHED_BORE - - /* - * 'ptraced' is the list of tasks this task is using ptrace() on. diff --git a/init/Kconfig b/init/Kconfig -index 9dee4c100348..6e5c69185d62 100644 +index 9dee4c100..49d343e97 100644 --- a/init/Kconfig +++ b/init/Kconfig @@ -1278,6 +1278,25 @@ config CHECKPOINT_RESTORE @@ -95,60 +59,38 @@ index 9dee4c100348..6e5c69185d62 100644 + + You can turn it off by setting the sysctl kernel.sched_bore = 0. + -+ If unsure say Y here. ++ If unsure, say Y here. + config SCHED_AUTOGROUP bool "Automatic process group scheduling" select CGROUPS -diff --git a/kernel/sched/autogroup.c b/kernel/sched/autogroup.c -index 991fc9002535..fdeb3401730c 100644 ---- a/kernel/sched/autogroup.c -+++ b/kernel/sched/autogroup.c -@@ -4,7 +4,11 @@ - * Auto-group scheduling implementation: - */ - -+#ifdef CONFIG_SCHED_BORE -+unsigned int __read_mostly sysctl_sched_autogroup_enabled = 0; -+#else // CONFIG_SCHED_BORE - unsigned int __read_mostly sysctl_sched_autogroup_enabled = 1; -+#endif // CONFIG_SCHED_BORE - static struct autogroup autogroup_default; - static atomic_t autogroup_seq_nr; - diff --git a/kernel/sched/core.c b/kernel/sched/core.c -index 6d72bb67e84f..b08003611d28 100644 +index a854b7183..a98cfa7ab 100644 --- a/kernel/sched/core.c +++ b/kernel/sched/core.c -@@ -4490,6 +4490,158 @@ int wake_up_state(struct task_struct *p, unsigned int state) +@@ -4488,6 +4488,123 @@ int wake_up_state(struct task_struct *p, unsigned int state) return try_to_wake_up(p, state, 0); } +#ifdef CONFIG_SCHED_BORE -+extern unsigned int sched_burst_cache_lifetime; -+extern unsigned int sched_bore; -+extern unsigned int sched_burst_fork_atavistic; ++extern bool sched_bore; ++extern u8 sched_burst_fork_atavistic; ++extern uint sched_burst_cache_lifetime; + +void __init sched_init_bore(void) { -+ init_task.child_burst_cache = 0; -+ init_task.child_burst_count_cache = 0; -+ init_task.child_burst_last_cached = 0; -+ init_task.group_burst_cache = 0; -+ init_task.group_burst_last_cached = 0; + init_task.se.burst_time = 0; + init_task.se.prev_burst_penalty = 0; + init_task.se.curr_burst_penalty = 0; + init_task.se.burst_penalty = 0; ++ init_task.se.slice_score = 0; ++ init_task.se.child_burst_last_cached = 0; +} + +void inline sched_fork_bore(struct task_struct *p) { -+ p->child_burst_cache = 0; -+ p->child_burst_count_cache = 0; -+ p->child_burst_last_cached = 0; -+ p->group_burst_cache = 0; -+ p->group_burst_last_cached = 0; + p->se.burst_time = 0; + p->se.curr_burst_penalty = 0; ++ p->se.slice_score = 0; ++ p->se.child_burst_last_cached = 0; +} + +static u32 count_child_tasks(struct task_struct *p) { @@ -159,20 +101,16 @@ index 6d72bb67e84f..b08003611d28 100644 +} + +static inline bool child_burst_cache_expired(struct task_struct *p, u64 now) { -+ return (p->child_burst_last_cached + sched_burst_cache_lifetime < now); -+} -+ -+static inline bool group_burst_cache_expired(struct task_struct *p, u64 now) { -+ return (p->group_burst_last_cached + sched_burst_cache_lifetime < now); ++ return (p->se.child_burst_last_cached + sched_burst_cache_lifetime < now); +} + +static void __update_child_burst_cache( + struct task_struct *p, u32 cnt, u32 sum, u64 now) { -+ u16 avg = 0; ++ u8 avg = 0; + if (cnt) avg = sum / cnt; -+ p->child_burst_cache = max(avg, p->se.burst_penalty); -+ p->child_burst_count_cache = cnt; -+ p->child_burst_last_cached = now; ++ p->se.child_burst = max(avg, p->se.burst_penalty); ++ p->se.child_burst_cnt = cnt; ++ p->se.child_burst_last_cached = now; +} + +static void update_child_burst_cache(struct task_struct *p, u64 now) { @@ -181,6 +119,7 @@ index 6d72bb67e84f..b08003611d28 100644 + u32 sum = 0; + + list_for_each_entry(child, &p->children, sibling) { ++ if (child->sched_class != &fair_sched_class) continue; + cnt++; + sum += child->se.burst_penalty; + } @@ -200,16 +139,17 @@ index 6d72bb67e84f..b08003611d28 100644 + dec = list_first_entry(&dec->children, struct task_struct, sibling); + + if (!dcnt || !depth) { ++ if (dec->sched_class != &fair_sched_class) continue; + cnt++; + sum += dec->se.burst_penalty; -+ } else { -+ if (child_burst_cache_expired(dec, now)) -+ update_child_burst_cache_atavistic(dec, now, depth - 1, &cnt, &sum); -+ else { -+ cnt += dec->child_burst_count_cache; -+ sum += (u32)dec->child_burst_cache * dec->child_burst_count_cache; -+ } ++ continue; + } ++ if (!child_burst_cache_expired(dec, now)) { ++ cnt += dec->se.child_burst_cnt; ++ sum += (u32)dec->se.child_burst * dec->se.child_burst_cnt; ++ continue; ++ } ++ update_child_burst_cache_atavistic(dec, now, depth - 1, &cnt, &sum); + } + + __update_child_burst_cache(p, cnt, sum, now); @@ -217,57 +157,30 @@ index 6d72bb67e84f..b08003611d28 100644 + *asum += sum; +} + -+static void update_group_burst_cache(struct task_struct *p, u64 now) { -+ struct task_struct *member; -+ u32 cnt = 0, sum = 0; -+ u16 avg = 0; -+ -+ for_each_thread(p, member) { -+ cnt++; -+ sum += member->se.burst_penalty; -+ } -+ -+ if (cnt) avg = sum / cnt; -+ p->group_burst_cache = max(avg, p->se.burst_penalty); -+ p->group_burst_last_cached = now; -+} -+ -+#define forked_task_is_process(p) (p->pid == p->tgid) -+#define bore_thread_fork_group_inherit (sched_burst_fork_atavistic & 4) -+ -+static void fork_burst_penalty(struct task_struct *p) { ++static void sched_post_fork_bore(struct task_struct *p) { + struct sched_entity *se = &p->se; + struct task_struct *anc; -+ u64 now = ktime_get_ns(); ++ u64 now; + u32 cnt = 0, sum = 0, depth; -+ u16 burst_cache; ++ u8 burst_cache; + + if (likely(sched_bore)) { ++ now = ktime_get_ns(); + read_lock(&tasklist_lock); + -+ if (forked_task_is_process(p) || -+ likely(!bore_thread_fork_group_inherit)) { -+ anc = p->real_parent; -+ depth = sched_burst_fork_atavistic & 3; -+ if (likely(depth)) { -+ while ((anc->real_parent != anc) && -+ (count_child_tasks(anc) == 1)) -+ anc = anc->real_parent; -+ if (child_burst_cache_expired(anc, now)) -+ update_child_burst_cache_atavistic( -+ anc, now, depth - 1, &cnt, &sum); -+ } else -+ if (child_burst_cache_expired(anc, now)) -+ update_child_burst_cache(anc, now); ++ anc = p->real_parent; ++ depth = sched_burst_fork_atavistic; ++ if (likely(depth)) { ++ while ((anc->real_parent != anc) && (count_child_tasks(anc) == 1)) ++ anc = anc->real_parent; ++ if (child_burst_cache_expired(anc, now)) ++ update_child_burst_cache_atavistic( ++ anc, now, depth - 1, &cnt, &sum); ++ } else ++ if (child_burst_cache_expired(anc, now)) ++ update_child_burst_cache(anc, now); + -+ burst_cache = anc->child_burst_cache; -+ } else { -+ anc = p->group_leader; -+ if (group_burst_cache_expired(anc, now)) -+ update_group_burst_cache(anc, now); -+ -+ burst_cache = anc->group_burst_cache; -+ } ++ burst_cache = anc->se.child_burst; + + read_unlock(&tasklist_lock); + se->prev_burst_penalty = max(se->prev_burst_penalty, burst_cache); @@ -279,7 +192,7 @@ index 6d72bb67e84f..b08003611d28 100644 /* * Perform scheduler related setup for a newly forked process p. * p is forked by current. -@@ -4506,6 +4658,9 @@ static void __sched_fork(unsigned long clone_flags, struct task_struct *p) +@@ -4504,6 +4621,9 @@ static void __sched_fork(unsigned long clone_flags, struct task_struct *p) p->se.prev_sum_exec_runtime = 0; p->se.nr_migrations = 0; p->se.vruntime = 0; @@ -287,32 +200,32 @@ index 6d72bb67e84f..b08003611d28 100644 + sched_fork_bore(p); +#endif // CONFIG_SCHED_BORE p->se.vlag = 0; + p->se.slice = sysctl_sched_base_slice; INIT_LIST_HEAD(&p->se.group_node); - -@@ -4827,6 +4982,9 @@ void sched_cgroup_fork(struct task_struct *p, struct kernel_clone_args *kargs) +@@ -4823,6 +4943,9 @@ void sched_cgroup_fork(struct task_struct *p, struct kernel_clone_args *kargs) void sched_post_fork(struct task_struct *p) { +#ifdef CONFIG_SCHED_BORE -+ fork_burst_penalty(p); ++ sched_post_fork_bore(p); +#endif // CONFIG_SCHED_BORE uclamp_post_fork(p); } -@@ -9950,6 +10108,11 @@ void __init sched_init(void) +@@ -9922,6 +10045,11 @@ void __init sched_init(void) BUG_ON(&dl_sched_class != &stop_sched_class + 1); #endif +#ifdef CONFIG_SCHED_BORE + sched_init_bore(); -+ printk(KERN_INFO "BORE (Burst-Oriented Response Enhancer) CPU Scheduler modification 3.2.9 by Masahito Suzuki"); ++ printk(KERN_INFO "BORE (Burst-Oriented Response Enhancer) CPU Scheduler modification 3.5.7 by Masahito Suzuki"); +#endif // CONFIG_SCHED_BORE + wait_bit_init(); #ifdef CONFIG_FAIR_GROUP_SCHED diff --git a/kernel/sched/debug.c b/kernel/sched/debug.c -index 5c743bcb340d..d427b1e6b415 100644 +index 4c3d0d9f3..e37fdfad1 100644 --- a/kernel/sched/debug.c +++ b/kernel/sched/debug.c @@ -595,6 +595,9 @@ print_task(struct seq_file *m, struct rq *rq, struct task_struct *p) @@ -320,13 +233,13 @@ index 5c743bcb340d..d427b1e6b415 100644 SPLIT_NS(schedstat_val_or_zero(p->stats.sum_block_runtime))); +#ifdef CONFIG_SCHED_BORE -+ SEQ_printf(m, " %2d", ((x16*)&p->se.burst_penalty)->u8[1]); ++ SEQ_printf(m, " %2d", p->se.slice_score); +#endif #ifdef CONFIG_NUMA_BALANCING SEQ_printf(m, " %d %d", task_node(p), task_numa_group_id(p)); #endif diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c -index fd2fe1131d40..7a0e3a92c999 100644 +index fa9fff0f9..5e4f0ccff 100644 --- a/kernel/sched/fair.c +++ b/kernel/sched/fair.c @@ -19,6 +19,9 @@ @@ -370,32 +283,29 @@ index fd2fe1131d40..7a0e3a92c999 100644 /* * After fork, child runs first. If set to 0 (default) then -@@ -86,6 +100,66 @@ unsigned int sysctl_sched_child_runs_first __read_mostly; +@@ -86,6 +100,68 @@ unsigned int sysctl_sched_child_runs_first __read_mostly; const_debug unsigned int sysctl_sched_migration_cost = 500000UL; +#ifdef CONFIG_SCHED_BORE -+unsigned int __read_mostly sched_bore = 1; -+unsigned int __read_mostly sched_burst_cache_lifetime = 60000000; -+unsigned int __read_mostly sched_burst_penalty_offset = 22; -+unsigned int __read_mostly sched_burst_penalty_scale = 1280; -+unsigned int __read_mostly sched_burst_smoothness_up = 1; -+unsigned int __read_mostly sched_burst_smoothness_down = 0; -+unsigned int __read_mostly sched_burst_fork_atavistic = 2; -+static int three = 3; -+static int seven = 7; -+static int sixty_four = 64; -+static int maxval_12_bits = 4095; ++bool __read_mostly sched_bore = 1; ++bool __read_mostly sched_burst_score_rounding = 0; ++bool __read_mostly sched_burst_smoothness_long = 1; ++bool __read_mostly sched_burst_smoothness_short = 0; ++u8 __read_mostly sched_burst_fork_atavistic = 2; ++u8 __read_mostly sched_burst_penalty_offset = 22; ++uint __read_mostly sched_burst_penalty_scale = 1280; ++uint __read_mostly sched_burst_cache_lifetime = 60000000; ++static u8 sixty_four = 64; ++static uint maxval_12_bits = 4095; + -+#define MAX_BURST_PENALTY ((40U << 8) - 1) ++#define MAX_BURST_PENALTY (39U <<2) + +static inline u32 log2plus1_u64_u32f8(u64 v) { -+ x32 result; -+ int msb = fls64(v); -+ int excess_bits = msb - 9; -+ result.u8[0] = (0 <= excess_bits)? v >> excess_bits: v << -excess_bits; -+ result.u8[1] = msb; -+ return result.u32; ++ u32 msb = fls64(v); ++ s32 excess_bits = msb - 9; ++ u8 fractional = (0 <= excess_bits)? v >> excess_bits: v << -excess_bits; ++ return msb << 8 | fractional; +} + +static inline u32 calc_burst_penalty(u64 burst_time) { @@ -404,26 +314,31 @@ index fd2fe1131d40..7a0e3a92c999 100644 + greed = log2plus1_u64_u32f8(burst_time); + tolerance = sched_burst_penalty_offset << 8; + penalty = max(0, (s32)greed - (s32)tolerance); -+ scaled_penalty = penalty * sched_burst_penalty_scale >> 10; ++ scaled_penalty = penalty * sched_burst_penalty_scale >> 16; + + return min(MAX_BURST_PENALTY, scaled_penalty); +} + -+static void update_burst_penalty(struct sched_entity *se) { ++static inline void update_burst_penalty(struct sched_entity *se) { + se->curr_burst_penalty = calc_burst_penalty(se->burst_time); + se->burst_penalty = max(se->prev_burst_penalty, se->curr_burst_penalty); +} + -+static inline u64 penalty_scale(u64 delta, struct sched_entity *se) { -+ u32 score = ((x16*)&se->burst_penalty)->u8[1]; -+ return mul_u64_u32_shr(delta, sched_prio_to_wmult[score], 22); ++static inline void update_slice_score(struct sched_entity *se) { ++ u32 penalty = se->burst_penalty; ++ if (sched_burst_score_rounding) penalty += 0x2U; ++ se->slice_score = penalty >> 2; ++} ++ ++static inline u64 scale_slice(u64 delta, struct sched_entity *se) { ++ return mul_u64_u32_shr(delta, sched_prio_to_wmult[se->slice_score], 22); +} + +static inline u32 binary_smooth(u32 new, u32 old) { + int increment = new - old; + return (0 <= increment)? -+ old + ( increment >> sched_burst_smoothness_up): -+ old - (-increment >> sched_burst_smoothness_down); ++ old + ( increment >> (int)sched_burst_smoothness_long): ++ old - (-increment >> (int)sched_burst_smoothness_short); +} + +static void restart_burst(struct sched_entity *se) { @@ -437,7 +352,7 @@ index fd2fe1131d40..7a0e3a92c999 100644 int sched_thermal_decay_shift; static int __init setup_sched_thermal_decay_shift(char *str) { -@@ -145,6 +219,69 @@ static unsigned int sysctl_numa_balancing_promote_rate_limit = 65536; +@@ -145,6 +221,70 @@ static unsigned int sysctl_numa_balancing_promote_rate_limit = 65536; #ifdef CONFIG_SYSCTL static struct ctl_table sched_fair_sysctls[] = { @@ -445,79 +360,80 @@ index fd2fe1131d40..7a0e3a92c999 100644 + { + .procname = "sched_bore", + .data = &sched_bore, -+ .maxlen = sizeof(unsigned int), ++ .maxlen = sizeof(bool), + .mode = 0644, -+ .proc_handler = &proc_dointvec_minmax, -+ .extra1 = SYSCTL_ZERO, -+ .extra2 = SYSCTL_ONE, ++ .proc_handler = &proc_dobool, + }, + { + .procname = "sched_burst_cache_lifetime", + .data = &sched_burst_cache_lifetime, -+ .maxlen = sizeof(unsigned int), ++ .maxlen = sizeof(uint), + .mode = 0644, -+ .proc_handler = proc_dointvec, ++ .proc_handler = proc_douintvec, + }, + { + .procname = "sched_burst_fork_atavistic", + .data = &sched_burst_fork_atavistic, -+ .maxlen = sizeof(unsigned int), ++ .maxlen = sizeof(u8), + .mode = 0644, -+ .proc_handler = &proc_dointvec_minmax, ++ .proc_handler = &proc_dou8vec_minmax, + .extra1 = SYSCTL_ZERO, -+ .extra2 = &seven, ++ .extra2 = SYSCTL_THREE, + }, + { + .procname = "sched_burst_penalty_offset", + .data = &sched_burst_penalty_offset, -+ .maxlen = sizeof(unsigned int), ++ .maxlen = sizeof(u8), + .mode = 0644, -+ .proc_handler = &proc_dointvec_minmax, ++ .proc_handler = &proc_dou8vec_minmax, + .extra1 = SYSCTL_ZERO, + .extra2 = &sixty_four, + }, + { + .procname = "sched_burst_penalty_scale", + .data = &sched_burst_penalty_scale, -+ .maxlen = sizeof(unsigned int), ++ .maxlen = sizeof(uint), + .mode = 0644, -+ .proc_handler = &proc_dointvec_minmax, ++ .proc_handler = &proc_douintvec_minmax, + .extra1 = SYSCTL_ZERO, + .extra2 = &maxval_12_bits, + }, + { -+ .procname = "sched_burst_smoothness_down", -+ .data = &sched_burst_smoothness_down, -+ .maxlen = sizeof(unsigned int), ++ .procname = "sched_burst_score_rounding", ++ .data = &sched_burst_score_rounding, ++ .maxlen = sizeof(bool), + .mode = 0644, -+ .proc_handler = &proc_dointvec_minmax, -+ .extra1 = SYSCTL_ZERO, -+ .extra2 = &three, ++ .proc_handler = &proc_dobool, + }, + { -+ .procname = "sched_burst_smoothness_up", -+ .data = &sched_burst_smoothness_up, -+ .maxlen = sizeof(unsigned int), ++ .procname = "sched_burst_smoothness_long", ++ .data = &sched_burst_smoothness_long, ++ .maxlen = sizeof(bool), + .mode = 0644, -+ .proc_handler = &proc_dointvec_minmax, -+ .extra1 = SYSCTL_ZERO, -+ .extra2 = &three, ++ .proc_handler = &proc_dobool, ++ }, ++ { ++ .procname = "sched_burst_smoothness_short", ++ .data = &sched_burst_smoothness_short, ++ .maxlen = sizeof(bool), ++ .mode = 0644, ++ .proc_handler = &proc_dobool, + }, +#endif // CONFIG_SCHED_BORE { .procname = "sched_child_runs_first", .data = &sysctl_sched_child_runs_first, -@@ -313,6 +450,9 @@ static inline u64 calc_delta_fair(u64 delta, struct sched_entity *se) +@@ -313,6 +453,9 @@ static inline u64 calc_delta_fair(u64 delta, struct sched_entity *se) if (unlikely(se->load.weight != NICE_0_LOAD)) delta = __calc_delta(delta, NICE_0_LOAD, &se->load); +#ifdef CONFIG_SCHED_BORE -+ if (likely(sched_bore)) delta = penalty_scale(delta, se); ++ if (likely(sched_bore)) delta = scale_slice(delta, se); +#endif // CONFIG_SCHED_BORE return delta; } -@@ -668,7 +808,7 @@ void avg_vruntime_update(struct cfs_rq *cfs_rq, s64 delta) +@@ -668,7 +811,7 @@ void avg_vruntime_update(struct cfs_rq *cfs_rq, s64 delta) * Specifically: avg_runtime() + 0 must result in entity_eligible() := true * For this to be so, the result of this function must have a left bias. */ @@ -526,7 +442,7 @@ index fd2fe1131d40..7a0e3a92c999 100644 { struct sched_entity *curr = cfs_rq->curr; s64 avg = cfs_rq->avg_vruntime; -@@ -688,7 +828,11 @@ u64 avg_vruntime(struct cfs_rq *cfs_rq) +@@ -688,7 +831,11 @@ u64 avg_vruntime(struct cfs_rq *cfs_rq) avg = div_s64(avg, load); } @@ -539,7 +455,22 @@ index fd2fe1131d40..7a0e3a92c999 100644 } /* -@@ -981,7 +1125,6 @@ static struct sched_entity *pick_eevdf(struct cfs_rq *cfs_rq) +@@ -709,13 +856,8 @@ u64 avg_vruntime(struct cfs_rq *cfs_rq) + */ + static void update_entity_lag(struct cfs_rq *cfs_rq, struct sched_entity *se) + { +- s64 lag, limit; +- + SCHED_WARN_ON(!se->on_rq); +- lag = avg_vruntime(cfs_rq) - se->vruntime; +- +- limit = calc_delta_fair(max_t(u64, 2*se->slice, TICK_NSEC), se); +- se->vlag = clamp(lag, -limit, limit); ++ se->vlag = avg_vruntime(cfs_rq) - se->vruntime; + } + + /* +@@ -981,7 +1123,6 @@ static struct sched_entity *pick_eevdf(struct cfs_rq *cfs_rq) return se; } @@ -547,7 +478,7 @@ index fd2fe1131d40..7a0e3a92c999 100644 struct sched_entity *__pick_last_entity(struct cfs_rq *cfs_rq) { struct rb_node *last = rb_last(&cfs_rq->tasks_timeline.rb_root); -@@ -995,6 +1138,7 @@ struct sched_entity *__pick_last_entity(struct cfs_rq *cfs_rq) +@@ -995,6 +1136,7 @@ struct sched_entity *__pick_last_entity(struct cfs_rq *cfs_rq) /************************************************************** * Scheduling class statistics methods: */ @@ -555,7 +486,17 @@ index fd2fe1131d40..7a0e3a92c999 100644 #ifdef CONFIG_SMP int sched_update_scaling(void) { -@@ -1181,7 +1325,11 @@ static void update_curr(struct cfs_rq *cfs_rq) +@@ -1031,6 +1173,9 @@ static void update_deadline(struct cfs_rq *cfs_rq, struct sched_entity *se) + /* + * EEVDF: vd_i = ve_i + r_i / w_i + */ ++#ifdef CONFIG_SCHED_BORE ++ update_slice_score(se); ++#endif // CONFIG_SCHED_BORE + se->deadline = se->vruntime + calc_delta_fair(se->slice, se); + + /* +@@ -1173,7 +1318,11 @@ static void update_curr(struct cfs_rq *cfs_rq) curr->sum_exec_runtime += delta_exec; schedstat_add(cfs_rq->exec_clock, delta_exec); @@ -568,21 +509,44 @@ index fd2fe1131d40..7a0e3a92c999 100644 update_deadline(cfs_rq, curr); update_min_vruntime(cfs_rq); -@@ -5061,6 +5209,23 @@ place_entity(struct cfs_rq *cfs_rq, struct sched_entity *se, int flags) +@@ -5066,6 +5215,9 @@ place_entity(struct cfs_rq *cfs_rq, struct sched_entity *se, int flags) + s64 lag = 0; + + se->slice = sysctl_sched_base_slice; ++#ifdef CONFIG_SCHED_BORE ++ update_slice_score(se); ++#endif // CONFIG_SCHED_BORE + vslice = calc_delta_fair(se->slice, se); + + /* +@@ -5080,7 +5232,13 @@ place_entity(struct cfs_rq *cfs_rq, struct sched_entity *se, int flags) + struct sched_entity *curr = cfs_rq->curr; + unsigned long load; + +- lag = se->vlag; ++ u64 slice = se->slice; ++#ifdef CONFIG_SCHED_BORE ++ if (unlikely(!sched_bore)) ++#endif // CONFIG_SCHED_BORE ++ slice *= 2; ++ s64 limit = calc_delta_fair(max_t(u64, slice, TICK_NSEC), se); ++ lag = clamp(se->vlag, -limit, limit); + + /* + * If we want to place a task and preserve lag, we have to +@@ -5142,6 +5300,21 @@ place_entity(struct cfs_rq *cfs_rq, struct sched_entity *se, int flags) if (WARN_ON_ONCE(!load)) load = 1; lag = div_s64(lag, load); + +#ifdef CONFIG_SCHED_BORE + if (flags & ENQUEUE_MIGRATED && likely(sched_bore)) { -+ struct sched_entity *last, *first; + s64 left_vruntime = vruntime, right_vruntime = vruntime; ++ struct sched_entity *first = __pick_first_entity(cfs_rq), ++ *last = __pick_last_entity(cfs_rq); + -+ if (first = __pick_first_entity(cfs_rq)) -+ left_vruntime = first->vruntime; -+ -+ if (last = __pick_last_entity(cfs_rq)) -+ right_vruntime = last->vruntime; ++ if (first) left_vruntime = first->vruntime; ++ if (last) right_vruntime = last->vruntime; + + lag = clamp(lag, + (s64)vruntime - right_vruntime, @@ -592,17 +556,20 @@ index fd2fe1131d40..7a0e3a92c999 100644 } se->vruntime = vruntime - lag; -@@ -6619,6 +6784,9 @@ static void dequeue_task_fair(struct rq *rq, struct task_struct *p, int flags) +@@ -6698,6 +6871,12 @@ static void dequeue_task_fair(struct rq *rq, struct task_struct *p, int flags) + bool was_sched_idle = sched_idle_rq(rq); + util_est_dequeue(&rq->cfs, p); ++#ifdef CONFIG_SCHED_BORE ++ if (task_sleep) { ++ update_curr(cfs_rq_of(se)); ++ restart_burst(se); ++ } ++#endif // CONFIG_SCHED_BORE for_each_sched_entity(se) { -+#ifdef CONFIG_SCHED_BORE -+ if (task_sleep) restart_burst(se); -+#endif // CONFIG_SCHED_BORE cfs_rq = cfs_rq_of(se); - dequeue_entity(cfs_rq, se, flags); - -@@ -8349,8 +8517,12 @@ static void yield_task_fair(struct rq *rq) +@@ -8429,8 +8608,13 @@ static void yield_task_fair(struct rq *rq) /* * Are we the only task in the tree? */ @@ -610,24 +577,26 @@ index fd2fe1131d40..7a0e3a92c999 100644 + if (unlikely(rq->nr_running == 1)) { +#ifdef CONFIG_SCHED_BORE + restart_burst(se); ++ update_slice_score(se); +#endif // CONFIG_SCHED_BORE return; + } clear_buddies(cfs_rq, se); -@@ -8359,6 +8531,9 @@ static void yield_task_fair(struct rq *rq) +@@ -8439,6 +8623,10 @@ static void yield_task_fair(struct rq *rq) * Update run-time statistics of the 'current'. */ update_curr(cfs_rq); +#ifdef CONFIG_SCHED_BORE + restart_burst(se); ++ update_slice_score(se); +#endif // CONFIG_SCHED_BORE /* * Tell update_rq_clock() that we've just updated, * so we don't do microscopic update in schedule() diff --git a/kernel/sched/features.h b/kernel/sched/features.h -index f770168230ae..a2e09c04f3cb 100644 +index f77016823..a2e09c04f 100644 --- a/kernel/sched/features.h +++ b/kernel/sched/features.h @@ -6,7 +6,11 @@ @@ -643,4 +612,5 @@ index f770168230ae..a2e09c04f3cb 100644 /* * Prefer to schedule the task we woke last (assuming it failed -- -2.42.0 +2.43.0.rc2 + diff --git a/patches/cachyos/0001-cachyos-base-all.patch b/patches/cachyos/0001-cachyos-base-all.patch index 2e8f4ab..d5fb21d 100644 --- a/patches/cachyos/0001-cachyos-base-all.patch +++ b/patches/cachyos/0001-cachyos-base-all.patch @@ -1,6 +1,6 @@ -From af11ceb433af09bbb5b2103fd27d399dbf94c107 Mon Sep 17 00:00:00 2001 +From 3a5d5f1ba351406e62a3e4b276c2ce8d0661d7ec Mon Sep 17 00:00:00 2001 From: Peter Jung -Date: Mon, 11 Sep 2023 14:31:43 +0200 +Date: Wed, 29 Nov 2023 19:55:12 +0100 Subject: [PATCH 1/7] amd-hdr Signed-off-by: Peter Jung @@ -109,7 +109,7 @@ index 32fe05c810c6..84bf501b02f4 100644 #define AMDGPU_MAX_BL_LEVEL 0xFF diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c -index 868946dd7ef1..bd4b95308959 100644 +index f5fdb61c821d..b8c82ca5f3a1 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c @@ -4022,6 +4022,11 @@ static int amdgpu_dm_mode_config_init(struct amdgpu_device *adev) @@ -135,7 +135,7 @@ index 868946dd7ef1..bd4b95308959 100644 if (ret) return ret; -@@ -8114,6 +8121,10 @@ static void amdgpu_dm_commit_planes(struct drm_atomic_state *state, +@@ -8119,6 +8126,10 @@ static void amdgpu_dm_commit_planes(struct drm_atomic_state *state, bundle->surface_updates[planes_count].gamma = dc_plane->gamma_correction; bundle->surface_updates[planes_count].in_transfer_func = dc_plane->in_transfer_func; bundle->surface_updates[planes_count].gamut_remap_matrix = &dc_plane->gamut_remap_matrix; @@ -146,7 +146,7 @@ index 868946dd7ef1..bd4b95308959 100644 } amdgpu_dm_plane_fill_dc_scaling_info(dm->adev, new_plane_state, -@@ -8325,6 +8336,10 @@ static void amdgpu_dm_commit_planes(struct drm_atomic_state *state, +@@ -8330,6 +8341,10 @@ static void amdgpu_dm_commit_planes(struct drm_atomic_state *state, &acrtc_state->stream->csc_color_matrix; bundle->stream_update.out_transfer_func = acrtc_state->stream->out_transfer_func; @@ -157,7 +157,7 @@ index 868946dd7ef1..bd4b95308959 100644 } acrtc_state->stream->abm_level = acrtc_state->abm_level; -@@ -9513,6 +9528,7 @@ static int dm_update_crtc_state(struct amdgpu_display_manager *dm, +@@ -9518,6 +9533,7 @@ static int dm_update_crtc_state(struct amdgpu_display_manager *dm, * when a modeset is needed, to ensure it gets reprogrammed. */ if (dm_new_crtc_state->base.color_mgmt_changed || @@ -165,7 +165,7 @@ index 868946dd7ef1..bd4b95308959 100644 drm_atomic_crtc_needs_modeset(new_crtc_state)) { ret = amdgpu_dm_update_crtc_color_mgmt(dm_new_crtc_state); if (ret) -@@ -9580,6 +9596,10 @@ static bool should_reset_plane(struct drm_atomic_state *state, +@@ -9585,6 +9601,10 @@ static bool should_reset_plane(struct drm_atomic_state *state, */ for_each_oldnew_plane_in_state(state, other, old_other_state, new_other_state, i) { struct amdgpu_framebuffer *old_afb, *new_afb; @@ -176,7 +176,7 @@ index 868946dd7ef1..bd4b95308959 100644 if (other->type == DRM_PLANE_TYPE_CURSOR) continue; -@@ -9616,6 +9636,18 @@ static bool should_reset_plane(struct drm_atomic_state *state, +@@ -9621,6 +9641,18 @@ static bool should_reset_plane(struct drm_atomic_state *state, old_other_state->color_encoding != new_other_state->color_encoding) return true; @@ -2038,29 +2038,29 @@ index ea1b639bcb28..cea5653e4020 100644 /* * Values are mapped linearly to 0.0 - 1.0 range, with 0x0 == 0.0 and -- -2.42.0 +2.43.0 -From 03aaf94b08d53e75500ba2c64978dac25397ddc6 Mon Sep 17 00:00:00 2001 +From ae084d65240cfc24d394c25eb35d1d7bd7030ca4 Mon Sep 17 00:00:00 2001 From: Peter Jung -Date: Mon, 16 Oct 2023 19:50:15 +0200 +Date: Fri, 8 Dec 2023 10:30:43 +0100 Subject: [PATCH 2/7] amd-pref-core Signed-off-by: Peter Jung --- .../admin-guide/kernel-parameters.txt | 5 + - Documentation/admin-guide/pm/amd-pstate.rst | 59 ++++- + Documentation/admin-guide/pm/amd-pstate.rst | 59 +++++- arch/x86/Kconfig | 5 +- drivers/acpi/cppc_acpi.c | 13 ++ drivers/acpi/processor_driver.c | 6 + - drivers/cpufreq/amd-pstate.c | 206 ++++++++++++++++-- + drivers/cpufreq/amd-pstate.c | 175 +++++++++++++++++- drivers/cpufreq/cpufreq.c | 13 ++ include/acpi/cppc_acpi.h | 5 + include/linux/amd-pstate.h | 10 + include/linux/cpufreq.h | 5 + - 10 files changed, 306 insertions(+), 21 deletions(-) + 10 files changed, 284 insertions(+), 12 deletions(-) diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt -index 0a1731a0f0ef..e35b795aa8aa 100644 +index 41644336e358..6e121fdb68f9 100644 --- a/Documentation/admin-guide/kernel-parameters.txt +++ b/Documentation/admin-guide/kernel-parameters.txt @@ -363,6 +363,11 @@ @@ -2224,7 +2224,7 @@ index 4bd16b3f0781..29b2fb68a35d 100644 acpi_handle_debug(handle, "Unsupported event [0x%x]\n", event); break; diff --git a/drivers/cpufreq/amd-pstate.c b/drivers/cpufreq/amd-pstate.c -index 9a1e194d5cf8..1c1f04eab389 100644 +index 1f6186475715..25f0fb53d320 100644 --- a/drivers/cpufreq/amd-pstate.c +++ b/drivers/cpufreq/amd-pstate.c @@ -37,6 +37,7 @@ @@ -2235,16 +2235,15 @@ index 9a1e194d5cf8..1c1f04eab389 100644 #include #include -@@ -49,6 +50,8 @@ +@@ -49,6 +50,7 @@ #define AMD_PSTATE_TRANSITION_LATENCY 20000 #define AMD_PSTATE_TRANSITION_DELAY 1000 +#define AMD_PSTATE_PREFCORE_THRESHOLD 166 -+#define AMD_PSTATE_MAX_CPPC_PERF 255 /* * TODO: We need more time to fine tune processors with shared memory solution -@@ -64,6 +67,7 @@ static struct cpufreq_driver amd_pstate_driver; +@@ -64,6 +66,7 @@ static struct cpufreq_driver amd_pstate_driver; static struct cpufreq_driver amd_pstate_epp_driver; static int cppc_state = AMD_PSTATE_UNDEFINED; static bool cppc_enabled; @@ -2252,14 +2251,7 @@ index 9a1e194d5cf8..1c1f04eab389 100644 /* * AMD Energy Preference Performance (EPP) -@@ -290,27 +294,26 @@ static inline int amd_pstate_enable(bool enable) - static int pstate_init_perf(struct amd_cpudata *cpudata) - { - u64 cap1; -- u32 highest_perf; - - int ret = rdmsrl_safe_on_cpu(cpudata->cpu, MSR_AMD_CPPC_CAP1, - &cap1); +@@ -297,13 +300,14 @@ static int pstate_init_perf(struct amd_cpudata *cpudata) if (ret) return ret; @@ -2274,59 +2266,41 @@ index 9a1e194d5cf8..1c1f04eab389 100644 */ - highest_perf = amd_get_highest_perf(); - if (highest_perf > AMD_CPPC_HIGHEST_PERF(cap1)) -- highest_perf = AMD_CPPC_HIGHEST_PERF(cap1); -- -- WRITE_ONCE(cpudata->highest_perf, highest_perf); + if (cpudata->hw_prefcore) -+ WRITE_ONCE(cpudata->highest_perf, AMD_PSTATE_PREFCORE_THRESHOLD); ++ highest_perf = AMD_PSTATE_PREFCORE_THRESHOLD; + else -+ WRITE_ONCE(cpudata->highest_perf, AMD_CPPC_HIGHEST_PERF(cap1)); + highest_perf = AMD_CPPC_HIGHEST_PERF(cap1); + WRITE_ONCE(cpudata->highest_perf, highest_perf); +@@ -311,6 +315,7 @@ static int pstate_init_perf(struct amd_cpudata *cpudata) WRITE_ONCE(cpudata->nominal_perf, AMD_CPPC_NOMINAL_PERF(cap1)); WRITE_ONCE(cpudata->lowest_nonlinear_perf, AMD_CPPC_LOWNONLIN_PERF(cap1)); WRITE_ONCE(cpudata->lowest_perf, AMD_CPPC_LOWEST_PERF(cap1)); + WRITE_ONCE(cpudata->prefcore_ranking, AMD_CPPC_HIGHEST_PERF(cap1)); - + WRITE_ONCE(cpudata->min_limit_perf, AMD_CPPC_LOWEST_PERF(cap1)); return 0; } -@@ -318,22 +321,21 @@ static int pstate_init_perf(struct amd_cpudata *cpudata) - static int cppc_init_perf(struct amd_cpudata *cpudata) - { - struct cppc_perf_caps cppc_perf; -- u32 highest_perf; - - int ret = cppc_get_perf_caps(cpudata->cpu, &cppc_perf); +@@ -324,8 +329,9 @@ static int cppc_init_perf(struct amd_cpudata *cpudata) if (ret) return ret; - highest_perf = amd_get_highest_perf(); - if (highest_perf > cppc_perf.highest_perf) -- highest_perf = cppc_perf.highest_perf; -- -- WRITE_ONCE(cpudata->highest_perf, highest_perf); + if (cpudata->hw_prefcore) -+ WRITE_ONCE(cpudata->highest_perf, AMD_PSTATE_PREFCORE_THRESHOLD); ++ highest_perf = AMD_PSTATE_PREFCORE_THRESHOLD; + else -+ WRITE_ONCE(cpudata->highest_perf, cppc_perf.highest_perf); + highest_perf = cppc_perf.highest_perf; - WRITE_ONCE(cpudata->nominal_perf, cppc_perf.nominal_perf); + WRITE_ONCE(cpudata->highest_perf, highest_perf); +@@ -334,6 +340,7 @@ static int cppc_init_perf(struct amd_cpudata *cpudata) WRITE_ONCE(cpudata->lowest_nonlinear_perf, cppc_perf.lowest_nonlinear_perf); WRITE_ONCE(cpudata->lowest_perf, cppc_perf.lowest_perf); + WRITE_ONCE(cpudata->prefcore_ranking, cppc_perf.highest_perf); + WRITE_ONCE(cpudata->min_limit_perf, cppc_perf.lowest_perf); if (cppc_state == AMD_PSTATE_ACTIVE) - return 0; -@@ -540,7 +542,7 @@ static void amd_pstate_adjust_perf(unsigned int cpu, - if (target_perf < capacity) - des_perf = DIV_ROUND_UP(cap_perf * target_perf, capacity); - -- min_perf = READ_ONCE(cpudata->highest_perf); -+ min_perf = READ_ONCE(cpudata->lowest_perf); - if (_min_perf < capacity) - min_perf = DIV_ROUND_UP(cap_perf * _min_perf, capacity); - -@@ -676,6 +678,124 @@ static void amd_perf_ctl_reset(unsigned int cpu) +@@ -706,6 +713,106 @@ static void amd_perf_ctl_reset(unsigned int cpu) wrmsrl_on_cpu(cpu, MSR_AMD_PERF_CTL, 0); } @@ -2362,17 +2336,20 @@ index 9a1e194d5cf8..1c1f04eab389 100644 + u64 cppc_highest_perf; + + ret = cppc_get_highest_perf(cpu, &cppc_highest_perf); ++ if (ret) ++ return ret; + WRITE_ONCE(*highest_perf, cppc_highest_perf); + } + + return (ret); +} + ++#define CPPC_MAX_PERF U8_MAX ++ +static void amd_pstate_init_prefcore(struct amd_cpudata *cpudata) +{ + int ret, prio; + u32 highest_perf; -+ static u32 max_highest_perf = 0, min_highest_perf = U32_MAX; + + ret = amd_pstate_get_highest_perf(cpudata->cpu, &highest_perf); + if (ret) @@ -2380,7 +2357,9 @@ index 9a1e194d5cf8..1c1f04eab389 100644 + + cpudata->hw_prefcore = true; + /* check if CPPC preferred core feature is enabled*/ -+ if (highest_perf == AMD_PSTATE_MAX_CPPC_PERF) { ++ if (highest_perf < CPPC_MAX_PERF) ++ prio = (int)highest_perf; ++ else { + pr_debug("AMD CPPC preferred core is unsupported!\n"); + cpudata->hw_prefcore = false; + return; @@ -2389,8 +2368,6 @@ index 9a1e194d5cf8..1c1f04eab389 100644 + if (!amd_pstate_prefcore) + return; + -+ /* The maximum value of highest perf is 255 */ -+ prio = (int)(highest_perf & 0xff); + /* + * The priorities can be set regardless of whether or not + * sched_set_itmt_support(true) has been called and it is valid to @@ -2398,60 +2375,39 @@ index 9a1e194d5cf8..1c1f04eab389 100644 + */ + sched_set_itmt_core_prio(prio, cpudata->cpu); + -+ if (max_highest_perf <= min_highest_perf) { -+ if (highest_perf > max_highest_perf) -+ max_highest_perf = highest_perf; -+ -+ if (highest_perf < min_highest_perf) -+ min_highest_perf = highest_perf; -+ -+ if (max_highest_perf > min_highest_perf) { -+ /* -+ * This code can be run during CPU online under the -+ * CPU hotplug locks, so sched_set_itmt_support() -+ * cannot be called from here. Queue up a work item -+ * to invoke it. -+ */ -+ schedule_work(&sched_prefcore_work); -+ } -+ } ++ schedule_work(&sched_prefcore_work); +} + +static void amd_pstate_update_highest_perf(unsigned int cpu) +{ -+ struct cpufreq_policy *policy; -+ struct amd_cpudata *cpudata; ++ struct cpufreq_policy *policy = cpufreq_cpu_get(cpu); ++ struct amd_cpudata *cpudata = policy->driver_data; + u32 prev_high = 0, cur_high = 0; + int ret; + + if ((!amd_pstate_prefcore) || (!cpudata->hw_prefcore)) -+ return; ++ goto free_cpufreq_put; + + ret = amd_pstate_get_highest_perf(cpu, &cur_high); + if (ret) -+ return; ++ goto free_cpufreq_put; + -+ policy = cpufreq_cpu_get(cpu); -+ cpudata = policy->driver_data; + prev_high = READ_ONCE(cpudata->prefcore_ranking); -+ + if (prev_high != cur_high) { -+ int prio; -+ + WRITE_ONCE(cpudata->prefcore_ranking, cur_high); + -+ /* The maximum value of highest perf is 255 */ -+ prio = (int)(cur_high & 0xff); -+ sched_set_itmt_core_prio(prio, cpu); ++ if (cur_high < CPPC_MAX_PERF) ++ sched_set_itmt_core_prio((int)cur_high, cpu); + } + ++free_cpufreq_put: + cpufreq_cpu_put(policy); +} + static int amd_pstate_cpu_init(struct cpufreq_policy *policy) { int min_freq, max_freq, nominal_freq, lowest_nonlinear_freq, ret; -@@ -697,6 +817,8 @@ static int amd_pstate_cpu_init(struct cpufreq_policy *policy) +@@ -727,6 +834,8 @@ static int amd_pstate_cpu_init(struct cpufreq_policy *policy) cpudata->cpu = policy->cpu; @@ -2460,7 +2416,7 @@ index 9a1e194d5cf8..1c1f04eab389 100644 ret = amd_pstate_init_perf(cpudata); if (ret) goto free_cpudata1; -@@ -845,6 +967,28 @@ static ssize_t show_amd_pstate_highest_perf(struct cpufreq_policy *policy, +@@ -877,6 +986,28 @@ static ssize_t show_amd_pstate_highest_perf(struct cpufreq_policy *policy, return sysfs_emit(buf, "%u\n", perf); } @@ -2483,20 +2439,20 @@ index 9a1e194d5cf8..1c1f04eab389 100644 + + hw_prefcore = READ_ONCE(cpudata->hw_prefcore); + -+ return sysfs_emit(buf, "%s\n", hw_prefcore ? "supported" : "unsupported"); ++ return sysfs_emit(buf, "%s\n", str_enabled_disabled(hw_prefcore)); +} + static ssize_t show_energy_performance_available_preferences( struct cpufreq_policy *policy, char *buf) { -@@ -1037,18 +1181,29 @@ static ssize_t status_store(struct device *a, struct device_attribute *b, +@@ -1074,18 +1205,29 @@ static ssize_t status_store(struct device *a, struct device_attribute *b, return ret < 0 ? ret : count; } +static ssize_t prefcore_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ -+ return sysfs_emit(buf, "%s\n", amd_pstate_prefcore ? "enabled" : "disabled"); ++ return sysfs_emit(buf, "%s\n", str_enabled_disabled(amd_pstate_prefcore)); +} + cpufreq_freq_attr_ro(amd_pstate_max_freq); @@ -2519,7 +2475,7 @@ index 9a1e194d5cf8..1c1f04eab389 100644 NULL, }; -@@ -1056,6 +1211,8 @@ static struct freq_attr *amd_pstate_epp_attr[] = { +@@ -1093,6 +1235,8 @@ static struct freq_attr *amd_pstate_epp_attr[] = { &amd_pstate_max_freq, &amd_pstate_lowest_nonlinear_freq, &amd_pstate_highest_perf, @@ -2528,7 +2484,7 @@ index 9a1e194d5cf8..1c1f04eab389 100644 &energy_performance_preference, &energy_performance_available_preferences, NULL, -@@ -1063,6 +1220,7 @@ static struct freq_attr *amd_pstate_epp_attr[] = { +@@ -1100,6 +1244,7 @@ static struct freq_attr *amd_pstate_epp_attr[] = { static struct attribute *pstate_global_attributes[] = { &dev_attr_status.attr, @@ -2536,7 +2492,7 @@ index 9a1e194d5cf8..1c1f04eab389 100644 NULL }; -@@ -1114,6 +1272,8 @@ static int amd_pstate_epp_cpu_init(struct cpufreq_policy *policy) +@@ -1151,6 +1296,8 @@ static int amd_pstate_epp_cpu_init(struct cpufreq_policy *policy) cpudata->cpu = policy->cpu; cpudata->epp_policy = 0; @@ -2545,7 +2501,7 @@ index 9a1e194d5cf8..1c1f04eab389 100644 ret = amd_pstate_init_perf(cpudata); if (ret) goto free_cpudata1; -@@ -1392,6 +1552,7 @@ static struct cpufreq_driver amd_pstate_driver = { +@@ -1433,6 +1580,7 @@ static struct cpufreq_driver amd_pstate_driver = { .suspend = amd_pstate_cpu_suspend, .resume = amd_pstate_cpu_resume, .set_boost = amd_pstate_set_boost, @@ -2553,7 +2509,7 @@ index 9a1e194d5cf8..1c1f04eab389 100644 .name = "amd-pstate", .attr = amd_pstate_attr, }; -@@ -1406,6 +1567,7 @@ static struct cpufreq_driver amd_pstate_epp_driver = { +@@ -1447,6 +1595,7 @@ static struct cpufreq_driver amd_pstate_epp_driver = { .online = amd_pstate_epp_cpu_online, .suspend = amd_pstate_epp_suspend, .resume = amd_pstate_epp_resume, @@ -2561,7 +2517,7 @@ index 9a1e194d5cf8..1c1f04eab389 100644 .name = "amd-pstate-epp", .attr = amd_pstate_epp_attr, }; -@@ -1527,7 +1689,17 @@ static int __init amd_pstate_param(char *str) +@@ -1568,7 +1717,17 @@ static int __init amd_pstate_param(char *str) return amd_pstate_set_driver(mode_idx); } @@ -2627,7 +2583,7 @@ index 6126c977ece0..c0b69ffe7bdb 100644 { return -ENOTSUPP; diff --git a/include/linux/amd-pstate.h b/include/linux/amd-pstate.h -index 446394f84606..426822612373 100644 +index 6ad02ad9c7b4..d21838835abd 100644 --- a/include/linux/amd-pstate.h +++ b/include/linux/amd-pstate.h @@ -39,11 +39,16 @@ struct amd_aperf_mperf { @@ -2662,10 +2618,10 @@ index 446394f84606..426822612373 100644 u32 lowest_nonlinear_perf; u32 lowest_perf; + u32 prefcore_ranking; - - u32 max_freq; - u32 min_freq; -@@ -81,6 +90,7 @@ struct amd_cpudata { + u32 min_limit_perf; + u32 max_limit_perf; + u32 min_limit_freq; +@@ -85,6 +94,7 @@ struct amd_cpudata { u64 freq; bool boost_supported; @@ -2704,11 +2660,11 @@ index 71d186d6933a..1cc1241fb698 100644 int (*bios_limit)(int cpu, unsigned int *limit); -- -2.42.0 +2.43.0 -From 52cb236ce6159056f0a78e4d8cf5a6ff1d612d9f Mon Sep 17 00:00:00 2001 +From 619757e1b70db8bd3163db65258ceb0c20289699 Mon Sep 17 00:00:00 2001 From: Peter Jung -Date: Mon, 11 Sep 2023 14:32:14 +0200 +Date: Wed, 29 Nov 2023 19:55:38 +0100 Subject: [PATCH 3/7] bbr3 Signed-off-by: Peter Jung @@ -2761,7 +2717,7 @@ index 5d2fcc137b88..3f7d429f73e5 100644 #define ICSK_TIME_RETRANS 1 /* Retransmit timer */ diff --git a/include/net/tcp.h b/include/net/tcp.h -index 4b03ca7cb8a5..4d4d323e9d7b 100644 +index 0239e815edf7..877fc1079435 100644 --- a/include/net/tcp.h +++ b/include/net/tcp.h @@ -372,6 +372,8 @@ static inline void tcp_dec_quickack_mode(struct sock *sk) @@ -3028,10 +2984,10 @@ index 2dfb12230f08..2e14db3bee70 100644 choice prompt "Default TCP congestion control" diff --git a/net/ipv4/tcp.c b/net/ipv4/tcp.c -index d3456cf840de..c42ae990c694 100644 +index 3d3a24f79573..f9c1ca064b50 100644 --- a/net/ipv4/tcp.c +++ b/net/ipv4/tcp.c -@@ -3077,6 +3077,7 @@ int tcp_disconnect(struct sock *sk, int flags) +@@ -3079,6 +3079,7 @@ int tcp_disconnect(struct sock *sk, int flags) tp->rx_opt.dsack = 0; tp->rx_opt.num_sacks = 0; tp->rcv_ooopack = 0; @@ -3039,7 +2995,7 @@ index d3456cf840de..c42ae990c694 100644 /* Clean up fastopen related fields */ -@@ -3752,6 +3753,8 @@ void tcp_get_info(struct sock *sk, struct tcp_info *info) +@@ -3754,6 +3755,8 @@ void tcp_get_info(struct sock *sk, struct tcp_info *info) info->tcpi_options |= TCPI_OPT_ECN; if (tp->ecn_flags & TCP_ECN_SEEN) info->tcpi_options |= TCPI_OPT_ECN_SEEN; @@ -5706,7 +5662,7 @@ index 1b34050a7538..66d40449b3f4 100644 icsk->icsk_ca_ops->init(sk); if (tcp_ca_needs_ecn(sk)) diff --git a/net/ipv4/tcp_input.c b/net/ipv4/tcp_input.c -index 8afb0950a697..90abb9418b92 100644 +index 1f9d1d445fb3..d64d0672b8c4 100644 --- a/net/ipv4/tcp_input.c +++ b/net/ipv4/tcp_input.c @@ -371,7 +371,7 @@ static void __tcp_ecn_check_ce(struct sock *sk, const struct sk_buff *skb) @@ -5758,7 +5714,7 @@ index 8afb0950a697..90abb9418b92 100644 /* When we're adding to gso_segs == 1, gso_size will be zero, * in theory this shouldn't be necessary but as long as DSACK * code can come after this skb later on it's better to keep -@@ -3705,7 +3721,8 @@ static void tcp_replace_ts_recent(struct tcp_sock *tp, u32 seq) +@@ -3706,7 +3722,8 @@ static void tcp_replace_ts_recent(struct tcp_sock *tp, u32 seq) /* This routine deals with acks during a TLP episode and ends an episode by * resetting tlp_high_seq. Ref: TLP algorithm in draft-ietf-tcpm-rack */ @@ -5768,7 +5724,7 @@ index 8afb0950a697..90abb9418b92 100644 { struct tcp_sock *tp = tcp_sk(sk); -@@ -3722,6 +3739,7 @@ static void tcp_process_tlp_ack(struct sock *sk, u32 ack, int flag) +@@ -3723,6 +3740,7 @@ static void tcp_process_tlp_ack(struct sock *sk, u32 ack, int flag) /* ACK advances: there was a loss, so reduce cwnd. Reset * tlp_high_seq in tcp_init_cwnd_reduction() */ @@ -5776,7 +5732,7 @@ index 8afb0950a697..90abb9418b92 100644 tcp_init_cwnd_reduction(sk); tcp_set_ca_state(sk, TCP_CA_CWR); tcp_end_cwnd_reduction(sk); -@@ -3732,6 +3750,11 @@ static void tcp_process_tlp_ack(struct sock *sk, u32 ack, int flag) +@@ -3733,6 +3751,11 @@ static void tcp_process_tlp_ack(struct sock *sk, u32 ack, int flag) FLAG_NOT_DUP | FLAG_DATA_SACKED))) { /* Pure dupack: original and TLP probe arrived; no loss */ tp->tlp_high_seq = 0; @@ -5788,7 +5744,7 @@ index 8afb0950a697..90abb9418b92 100644 } } -@@ -3836,6 +3859,7 @@ static int tcp_ack(struct sock *sk, const struct sk_buff *skb, int flag) +@@ -3837,6 +3860,7 @@ static int tcp_ack(struct sock *sk, const struct sk_buff *skb, int flag) prior_fack = tcp_is_sack(tp) ? tcp_highest_sack_seq(tp) : tp->snd_una; rs.prior_in_flight = tcp_packets_in_flight(tp); @@ -5796,7 +5752,7 @@ index 8afb0950a697..90abb9418b92 100644 /* ts_recent update must be made after we are sure that the packet * is in window. -@@ -3910,7 +3934,7 @@ static int tcp_ack(struct sock *sk, const struct sk_buff *skb, int flag) +@@ -3911,7 +3935,7 @@ static int tcp_ack(struct sock *sk, const struct sk_buff *skb, int flag) tcp_rack_update_reo_wnd(sk, &rs); if (tp->tlp_high_seq) @@ -5805,7 +5761,7 @@ index 8afb0950a697..90abb9418b92 100644 if (tcp_ack_is_dubious(sk, flag)) { if (!(flag & (FLAG_SND_UNA_ADVANCED | -@@ -3934,6 +3958,7 @@ static int tcp_ack(struct sock *sk, const struct sk_buff *skb, int flag) +@@ -3935,6 +3959,7 @@ static int tcp_ack(struct sock *sk, const struct sk_buff *skb, int flag) delivered = tcp_newly_delivered(sk, delivered, flag); lost = tp->lost - lost; /* freshly marked lost */ rs.is_ack_delayed = !!(flag & FLAG_ACK_MAYBE_DELAYED); @@ -5813,7 +5769,7 @@ index 8afb0950a697..90abb9418b92 100644 tcp_rate_gen(sk, delivered, lost, is_sack_reneg, sack_state.rate); tcp_cong_control(sk, ack, delivered, flag, sack_state.rate); tcp_xmit_recovery(sk, rexmit); -@@ -3953,7 +3978,7 @@ static int tcp_ack(struct sock *sk, const struct sk_buff *skb, int flag) +@@ -3954,7 +3979,7 @@ static int tcp_ack(struct sock *sk, const struct sk_buff *skb, int flag) tcp_ack_probe(sk); if (tp->tlp_high_seq) @@ -5822,7 +5778,7 @@ index 8afb0950a697..90abb9418b92 100644 return 1; old_ack: -@@ -5555,13 +5580,14 @@ static void __tcp_ack_snd_check(struct sock *sk, int ofo_possible) +@@ -5556,13 +5581,14 @@ static void __tcp_ack_snd_check(struct sock *sk, int ofo_possible) /* More than one full frame received... */ if (((tp->rcv_nxt - tp->rcv_wup) > inet_csk(sk)->icsk_ack.rcv_mss && @@ -5853,7 +5809,7 @@ index b98d476f1594..ca5c89cc7dc8 100644 const struct tcp_congestion_ops *ca; diff --git a/net/ipv4/tcp_output.c b/net/ipv4/tcp_output.c -index f0723460753c..5f8e18e9a3ee 100644 +index 9ccfdc825004..680a7eb6fccc 100644 --- a/net/ipv4/tcp_output.c +++ b/net/ipv4/tcp_output.c @@ -332,10 +332,9 @@ static void tcp_ecn_send_syn(struct sock *sk, struct sk_buff *skb) @@ -6056,11 +6012,11 @@ index 984ab4a0421e..037f54263aee 100644 event = icsk->icsk_pending; -- -2.42.0 +2.43.0 -From fae4ab7e8ace9c9c88cc5f008169d0e9079b672a Mon Sep 17 00:00:00 2001 +From 3fcc633f1079391c5ee4ffa122ea7f2e441c426d Mon Sep 17 00:00:00 2001 From: Peter Jung -Date: Mon, 9 Oct 2023 17:29:27 +0200 +Date: Wed, 29 Nov 2023 19:56:03 +0100 Subject: [PATCH 4/7] cachy Signed-off-by: Peter Jung @@ -6108,7 +6064,7 @@ Signed-off-by: Peter Jung create mode 100644 drivers/platform/x86/steamdeck.c diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt -index e35b795aa8aa..dbf3cbee7107 100644 +index 6e121fdb68f9..0fba49520140 100644 --- a/Documentation/admin-guide/kernel-parameters.txt +++ b/Documentation/admin-guide/kernel-parameters.txt @@ -4292,6 +4292,15 @@ @@ -6128,7 +6084,7 @@ index e35b795aa8aa..dbf3cbee7107 100644 Safety option to keep boot IRQs enabled. This should never be necessary. diff --git a/Makefile b/Makefile -index 5fc735c7fed1..f7bbfaf2b94f 100644 +index ee4e504a3e78..5c6874a1bb37 100644 --- a/Makefile +++ b/Makefile @@ -819,6 +819,9 @@ KBUILD_CFLAGS += -fno-delete-null-pointer-checks @@ -7003,10 +6959,10 @@ index 438c9e75a04d..1bbfeca5f01e 100644 This driver adds a CPUFreq driver which utilizes a fine grain processor performance frequency control range instead of legacy diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig -index 6644eebedaf3..e1950105d861 100644 +index 97d27e01a6ee..6a5ca108ac71 100644 --- a/drivers/i2c/busses/Kconfig +++ b/drivers/i2c/busses/Kconfig -@@ -229,6 +229,15 @@ config I2C_CHT_WC +@@ -230,6 +230,15 @@ config I2C_CHT_WC combined with a FUSB302 Type-C port-controller as such it is advised to also select CONFIG_TYPEC_FUSB302=m. @@ -7707,10 +7663,10 @@ index 809fbd014cd6..d54b35b147ee 100644 /* If the SMBus is still busy, we give up */ if (timeout == MAX_TIMEOUT) { diff --git a/drivers/md/dm-crypt.c b/drivers/md/dm-crypt.c -index 5315fd261c23..0c137e8118c7 100644 +index cef9353370b2..89747847dfc8 100644 --- a/drivers/md/dm-crypt.c +++ b/drivers/md/dm-crypt.c -@@ -3240,6 +3240,11 @@ static int crypt_ctr(struct dm_target *ti, unsigned int argc, char **argv) +@@ -3249,6 +3249,11 @@ static int crypt_ctr(struct dm_target *ti, unsigned int argc, char **argv) goto bad; } @@ -8206,10 +8162,10 @@ index 000000000000..e105e6f5cc91 +MODULE_AUTHOR("Daniel Drake "); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/pci/quirks.c b/drivers/pci/quirks.c -index eeec1d6f9023..a99b940d3c3e 100644 +index ae95d0950772..07596cfed8b7 100644 --- a/drivers/pci/quirks.c +++ b/drivers/pci/quirks.c -@@ -3720,6 +3720,106 @@ static void quirk_no_bus_reset(struct pci_dev *dev) +@@ -3722,6 +3722,106 @@ static void quirk_no_bus_reset(struct pci_dev *dev) dev->dev_flags |= PCI_DEV_FLAGS_NO_BUS_RESET; } @@ -8316,7 +8272,7 @@ index eeec1d6f9023..a99b940d3c3e 100644 /* * Some NVIDIA GPU devices do not work with bus reset, SBR needs to be * prevented for those affected devices. -@@ -5114,6 +5214,7 @@ static const struct pci_dev_acs_enabled { +@@ -5116,6 +5216,7 @@ static const struct pci_dev_acs_enabled { { PCI_VENDOR_ID_ZHAOXIN, PCI_ANY_ID, pci_quirk_zhaoxin_pcie_ports_acs }, /* Wangxun nics */ { PCI_VENDOR_ID_WANGXUN, PCI_ANY_ID, pci_quirk_wangxun_nic_acs }, @@ -14792,10 +14748,10 @@ index bf5d0b1b16f4..5a62f3ab1b80 100644 extern int sysctl_max_map_count; diff --git a/include/linux/pagemap.h b/include/linux/pagemap.h -index 351c3b7f93a1..a2ab344fc08b 100644 +index 8c9608b217b0..0bdee469ff93 100644 --- a/include/linux/pagemap.h +++ b/include/linux/pagemap.h -@@ -1261,7 +1261,7 @@ struct readahead_control { +@@ -1278,7 +1278,7 @@ struct readahead_control { ._index = i, \ } @@ -14920,7 +14876,7 @@ index 38ef6d06888e..0f78364efd4f 100644 config SCHED_HRTICK diff --git a/kernel/fork.c b/kernel/fork.c -index 3b6d20dfb9a8..200a77738a80 100644 +index 177ce7438db6..6ecece1407fc 100644 --- a/kernel/fork.c +++ b/kernel/fork.c @@ -100,6 +100,10 @@ @@ -15020,7 +14976,7 @@ index 264a2df5ecf5..0bf8853cc3a8 100644 # diff --git a/mm/page-writeback.c b/mm/page-writeback.c -index b8d3d7040a50..b11fde5c697c 100644 +index 4656534b8f5c..15ac9f2049c9 100644 --- a/mm/page-writeback.c +++ b/mm/page-writeback.c @@ -71,7 +71,11 @@ static long ratelimit_pages = 32; @@ -15113,11 +15069,11 @@ index 6f13394b112e..1fb69bffa109 100644 static void lru_gen_age_node(struct pglist_data *pgdat, struct scan_control *sc) { -- -2.42.0 +2.43.0 -From f056f2de8431bc9f51513c822c04c2977d5f7dd8 Mon Sep 17 00:00:00 2001 +From 30f602327964d34b5fc7155724fcaf97a77a7848 Mon Sep 17 00:00:00 2001 From: Peter Jung -Date: Mon, 9 Oct 2023 17:29:38 +0200 +Date: Wed, 29 Nov 2023 19:56:14 +0100 Subject: [PATCH 5/7] fixes Signed-off-by: Peter Jung @@ -15129,6 +15085,7 @@ Signed-off-by: Peter Jung arch/x86/include/asm/barrier.h | 18 - arch/x86/include/asm/processor.h | 19 + drivers/bluetooth/btusb.c | 2 +- + drivers/hid/amd-sfh-hid/amd_sfh_client.c | 6 + drivers/leds/trigger/Kconfig | 9 + drivers/leds/trigger/Makefile | 1 + drivers/leds/trigger/ledtrig-blkdev.c | 1218 +++++++++++++++++ @@ -15138,7 +15095,7 @@ Signed-off-by: Peter Jung mm/page_alloc.c | 2 +- mm/slub.c | 17 +- sound/pci/hda/patch_realtek.c | 1 + - 16 files changed, 1508 insertions(+), 34 deletions(-) + 17 files changed, 1514 insertions(+), 34 deletions(-) create mode 100644 Documentation/ABI/testing/sysfs-class-led-trigger-blkdev create mode 100644 Documentation/leds/ledtrig-blkdev.rst create mode 100644 drivers/leds/trigger/ledtrig-blkdev.c @@ -15480,10 +15437,10 @@ index a3669a7774ed..3e175d55488d 100644 + #endif /* _ASM_X86_PROCESSOR_H */ diff --git a/drivers/bluetooth/btusb.c b/drivers/bluetooth/btusb.c -index 499f4809fcdf..5e610bdba167 100644 +index 66080fae072f..7778883d3381 100644 --- a/drivers/bluetooth/btusb.c +++ b/drivers/bluetooth/btusb.c -@@ -1028,7 +1028,7 @@ static void btusb_qca_cmd_timeout(struct hci_dev *hdev) +@@ -1032,7 +1032,7 @@ static void btusb_qca_cmd_timeout(struct hci_dev *hdev) } gpiod_set_value_cansleep(reset_gpio, 0); @@ -15492,6 +15449,30 @@ index 499f4809fcdf..5e610bdba167 100644 gpiod_set_value_cansleep(reset_gpio, 1); return; +diff --git a/drivers/hid/amd-sfh-hid/amd_sfh_client.c b/drivers/hid/amd-sfh-hid/amd_sfh_client.c +index bdb578e0899f..0f1d6778051f 100644 +--- a/drivers/hid/amd-sfh-hid/amd_sfh_client.c ++++ b/drivers/hid/amd-sfh-hid/amd_sfh_client.c +@@ -25,6 +25,9 @@ void amd_sfh_set_report(struct hid_device *hid, int report_id, + struct amdtp_cl_data *cli_data = hid_data->cli_data; + int i; + ++ if (!cli_data->is_any_sensor_enabled) ++ return; ++ + for (i = 0; i < cli_data->num_hid_devices; i++) { + if (cli_data->hid_sensor_hubs[i] == hid) { + cli_data->cur_hid_dev = i; +@@ -41,6 +44,9 @@ int amd_sfh_get_report(struct hid_device *hid, int report_id, int report_type) + struct request_list *req_list = &cli_data->req_list; + int i; + ++ if (!cli_data->is_any_sensor_enabled) ++ return -ENODEV; ++ + for (i = 0; i < cli_data->num_hid_devices; i++) { + if (cli_data->hid_sensor_hubs[i] == hid) { + struct request_list *new = kzalloc(sizeof(*new), GFP_KERNEL); diff --git a/drivers/leds/trigger/Kconfig b/drivers/leds/trigger/Kconfig index 2a57328eca20..05e80cfd0ed8 100644 --- a/drivers/leds/trigger/Kconfig @@ -16757,7 +16738,7 @@ index e83c4c095041..21b8dfa5d828 100644 #endif /* CONFIG_HUGETLB_PAGE */ diff --git a/kernel/padata.c b/kernel/padata.c -index 222d60195de6..b8e6b7c48746 100644 +index 179fb1518070..b6bb0c45d7b8 100644 --- a/kernel/padata.c +++ b/kernel/padata.c @@ -45,7 +45,7 @@ struct padata_mt_job_state { @@ -16779,10 +16760,10 @@ index 222d60195de6..b8e6b7c48746 100644 struct padata_work *pw = container_of(w, struct padata_work, pw_work); struct padata_mt_job_state *ps = pw->pw_data; diff --git a/kernel/smp.c b/kernel/smp.c -index 8455a53465af..1d330b086290 100644 +index 695eb13a276d..83b0468808f9 100644 --- a/kernel/smp.c +++ b/kernel/smp.c -@@ -946,7 +946,7 @@ early_param("maxcpus", maxcpus); +@@ -957,7 +957,7 @@ early_param("maxcpus", maxcpus); #if (NR_CPUS > 1) && !defined(CONFIG_FORCE_NR_CPUS) /* Setup number of possible processor ids */ @@ -16792,7 +16773,7 @@ index 8455a53465af..1d330b086290 100644 #endif diff --git a/mm/page_alloc.c b/mm/page_alloc.c -index 95546f376302..d82162296ae7 100644 +index 85741403948f..313338789e46 100644 --- a/mm/page_alloc.c +++ b/mm/page_alloc.c @@ -294,7 +294,7 @@ int movable_zone; @@ -16856,10 +16837,10 @@ index f7940048138c..3cf4842d534e 100644 * Doh this slab cannot be placed using slub_max_order. */ diff --git a/sound/pci/hda/patch_realtek.c b/sound/pci/hda/patch_realtek.c -index 9677c09cf7a9..ed2f36335f04 100644 +index 758abe9dffd6..aa6460f49ee2 100644 --- a/sound/pci/hda/patch_realtek.c +++ b/sound/pci/hda/patch_realtek.c -@@ -9824,6 +9824,7 @@ static const struct snd_pci_quirk alc269_fixup_tbl[] = { +@@ -9873,6 +9873,7 @@ static const struct snd_pci_quirk alc269_fixup_tbl[] = { SND_PCI_QUIRK(0x1043, 0x17f3, "ROG Ally RC71L_RC71L", ALC294_FIXUP_ASUS_ALLY), SND_PCI_QUIRK(0x1043, 0x1881, "ASUS Zephyrus S/M", ALC294_FIXUP_ASUS_GX502_PINS), SND_PCI_QUIRK(0x1043, 0x18b1, "Asus MJ401TA", ALC256_FIXUP_ASUS_HEADSET_MIC), @@ -16868,11 +16849,11 @@ index 9677c09cf7a9..ed2f36335f04 100644 SND_PCI_QUIRK(0x1043, 0x194e, "ASUS UX563FD", ALC294_FIXUP_ASUS_HPE), SND_PCI_QUIRK(0x1043, 0x1970, "ASUS UX550VE", ALC289_FIXUP_ASUS_GA401), -- -2.42.0 +2.43.0 -From 3f83a38e3f5911c475ce587b716311093cf5dee6 Mon Sep 17 00:00:00 2001 +From 202d60b35aafc317c784518f1c0aa46fb68a2dcf Mon Sep 17 00:00:00 2001 From: Peter Jung -Date: Mon, 9 Oct 2023 17:29:48 +0200 +Date: Wed, 29 Nov 2023 19:56:24 +0100 Subject: [PATCH 6/7] ksm Signed-off-by: Peter Jung @@ -17148,10 +17129,10 @@ index abe087c53b4b..e393422e2983 100644 /* * 32 bit systems traditionally used different diff --git a/kernel/sys.c b/kernel/sys.c -index 2410e3999ebe..b0841a2dd2b7 100644 +index 7a4ae6d5aecd..8e0080338bfc 100644 --- a/kernel/sys.c +++ b/kernel/sys.c -@@ -2727,6 +2727,153 @@ SYSCALL_DEFINE5(prctl, int, option, unsigned long, arg2, unsigned long, arg3, +@@ -2751,6 +2751,153 @@ SYSCALL_DEFINE5(prctl, int, option, unsigned long, arg2, unsigned long, arg3, return error; } @@ -17320,11 +17301,11 @@ index e137c1385c56..2d9772d11c92 100644 COND_SYSCALL(mbind); COND_SYSCALL(get_mempolicy); -- -2.42.0 +2.43.0 -From 9980a466bacf95de3d7190183d39f918f7f562d6 Mon Sep 17 00:00:00 2001 +From 9e8cfc68f6c3690c813f08b58de6f4b505422c98 Mon Sep 17 00:00:00 2001 From: Peter Jung -Date: Mon, 16 Oct 2023 19:50:40 +0200 +Date: Wed, 29 Nov 2023 19:56:57 +0100 Subject: [PATCH 7/7] zstd Signed-off-by: Peter Jung @@ -17338,7 +17319,7 @@ Signed-off-by: Peter Jung lib/zstd/common/bitstream.h | 53 +- lib/zstd/common/compiler.h | 14 +- lib/zstd/common/cpu.h | 3 +- - lib/zstd/common/debug.c | 3 +- + lib/zstd/common/debug.c | 5 +- lib/zstd/common/debug.h | 3 +- lib/zstd/common/entropy_common.c | 42 +- lib/zstd/common/error_private.c | 12 +- @@ -17349,7 +17330,7 @@ Signed-off-by: Peter Jung lib/zstd/common/mem.h | 2 +- lib/zstd/common/portability_macros.h | 26 +- lib/zstd/common/zstd_common.c | 38 +- - lib/zstd/common/zstd_deps.h | 20 +- + lib/zstd/common/zstd_deps.h | 16 +- lib/zstd/common/zstd_internal.h | 99 +- lib/zstd/compress/clevels.h | 3 +- lib/zstd/compress/fse_compress.c | 59 +- @@ -17376,7 +17357,7 @@ Signed-off-by: Peter Jung lib/zstd/compress/zstd_ldm_geartab.h | 3 +- lib/zstd/compress/zstd_opt.c | 187 +- lib/zstd/compress/zstd_opt.h | 3 +- - lib/zstd/decompress/huf_decompress.c | 731 ++++--- + lib/zstd/decompress/huf_decompress.c | 770 ++++--- lib/zstd/decompress/zstd_ddict.c | 9 +- lib/zstd/decompress/zstd_ddict.h | 3 +- lib/zstd/decompress/zstd_decompress.c | 261 ++- @@ -17386,8 +17367,8 @@ Signed-off-by: Peter Jung lib/zstd/decompress_sources.h | 2 +- lib/zstd/zstd_common_module.c | 5 +- lib/zstd/zstd_compress_module.c | 2 +- - lib/zstd/zstd_decompress_module.c | 2 +- - 58 files changed, 4752 insertions(+), 2594 deletions(-) + lib/zstd/zstd_decompress_module.c | 4 +- + 58 files changed, 4790 insertions(+), 2595 deletions(-) create mode 100644 lib/zstd/common/allocations.h create mode 100644 lib/zstd/common/bits.h @@ -19060,7 +19041,7 @@ index 0db7b42407ee..d8319a2bef4c 100644 * * This source code is licensed under both the BSD-style license (found in the diff --git a/lib/zstd/common/debug.c b/lib/zstd/common/debug.c -index bb863c9ea616..e56ff6464e91 100644 +index bb863c9ea616..d77926cbad14 100644 --- a/lib/zstd/common/debug.c +++ b/lib/zstd/common/debug.c @@ -1,7 +1,8 @@ @@ -19073,6 +19054,13 @@ index bb863c9ea616..e56ff6464e91 100644 * * You can contact the author at : * - Source repository : https://github.com/Cyan4973/FiniteStateEntropy +@@ -21,4 +22,6 @@ + + #include "debug.h" + ++#if (DEBUGLEVEL>=2) + int g_debuglevel = DEBUGLEVEL; ++#endif diff --git a/lib/zstd/common/debug.h b/lib/zstd/common/debug.h index 6dd88d1fbd02..da0dbfc614b8 100644 --- a/lib/zstd/common/debug.h @@ -20033,7 +20021,7 @@ index 3d7e35b309b5..44b95b25344a 100644 - } -} diff --git a/lib/zstd/common/zstd_deps.h b/lib/zstd/common/zstd_deps.h -index 2c34e8a33a1c..670c5fa2a952 100644 +index 2c34e8a33a1c..f931f7d0e294 100644 --- a/lib/zstd/common/zstd_deps.h +++ b/lib/zstd/common/zstd_deps.h @@ -1,6 +1,6 @@ @@ -20044,7 +20032,7 @@ index 2c34e8a33a1c..670c5fa2a952 100644 * All rights reserved. * * This source code is licensed under both the BSD-style license (found in the -@@ -105,3 +105,21 @@ static uint64_t ZSTD_div64(uint64_t dividend, uint32_t divisor) { +@@ -105,3 +105,17 @@ static uint64_t ZSTD_div64(uint64_t dividend, uint32_t divisor) { #endif /* ZSTD_DEPS_IO */ #endif /* ZSTD_DEPS_NEED_IO */ @@ -20058,11 +20046,7 @@ index 2c34e8a33a1c..670c5fa2a952 100644 +#ifndef ZSTD_DEPS_STDINT +#define ZSTD_DEPS_STDINT + -+/* -+ * The Linux Kernel doesn't provide intptr_t, only uintptr_t, which -+ * is an unsigned long. -+ */ -+typedef long intptr_t; ++/* intptr_t already provided by ZSTD_DEPS_COMMON */ + +#endif /* ZSTD_DEPS_STDINT */ +#endif /* ZSTD_DEPS_NEED_STDINT */ @@ -28644,7 +28628,7 @@ index 22b862858ba7..faa73ff4b03d 100644 * * This source code is licensed under both the BSD-style license (found in the diff --git a/lib/zstd/decompress/huf_decompress.c b/lib/zstd/decompress/huf_decompress.c -index 60958afebc41..d172e35fbd9a 100644 +index 60958afebc41..db670d71fdab 100644 --- a/lib/zstd/decompress/huf_decompress.c +++ b/lib/zstd/decompress/huf_decompress.c @@ -1,7 +1,8 @@ @@ -28669,7 +28653,20 @@ index 60958afebc41..d172e35fbd9a 100644 /* ************************************************************** * Constants -@@ -43,27 +44,25 @@ +@@ -34,6 +35,12 @@ + * Macros + ****************************************************************/ + ++#ifdef HUF_DISABLE_FAST_DECODE ++# define HUF_ENABLE_FAST_DECODE 0 ++#else ++# define HUF_ENABLE_FAST_DECODE 1 ++#endif ++ + /* These two optional macros force the use one way or another of the two + * Huffman decompression implementations. You can't force in both directions + * at the same time. +@@ -43,27 +50,25 @@ #error "Cannot force the use of the X1 and X2 decoders at the same time!" #endif @@ -28705,7 +28702,7 @@ index 60958afebc41..d172e35fbd9a 100644 /* ************************************************************** * Error Management ****************************************************************/ -@@ -80,6 +79,11 @@ +@@ -80,6 +85,11 @@ /* ************************************************************** * BMI2 Variant Wrappers ****************************************************************/ @@ -28717,7 +28714,7 @@ index 60958afebc41..d172e35fbd9a 100644 #if DYNAMIC_BMI2 #define HUF_DGEN(fn) \ -@@ -101,9 +105,9 @@ +@@ -101,9 +111,9 @@ } \ \ static size_t fn(void* dst, size_t dstSize, void const* cSrc, \ @@ -28729,7 +28726,7 @@ index 60958afebc41..d172e35fbd9a 100644 return fn##_bmi2(dst, dstSize, cSrc, cSrcSize, DTable); \ } \ return fn##_default(dst, dstSize, cSrc, cSrcSize, DTable); \ -@@ -113,9 +117,9 @@ +@@ -113,9 +123,9 @@ #define HUF_DGEN(fn) \ static size_t fn(void* dst, size_t dstSize, void const* cSrc, \ @@ -28741,7 +28738,7 @@ index 60958afebc41..d172e35fbd9a 100644 return fn##_body(dst, dstSize, cSrc, cSrcSize, DTable); \ } -@@ -134,15 +138,28 @@ static DTableDesc HUF_getDTableDesc(const HUF_DTable* table) +@@ -134,15 +144,28 @@ static DTableDesc HUF_getDTableDesc(const HUF_DTable* table) return dtd; } @@ -28774,7 +28771,7 @@ index 60958afebc41..d172e35fbd9a 100644 typedef struct { BYTE const* ip[4]; BYTE* op[4]; -@@ -151,15 +168,17 @@ typedef struct { +@@ -151,15 +174,17 @@ typedef struct { BYTE const* ilimit; BYTE* oend; BYTE const* iend[4]; @@ -28797,7 +28794,7 @@ index 60958afebc41..d172e35fbd9a 100644 { void const* dt = DTable + 1; U32 const dtLog = HUF_getDTableDesc(DTable).tableLog; -@@ -168,9 +187,11 @@ static size_t HUF_DecompressAsmArgs_init(HUF_DecompressAsmArgs* args, void* dst, +@@ -168,9 +193,11 @@ static size_t HUF_DecompressAsmArgs_init(HUF_DecompressAsmArgs* args, void* dst, BYTE* const oend = (BYTE*)dst + dstSize; @@ -28812,7 +28809,7 @@ index 60958afebc41..d172e35fbd9a 100644 /* strict minimum : jump table + 1 byte per stream */ if (srcSize < 10) -@@ -181,7 +202,7 @@ static size_t HUF_DecompressAsmArgs_init(HUF_DecompressAsmArgs* args, void* dst, +@@ -181,7 +208,7 @@ static size_t HUF_DecompressAsmArgs_init(HUF_DecompressAsmArgs* args, void* dst, * On small inputs we don't have enough data to trigger the fast loop, so use the old decoder. */ if (dtLog != HUF_DECODER_FAST_TABLELOG) @@ -28821,7 +28818,7 @@ index 60958afebc41..d172e35fbd9a 100644 /* Read the jump table. */ { -@@ -195,13 +216,13 @@ static size_t HUF_DecompressAsmArgs_init(HUF_DecompressAsmArgs* args, void* dst, +@@ -195,13 +222,13 @@ static size_t HUF_DecompressAsmArgs_init(HUF_DecompressAsmArgs* args, void* dst, args->iend[2] = args->iend[1] + length2; args->iend[3] = args->iend[2] + length3; @@ -28837,7 +28834,7 @@ index 60958afebc41..d172e35fbd9a 100644 if (length4 > srcSize) return ERROR(corruption_detected); /* overflow */ } /* ip[] contains the position that is currently loaded into bits[]. */ -@@ -218,7 +239,7 @@ static size_t HUF_DecompressAsmArgs_init(HUF_DecompressAsmArgs* args, void* dst, +@@ -218,7 +245,7 @@ static size_t HUF_DecompressAsmArgs_init(HUF_DecompressAsmArgs* args, void* dst, /* No point to call the ASM loop for tiny outputs. */ if (args->op[3] >= oend) @@ -28846,7 +28843,7 @@ index 60958afebc41..d172e35fbd9a 100644 /* bits[] is the bit container. * It is read from the MSB down to the LSB. -@@ -227,10 +248,10 @@ static size_t HUF_DecompressAsmArgs_init(HUF_DecompressAsmArgs* args, void* dst, +@@ -227,10 +254,10 @@ static size_t HUF_DecompressAsmArgs_init(HUF_DecompressAsmArgs* args, void* dst, * set, so that CountTrailingZeros(bits[]) can be used * to count how many bits we've consumed. */ @@ -28861,7 +28858,7 @@ index 60958afebc41..d172e35fbd9a 100644 /* If ip[] >= ilimit, it is guaranteed to be safe to * reload bits[]. It may be beyond its section, but is -@@ -241,10 +262,10 @@ static size_t HUF_DecompressAsmArgs_init(HUF_DecompressAsmArgs* args, void* dst, +@@ -241,10 +268,10 @@ static size_t HUF_DecompressAsmArgs_init(HUF_DecompressAsmArgs* args, void* dst, args->oend = oend; args->dt = dt; @@ -28874,7 +28871,7 @@ index 60958afebc41..d172e35fbd9a 100644 { /* Validate that we haven't overwritten. */ if (args->op[stream] > segmentEnd) -@@ -258,15 +279,15 @@ static size_t HUF_initRemainingDStream(BIT_DStream_t* bit, HUF_DecompressAsmArgs +@@ -258,15 +285,33 @@ static size_t HUF_initRemainingDStream(BIT_DStream_t* bit, HUF_DecompressAsmArgs return ERROR(corruption_detected); /* Construct the BIT_DStream_t. */ @@ -28890,10 +28887,28 @@ index 60958afebc41..d172e35fbd9a 100644 return 0; } -#endif ++ ++/* Calls X(N) for each stream 0, 1, 2, 3. */ ++#define HUF_4X_FOR_EACH_STREAM(X) \ ++ { \ ++ X(0) \ ++ X(1) \ ++ X(2) \ ++ X(3) \ ++ } ++ ++/* Calls X(N, var) for each stream 0, 1, 2, 3. */ ++#define HUF_4X_FOR_EACH_STREAM_WITH_VAR(X, var) \ ++ { \ ++ X(0, (var)) \ ++ X(1, (var)) \ ++ X(2, (var)) \ ++ X(3, (var)) \ ++ } #ifndef HUF_FORCE_DECOMPRESS_X2 -@@ -283,10 +304,11 @@ typedef struct { BYTE nbBits; BYTE byte; } HUF_DEltX1; /* single-symbol decodi +@@ -283,10 +328,11 @@ typedef struct { BYTE nbBits; BYTE byte; } HUF_DEltX1; /* single-symbol decodi static U64 HUF_DEltX1_set4(BYTE symbol, BYTE nbBits) { U64 D4; if (MEM_isLittleEndian()) { @@ -28907,7 +28922,7 @@ index 60958afebc41..d172e35fbd9a 100644 D4 *= 0x0001000100010001ULL; return D4; } -@@ -329,13 +351,7 @@ typedef struct { +@@ -329,13 +375,7 @@ typedef struct { BYTE huffWeight[HUF_SYMBOLVALUE_MAX + 1]; } HUF_ReadDTableX1_Workspace; @@ -28922,7 +28937,7 @@ index 60958afebc41..d172e35fbd9a 100644 { U32 tableLog = 0; U32 nbSymbols = 0; -@@ -350,7 +366,7 @@ size_t HUF_readDTableX1_wksp_bmi2(HUF_DTable* DTable, const void* src, size_t sr +@@ -350,7 +390,7 @@ size_t HUF_readDTableX1_wksp_bmi2(HUF_DTable* DTable, const void* src, size_t sr DEBUG_STATIC_ASSERT(sizeof(DTableDesc) == sizeof(HUF_DTable)); /* ZSTD_memset(huffWeight, 0, sizeof(huffWeight)); */ /* is not necessary, even though some analyzer complain ... */ @@ -28931,7 +28946,7 @@ index 60958afebc41..d172e35fbd9a 100644 if (HUF_isError(iSize)) return iSize; -@@ -377,9 +393,8 @@ size_t HUF_readDTableX1_wksp_bmi2(HUF_DTable* DTable, const void* src, size_t sr +@@ -377,9 +417,8 @@ size_t HUF_readDTableX1_wksp_bmi2(HUF_DTable* DTable, const void* src, size_t sr * rankStart[0] is not filled because there are no entries in the table for * weight 0. */ @@ -28943,7 +28958,7 @@ index 60958afebc41..d172e35fbd9a 100644 int const unroll = 4; int const nLimit = (int)nbSymbols - unroll + 1; for (n=0; n<(int)tableLog+1; n++) { -@@ -406,10 +421,9 @@ size_t HUF_readDTableX1_wksp_bmi2(HUF_DTable* DTable, const void* src, size_t sr +@@ -406,10 +445,9 @@ size_t HUF_readDTableX1_wksp_bmi2(HUF_DTable* DTable, const void* src, size_t sr * We can switch based on the length to a different inner loop which is * optimized for that particular case. */ @@ -28957,7 +28972,7 @@ index 60958afebc41..d172e35fbd9a 100644 for (w=1; wrankVal[w]; int const length = (1 << w) >> 1; -@@ -519,7 +533,7 @@ HUF_decodeStreamX1(BYTE* p, BIT_DStream_t* const bitDPtr, BYTE* const pEnd, cons +@@ -519,7 +557,7 @@ HUF_decodeStreamX1(BYTE* p, BIT_DStream_t* const bitDPtr, BYTE* const pEnd, cons while (p < pEnd) HUF_DECODE_SYMBOLX1_0(p, bitDPtr); @@ -28966,7 +28981,7 @@ index 60958afebc41..d172e35fbd9a 100644 } FORCE_INLINE_TEMPLATE size_t -@@ -545,6 +559,10 @@ HUF_decompress1X1_usingDTable_internal_body( +@@ -545,6 +583,10 @@ HUF_decompress1X1_usingDTable_internal_body( return dstSize; } @@ -28977,7 +28992,7 @@ index 60958afebc41..d172e35fbd9a 100644 FORCE_INLINE_TEMPLATE size_t HUF_decompress4X1_usingDTable_internal_body( void* dst, size_t dstSize, -@@ -588,6 +606,7 @@ HUF_decompress4X1_usingDTable_internal_body( +@@ -588,6 +630,7 @@ HUF_decompress4X1_usingDTable_internal_body( if (length4 > cSrcSize) return ERROR(corruption_detected); /* overflow */ if (opStart4 > oend) return ERROR(corruption_detected); /* overflow */ @@ -28985,7 +29000,7 @@ index 60958afebc41..d172e35fbd9a 100644 CHECK_F( BIT_initDStream(&bitD1, istart1, length1) ); CHECK_F( BIT_initDStream(&bitD2, istart2, length2) ); CHECK_F( BIT_initDStream(&bitD3, istart3, length3) ); -@@ -650,38 +669,142 @@ size_t HUF_decompress4X1_usingDTable_internal_bmi2(void* dst, size_t dstSize, vo +@@ -650,38 +693,156 @@ size_t HUF_decompress4X1_usingDTable_internal_bmi2(void* dst, size_t dstSize, vo } #endif @@ -29025,7 +29040,6 @@ index 60958afebc41..d172e35fbd9a 100644 + for (;;) { + BYTE* olimit; + int stream; -+ int symbol; + + /* Assert loop preconditions */ +#ifndef NDEBUG @@ -29072,27 +29086,42 @@ index 60958afebc41..d172e35fbd9a 100644 + } +#endif + ++#define HUF_4X1_DECODE_SYMBOL(_stream, _symbol) \ ++ { \ ++ int const index = (int)(bits[(_stream)] >> 53); \ ++ int const entry = (int)dtable[index]; \ ++ bits[(_stream)] <<= (entry & 0x3F); \ ++ op[(_stream)][(_symbol)] = (BYTE)((entry >> 8) & 0xFF); \ ++ } ++ ++#define HUF_4X1_RELOAD_STREAM(_stream) \ ++ { \ ++ int const ctz = ZSTD_countTrailingZeros64(bits[(_stream)]); \ ++ int const nbBits = ctz & 7; \ ++ int const nbBytes = ctz >> 3; \ ++ op[(_stream)] += 5; \ ++ ip[(_stream)] -= nbBytes; \ ++ bits[(_stream)] = MEM_read64(ip[(_stream)]) | 1; \ ++ bits[(_stream)] <<= nbBits; \ ++ } ++ ++ /* Manually unroll the loop because compilers don't consistently ++ * unroll the inner loops, which destroys performance. ++ */ + do { + /* Decode 5 symbols in each of the 4 streams */ -+ for (symbol = 0; symbol < 5; ++symbol) { -+ for (stream = 0; stream < 4; ++stream) { -+ int const index = (int)(bits[stream] >> 53); -+ int const entry = (int)dtable[index]; -+ bits[stream] <<= (entry & 63); -+ op[stream][symbol] = (BYTE)((entry >> 8) & 0xFF); -+ } -+ } -+ /* Reload the bitstreams */ -+ for (stream = 0; stream < 4; ++stream) { -+ int const ctz = ZSTD_countTrailingZeros64(bits[stream]); -+ int const nbBits = ctz & 7; -+ int const nbBytes = ctz >> 3; -+ op[stream] += 5; -+ ip[stream] -= nbBytes; -+ bits[stream] = MEM_read64(ip[stream]) | 1; -+ bits[stream] <<= nbBits; -+ } ++ HUF_4X_FOR_EACH_STREAM_WITH_VAR(HUF_4X1_DECODE_SYMBOL, 0) ++ HUF_4X_FOR_EACH_STREAM_WITH_VAR(HUF_4X1_DECODE_SYMBOL, 1) ++ HUF_4X_FOR_EACH_STREAM_WITH_VAR(HUF_4X1_DECODE_SYMBOL, 2) ++ HUF_4X_FOR_EACH_STREAM_WITH_VAR(HUF_4X1_DECODE_SYMBOL, 3) ++ HUF_4X_FOR_EACH_STREAM_WITH_VAR(HUF_4X1_DECODE_SYMBOL, 4) ++ ++ /* Reload each of the 4 the bitstreams */ ++ HUF_4X_FOR_EACH_STREAM(HUF_4X1_RELOAD_STREAM) + } while (op[3] < olimit); ++ ++#undef HUF_4X1_DECODE_SYMBOL ++#undef HUF_4X1_RELOAD_STREAM + } + +_out: @@ -29141,7 +29170,7 @@ index 60958afebc41..d172e35fbd9a 100644 /* Our loop guarantees that ip[] >= ilimit and that we haven't * overwritten any op[]. -@@ -694,8 +817,7 @@ HUF_decompress4X1_usingDTable_internal_bmi2_asm( +@@ -694,8 +855,7 @@ HUF_decompress4X1_usingDTable_internal_bmi2_asm( (void)iend; /* finish bit streams one by one. */ @@ -29151,7 +29180,7 @@ index 60958afebc41..d172e35fbd9a 100644 BYTE* segmentEnd = (BYTE*)dst; int i; for (i = 0; i < 4; ++i) { -@@ -712,97 +834,59 @@ HUF_decompress4X1_usingDTable_internal_bmi2_asm( +@@ -712,97 +872,59 @@ HUF_decompress4X1_usingDTable_internal_bmi2_asm( } /* decoded size */ @@ -29213,13 +29242,13 @@ index 60958afebc41..d172e35fbd9a 100644 - if (dtd.tableType != 0) return ERROR(GENERIC); - return HUF_decompress1X1_usingDTable_internal(dst, dstSize, cSrc, cSrcSize, DTable, /* bmi2 */ 0); -} - +- -size_t HUF_decompress1X1_DCtx_wksp(HUF_DTable* DCtx, void* dst, size_t dstSize, - const void* cSrc, size_t cSrcSize, - void* workSpace, size_t wkspSize) -{ - const BYTE* ip = (const BYTE*) cSrc; -- + - size_t const hSize = HUF_readDTableX1_wksp(DCtx, cSrc, cSrcSize, workSpace, wkspSize); - if (HUF_isError(hSize)) return hSize; - if (hSize >= cSrcSize) return ERROR(srcSize_wrong); @@ -29237,7 +29266,7 @@ index 60958afebc41..d172e35fbd9a 100644 - DTableDesc dtd = HUF_getDTableDesc(DTable); - if (dtd.tableType != 0) return ERROR(GENERIC); - return HUF_decompress4X1_usingDTable_internal(dst, dstSize, cSrc, cSrcSize, DTable, /* bmi2 */ 0); -+ if (!(flags & HUF_flags_disableFast)) { ++ if (HUF_ENABLE_FAST_DECODE && !(flags & HUF_flags_disableFast)) { + size_t const ret = HUF_decompress4X1_usingDTable_internal_fast(dst, dstSize, cSrc, cSrcSize, DTable, loopFn); + if (ret != 0) + return ret; @@ -29260,21 +29289,21 @@ index 60958afebc41..d172e35fbd9a 100644 ip += hSize; cSrcSize -= hSize; - return HUF_decompress4X1_usingDTable_internal(dst, dstSize, ip, cSrcSize, dctx, bmi2); -+ return HUF_decompress4X1_usingDTable_internal(dst, dstSize, ip, cSrcSize, dctx, flags); - } - +-} +- -size_t HUF_decompress4X1_DCtx_wksp(HUF_DTable* dctx, void* dst, size_t dstSize, - const void* cSrc, size_t cSrcSize, - void* workSpace, size_t wkspSize) -{ - return HUF_decompress4X1_DCtx_wksp_bmi2(dctx, dst, dstSize, cSrc, cSrcSize, workSpace, wkspSize, 0); --} -- ++ return HUF_decompress4X1_usingDTable_internal(dst, dstSize, ip, cSrcSize, dctx, flags); + } + - #endif /* HUF_FORCE_DECOMPRESS_X2 */ -@@ -985,7 +1069,7 @@ static void HUF_fillDTableX2Level2(HUF_DEltX2* DTable, U32 targetLog, const U32 +@@ -985,7 +1107,7 @@ static void HUF_fillDTableX2Level2(HUF_DEltX2* DTable, U32 targetLog, const U32 static void HUF_fillDTableX2(HUF_DEltX2* DTable, const U32 targetLog, const sortedSymbol_t* sortedList, @@ -29283,7 +29312,7 @@ index 60958afebc41..d172e35fbd9a 100644 const U32 nbBitsBaseline) { U32* const rankVal = rankValOrigin[0]; -@@ -1040,14 +1124,7 @@ typedef struct { +@@ -1040,14 +1162,7 @@ typedef struct { size_t HUF_readDTableX2_wksp(HUF_DTable* DTable, const void* src, size_t srcSize, @@ -29299,7 +29328,7 @@ index 60958afebc41..d172e35fbd9a 100644 { U32 tableLog, maxW, nbSymbols; DTableDesc dtd = HUF_getDTableDesc(DTable); -@@ -1069,7 +1146,7 @@ size_t HUF_readDTableX2_wksp_bmi2(HUF_DTable* DTable, +@@ -1069,7 +1184,7 @@ size_t HUF_readDTableX2_wksp_bmi2(HUF_DTable* DTable, if (maxTableLog > HUF_TABLELOG_MAX) return ERROR(tableLog_tooLarge); /* ZSTD_memset(weightList, 0, sizeof(weightList)); */ /* is not necessary, even though some analyzer complain ... */ @@ -29308,7 +29337,7 @@ index 60958afebc41..d172e35fbd9a 100644 if (HUF_isError(iSize)) return iSize; /* check result */ -@@ -1240,6 +1317,11 @@ HUF_decompress1X2_usingDTable_internal_body( +@@ -1240,6 +1355,11 @@ HUF_decompress1X2_usingDTable_internal_body( /* decoded size */ return dstSize; } @@ -29320,7 +29349,7 @@ index 60958afebc41..d172e35fbd9a 100644 FORCE_INLINE_TEMPLATE size_t HUF_decompress4X2_usingDTable_internal_body( void* dst, size_t dstSize, -@@ -1280,8 +1362,9 @@ HUF_decompress4X2_usingDTable_internal_body( +@@ -1280,8 +1400,9 @@ HUF_decompress4X2_usingDTable_internal_body( DTableDesc const dtd = HUF_getDTableDesc(DTable); U32 const dtLog = dtd.tableLog; @@ -29332,7 +29361,7 @@ index 60958afebc41..d172e35fbd9a 100644 CHECK_F( BIT_initDStream(&bitD1, istart1, length1) ); CHECK_F( BIT_initDStream(&bitD2, istart2, length2) ); CHECK_F( BIT_initDStream(&bitD3, istart3, length3) ); -@@ -1366,36 +1449,177 @@ size_t HUF_decompress4X2_usingDTable_internal_bmi2(void* dst, size_t dstSize, vo +@@ -1366,36 +1487,178 @@ size_t HUF_decompress4X2_usingDTable_internal_bmi2(void* dst, size_t dstSize, vo } #endif @@ -29348,9 +29377,7 @@ index 60958afebc41..d172e35fbd9a 100644 -HUF_ASM_DECL void HUF_decompress4X2_usingDTable_internal_bmi2_asm_loop(HUF_DecompressAsmArgs* args) ZSTDLIB_HIDDEN; +HUF_ASM_DECL void HUF_decompress4X2_usingDTable_internal_fast_asm_loop(HUF_DecompressFastArgs* args) ZSTDLIB_HIDDEN; - --static HUF_ASM_X86_64_BMI2_ATTRS size_t --HUF_decompress4X2_usingDTable_internal_bmi2_asm( ++ +#endif + +static HUF_FAST_BMI2_ATTRS @@ -29379,7 +29406,6 @@ index 60958afebc41..d172e35fbd9a 100644 + for (;;) { + BYTE* olimit; + int stream; -+ int symbol; + + /* Assert loop preconditions */ +#ifndef NDEBUG @@ -29436,53 +29462,57 @@ index 60958afebc41..d172e35fbd9a 100644 + } +#endif + ++#define HUF_4X2_DECODE_SYMBOL(_stream, _decode3) \ ++ if ((_decode3) || (_stream) != 3) { \ ++ int const index = (int)(bits[(_stream)] >> 53); \ ++ HUF_DEltX2 const entry = dtable[index]; \ ++ MEM_write16(op[(_stream)], entry.sequence); \ ++ bits[(_stream)] <<= (entry.nbBits) & 0x3F; \ ++ op[(_stream)] += (entry.length); \ ++ } ++ ++#define HUF_4X2_RELOAD_STREAM(_stream) \ ++ { \ ++ HUF_4X2_DECODE_SYMBOL(3, 1) \ ++ { \ ++ int const ctz = ZSTD_countTrailingZeros64(bits[(_stream)]); \ ++ int const nbBits = ctz & 7; \ ++ int const nbBytes = ctz >> 3; \ ++ ip[(_stream)] -= nbBytes; \ ++ bits[(_stream)] = MEM_read64(ip[(_stream)]) | 1; \ ++ bits[(_stream)] <<= nbBits; \ ++ } \ ++ } ++ ++ /* Manually unroll the loop because compilers don't consistently ++ * unroll the inner loops, which destroys performance. ++ */ + do { -+ /* Do 5 table lookups for each of the first 3 streams */ -+ for (symbol = 0; symbol < 5; ++symbol) { -+ for (stream = 0; stream < 3; ++stream) { -+ int const index = (int)(bits[stream] >> 53); -+ HUF_DEltX2 const entry = dtable[index]; -+ MEM_write16(op[stream], entry.sequence); -+ bits[stream] <<= (entry.nbBits); -+ op[stream] += (entry.length); -+ } -+ } -+ /* Do 1 table lookup from the final stream */ -+ { -+ int const index = (int)(bits[3] >> 53); -+ HUF_DEltX2 const entry = dtable[index]; -+ MEM_write16(op[3], entry.sequence); -+ bits[3] <<= (entry.nbBits); -+ op[3] += (entry.length); -+ } -+ /* Do 4 table lookups from the final stream & reload bitstreams */ -+ for (stream = 0; stream < 4; ++stream) { -+ /* Do a table lookup from the final stream. -+ * This is interleaved with the reloading to reduce register -+ * pressure. This shouldn't be necessary, but compilers can -+ * struggle with codegen with high register pressure. -+ */ -+ { -+ int const index = (int)(bits[3] >> 53); -+ HUF_DEltX2 const entry = dtable[index]; -+ MEM_write16(op[3], entry.sequence); -+ bits[3] <<= (entry.nbBits); -+ op[3] += (entry.length); -+ } -+ /* Reload the bistreams. The final bitstream must be reloaded -+ * after the 5th symbol was decoded. -+ */ -+ { -+ int const ctz = ZSTD_countTrailingZeros64(bits[stream]); -+ int const nbBits = ctz & 7; -+ int const nbBytes = ctz >> 3; -+ ip[stream] -= nbBytes; -+ bits[stream] = MEM_read64(ip[stream]) | 1; -+ bits[stream] <<= nbBits; -+ } -+ } ++ /* Decode 5 symbols from each of the first 3 streams. ++ * The final stream will be decoded during the reload phase ++ * to reduce register pressure. ++ */ ++ HUF_4X_FOR_EACH_STREAM_WITH_VAR(HUF_4X2_DECODE_SYMBOL, 0) ++ HUF_4X_FOR_EACH_STREAM_WITH_VAR(HUF_4X2_DECODE_SYMBOL, 0) ++ HUF_4X_FOR_EACH_STREAM_WITH_VAR(HUF_4X2_DECODE_SYMBOL, 0) ++ HUF_4X_FOR_EACH_STREAM_WITH_VAR(HUF_4X2_DECODE_SYMBOL, 0) ++ HUF_4X_FOR_EACH_STREAM_WITH_VAR(HUF_4X2_DECODE_SYMBOL, 0) ++ ++ /* Decode one symbol from the final stream */ ++ HUF_4X2_DECODE_SYMBOL(3, 1) ++ ++ /* Decode 4 symbols from the final stream & reload bitstreams. ++ * The final stream is reloaded last, meaning that all 5 symbols ++ * are decoded from the final stream before it is reloaded. ++ */ ++ HUF_4X_FOR_EACH_STREAM(HUF_4X2_RELOAD_STREAM) + } while (op[3] < olimit); + } + +-static HUF_ASM_X86_64_BMI2_ATTRS size_t +-HUF_decompress4X2_usingDTable_internal_bmi2_asm( ++#undef HUF_4X2_DECODE_SYMBOL ++#undef HUF_4X2_RELOAD_STREAM + +_out: + @@ -29521,7 +29551,7 @@ index 60958afebc41..d172e35fbd9a 100644 /* note : op4 already verified within main loop */ assert(args.ip[0] >= iend); -@@ -1426,91 +1650,72 @@ HUF_decompress4X2_usingDTable_internal_bmi2_asm( +@@ -1426,91 +1689,72 @@ HUF_decompress4X2_usingDTable_internal_bmi2_asm( /* decoded size */ return dstSize; } @@ -29562,7 +29592,7 @@ index 60958afebc41..d172e35fbd9a 100644 + } #endif + -+ if (!(flags & HUF_flags_disableFast)) { ++ if (HUF_ENABLE_FAST_DECODE && !(flags & HUF_flags_disableFast)) { + size_t const ret = HUF_decompress4X2_usingDTable_internal_fast(dst, dstSize, cSrc, cSrcSize, DTable, loopFn); + if (ret != 0) + return ret; @@ -29597,8 +29627,9 @@ index 60958afebc41..d172e35fbd9a 100644 ip += hSize; cSrcSize -= hSize; - return HUF_decompress1X2_usingDTable_internal(dst, dstSize, ip, cSrcSize, DCtx, /* bmi2 */ 0); --} -- ++ return HUF_decompress1X2_usingDTable_internal(dst, dstSize, ip, cSrcSize, DCtx, flags); + } + - -size_t HUF_decompress4X2_usingDTable( - void* dst, size_t dstSize, @@ -29608,9 +29639,8 @@ index 60958afebc41..d172e35fbd9a 100644 - DTableDesc dtd = HUF_getDTableDesc(DTable); - if (dtd.tableType != 1) return ERROR(GENERIC); - return HUF_decompress4X2_usingDTable_internal(dst, dstSize, cSrc, cSrcSize, DTable, /* bmi2 */ 0); -+ return HUF_decompress1X2_usingDTable_internal(dst, dstSize, ip, cSrcSize, DCtx, flags); - } - +-} +- -static size_t HUF_decompress4X2_DCtx_wksp_bmi2(HUF_DTable* dctx, void* dst, size_t dstSize, +static size_t HUF_decompress4X2_DCtx_wksp(HUF_DTable* dctx, void* dst, size_t dstSize, const void* cSrc, size_t cSrcSize, @@ -29641,7 +29671,7 @@ index 60958afebc41..d172e35fbd9a 100644 #endif /* HUF_FORCE_DECOMPRESS_X1 */ -@@ -1518,44 +1723,6 @@ size_t HUF_decompress4X2_DCtx_wksp(HUF_DTable* dctx, void* dst, size_t dstSize, +@@ -1518,44 +1762,6 @@ size_t HUF_decompress4X2_DCtx_wksp(HUF_DTable* dctx, void* dst, size_t dstSize, /* Universal decompression selectors */ /* ***********************************/ @@ -29686,7 +29716,7 @@ index 60958afebc41..d172e35fbd9a 100644 #if !defined(HUF_FORCE_DECOMPRESS_X1) && !defined(HUF_FORCE_DECOMPRESS_X2) typedef struct { U32 tableTime; U32 decode256Time; } algo_time_t; -@@ -1610,36 +1777,9 @@ U32 HUF_selectDecoder (size_t dstSize, size_t cSrcSize) +@@ -1610,36 +1816,9 @@ U32 HUF_selectDecoder (size_t dstSize, size_t cSrcSize) #endif } @@ -29724,7 +29754,7 @@ index 60958afebc41..d172e35fbd9a 100644 { /* validation checks */ if (dstSize == 0) return ERROR(dstSize_tooSmall); -@@ -1652,71 +1792,71 @@ size_t HUF_decompress1X_DCtx_wksp(HUF_DTable* dctx, void* dst, size_t dstSize, +@@ -1652,71 +1831,71 @@ size_t HUF_decompress1X_DCtx_wksp(HUF_DTable* dctx, void* dst, size_t dstSize, (void)algoNb; assert(algoNb == 0); return HUF_decompress1X1_DCtx_wksp(dctx, dst, dstSize, cSrc, @@ -29814,7 +29844,7 @@ index 60958afebc41..d172e35fbd9a 100644 { /* validation checks */ if (dstSize == 0) return ERROR(dstSize_tooSmall); -@@ -1726,15 +1866,14 @@ size_t HUF_decompress4X_hufOnly_wksp_bmi2(HUF_DTable* dctx, void* dst, size_t ds +@@ -1726,15 +1905,14 @@ size_t HUF_decompress4X_hufOnly_wksp_bmi2(HUF_DTable* dctx, void* dst, size_t ds #if defined(HUF_FORCE_DECOMPRESS_X1) (void)algoNb; assert(algoNb == 0); @@ -31087,7 +31117,7 @@ index 04e1b5c01d9b..8ecf43226af2 100644 * * This source code is licensed under both the BSD-style license (found in the diff --git a/lib/zstd/zstd_decompress_module.c b/lib/zstd/zstd_decompress_module.c -index f4ed952ed485..eb1c49e69722 100644 +index f4ed952ed485..7d31518e9d5a 100644 --- a/lib/zstd/zstd_decompress_module.c +++ b/lib/zstd/zstd_decompress_module.c @@ -1,6 +1,6 @@ @@ -31098,7 +31128,15 @@ index f4ed952ed485..eb1c49e69722 100644 * All rights reserved. * * This source code is licensed under both the BSD-style license (found in the +@@ -77,7 +77,7 @@ EXPORT_SYMBOL(zstd_init_dstream); + + size_t zstd_reset_dstream(zstd_dstream *dstream) + { +- return ZSTD_resetDStream(dstream); ++ return ZSTD_DCtx_reset(dstream, ZSTD_reset_session_only); + } + EXPORT_SYMBOL(zstd_reset_dstream); + -- -2.42.0 - +2.43.0 diff --git a/patches/nobara/0001-Revert-PCI-Add-a-REBAR-size-quirk-for-Sapphire-RX-56.patch b/patches/nobara/0001-Revert-PCI-Add-a-REBAR-size-quirk-for-Sapphire-RX-56.patch index e68df11..dd8f961 100644 --- a/patches/nobara/0001-Revert-PCI-Add-a-REBAR-size-quirk-for-Sapphire-RX-56.patch +++ b/patches/nobara/0001-Revert-PCI-Add-a-REBAR-size-quirk-for-Sapphire-RX-56.patch @@ -10,25 +10,25 @@ This reverts commit 907830b0fc9e374d00f3c83de5e426157b482c01. 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/drivers/pci/pci.c b/drivers/pci/pci.c -index 3f353572588d..1c8cc4b98f95 100644 +index a607f277c..3174fa871 100644 --- a/drivers/pci/pci.c +++ b/drivers/pci/pci.c -@@ -3637,14 +3637,7 @@ u32 pci_rebar_get_possible_sizes(struct pci_dev *pdev, int bar) +@@ -3755,14 +3755,8 @@ u32 pci_rebar_get_possible_sizes(struct pci_dev *pdev, int bar) return 0; - + pci_read_config_dword(pdev, pos + PCI_REBAR_CAP, &cap); -- cap &= PCI_REBAR_CAP_SIZES; -- +- cap = FIELD_GET(PCI_REBAR_CAP_SIZES, cap); + - /* Sapphire RX 5600 XT Pulse has an invalid cap dword for BAR 0 */ - if (pdev->vendor == PCI_VENDOR_ID_ATI && pdev->device == 0x731f && -- bar == 0 && cap == 0x7000) -- cap = 0x3f000; +- bar == 0 && cap == 0x700) +- return 0x3f00; - -- return cap >> 4; +- return cap; + return (cap & PCI_REBAR_CAP_SIZES) >> 4; } EXPORT_SYMBOL(pci_rebar_get_possible_sizes); - + -- 2.30.2 diff --git a/patches/nobara/0001-Revert-nvme-pci-drop-redundant-pci_enable_pcie_error.patch b/patches/nobara/0001-Revert-nvme-pci-drop-redundant-pci_enable_pcie_error.patch index b44c15f..6eac856 100644 --- a/patches/nobara/0001-Revert-nvme-pci-drop-redundant-pci_enable_pcie_error.patch +++ b/patches/nobara/0001-Revert-nvme-pci-drop-redundant-pci_enable_pcie_error.patch @@ -4,7 +4,11 @@ Date: Mon, 30 Oct 2023 22:36:19 -0600 Subject: [PATCH] Revert "nvme-pci: drop redundant pci_enable_pcie_error_reporting()" -This reverts commit 1ad11eafc63ac16e667853bee4273879226d2d1b. +This reverts commits: +1ad11eafc63ac16e667853bee4273879226d2d1b +7ec4b34be4234599cf1241ef807cdb7c3636f6fe +69b264df8a412820e98867dbab871c6526c5e5aa + --- drivers/nvme/host/pci.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) @@ -43,4 +47,62 @@ index 3f0c9ee09a12..bc11bfe6f87a 100644 nvme_cancel_tagset(&dev->ctrl); -- 2.41.0 +diff --git a/include/linux/aer.h b/include/linux/aer.h +index 29cc10220..94ce49a5f 100644 +--- a/include/linux/aer.h ++++ b/include/linux/aer.h +@@ -41,9 +41,20 @@ struct aer_capability_regs { + }; + #if defined(CONFIG_PCIEAER) ++/* PCIe port driver needs this function to enable AER */ ++int pci_enable_pcie_error_reporting(struct pci_dev *dev); ++int pci_disable_pcie_error_reporting(struct pci_dev *dev); + int pci_aer_clear_nonfatal_status(struct pci_dev *dev); + int pcie_aer_is_native(struct pci_dev *dev); + #else ++static inline int pci_enable_pcie_error_reporting(struct pci_dev *dev) ++{ ++ return -EINVAL; ++} ++static inline int pci_disable_pcie_error_reporting(struct pci_dev *dev) ++{ ++ return -EINVAL; ++} + static inline int pci_aer_clear_nonfatal_status(struct pci_dev *dev) + { + return -EINVAL; + +diff --git a/drivers/pci/pcie/aer.c b/drivers/pci/pcie/aer.c +index 9c8fd69ae..0dc7be481 100644 +--- a/drivers/pci/pcie/aer.c ++++ b/drivers/pci/pcie/aer.c +@@ -231,7 +231,7 @@ int pcie_aer_is_native(struct pci_dev *dev) + } + EXPORT_SYMBOL_NS_GPL(pcie_aer_is_native, CXL); + +-static int pci_enable_pcie_error_reporting(struct pci_dev *dev) ++int pci_enable_pcie_error_reporting(struct pci_dev *dev) + { + int rc; + +@@ -241,6 +241,19 @@ static int pci_enable_pcie_error_reporting(struct pci_dev *dev) + rc = pcie_capability_set_word(dev, PCI_EXP_DEVCTL, PCI_EXP_AER_FLAGS); + return pcibios_err_to_errno(rc); + } ++EXPORT_SYMBOL_GPL(pci_enable_pcie_error_reporting); ++ ++int pci_disable_pcie_error_reporting(struct pci_dev *dev) ++{ ++ int rc; ++ ++ if (!pcie_aer_is_native(dev)) ++ return -EIO; ++ ++ rc = pcie_capability_clear_word(dev, PCI_EXP_DEVCTL, PCI_EXP_AER_FLAGS); ++ return pcibios_err_to_errno(rc); ++} ++EXPORT_SYMBOL_GPL(pci_disable_pcie_error_reporting); + + int pci_aer_clear_nonfatal_status(struct pci_dev *dev) + { diff --git a/patches/nobara/0001-Set-amdgpu.ppfeaturemask-0xffffffff-as-default.patch b/patches/nobara/0001-Set-amdgpu.ppfeaturemask-0xffffffff-as-default.patch new file mode 100644 index 0000000..26e3ab7 --- /dev/null +++ b/patches/nobara/0001-Set-amdgpu.ppfeaturemask-0xffffffff-as-default.patch @@ -0,0 +1,25 @@ +From 9179080ffaaf1d438db6e0a5a37bdf8dafe233a6 Mon Sep 17 00:00:00 2001 +From: Thomas Crider +Date: Mon, 27 Nov 2023 16:13:13 -0500 +Subject: [PATCH] Set amdgpu.ppfeaturemask=0xffffffff as default + +--- + drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c +index e06009966..4e791eb8f 100644 +--- a/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c ++++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c +@@ -158,7 +158,7 @@ bool enforce_isolation; + * OverDrive(bit 14) disabled by default + * GFX DCS(bit 19) disabled by default + */ +-uint amdgpu_pp_feature_mask = 0xfff7bfff; ++uint amdgpu_pp_feature_mask = 0xffffffff; + uint amdgpu_force_long_training; + int amdgpu_lbpw = -1; + int amdgpu_compute_multipipe = -1; +-- +2.43.0 + diff --git a/patches/nobara/0001-add-acpi_call.patch b/patches/nobara/0001-add-acpi_call.patch new file mode 100644 index 0000000..b0a185a --- /dev/null +++ b/patches/nobara/0001-add-acpi_call.patch @@ -0,0 +1,506 @@ +From 3f14226e2e90dba5d72c106da29e1876eb7b88ff Mon Sep 17 00:00:00 2001 +From: Denis +Date: Thu, 28 Sep 2023 03:40:53 +0200 +Subject: [PATCH] add acpi_call + +--- + drivers/platform/x86/Kconfig | 5 + + drivers/platform/x86/Makefile | 4 + + drivers/platform/x86/acpi_call.c | 449 +++++++++++++++++++++++++++++++ + 3 files changed, 458 insertions(+) + create mode 100644 drivers/platform/x86/acpi_call.c + +diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig +index 49c2c4cd8d00..fde791e51261 100644 +--- a/drivers/platform/x86/Kconfig ++++ b/drivers/platform/x86/Kconfig +@@ -170,6 +170,11 @@ config ACER_WIRELESS + If you choose to compile this driver as a module the module will be + called acer-wireless. + ++config ACPI_CALL ++ tristate "acpi_call module" ++ help ++ This embeds acpi_call module into the kernel ++ + config ACER_WMI + tristate "Acer WMI Laptop Extras" + depends on BACKLIGHT_CLASS_DEVICE +diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile +index 52dfdf574ac2..1e434fcb8273 100644 +--- a/drivers/platform/x86/Makefile ++++ b/drivers/platform/x86/Makefile +@@ -4,10 +4,14 @@ + # x86 Platform-Specific Drivers + # + ++# ACPI calls ++ + # Windows Management Interface + obj-$(CONFIG_ACPI_WMI) += wmi.o + obj-$(CONFIG_WMI_BMOF) += wmi-bmof.o + ++obj-$(CONFIG_ACPI_CALL) += acpi_call.o ++ + # WMI drivers + obj-$(CONFIG_HUAWEI_WMI) += huawei-wmi.o + obj-$(CONFIG_MXM_WMI) += mxm-wmi.o +diff --git a/drivers/platform/x86/acpi_call.c b/drivers/platform/x86/acpi_call.c +new file mode 100644 +index 000000000000..d7bc238e16da +--- /dev/null ++++ b/drivers/platform/x86/acpi_call.c +@@ -0,0 +1,449 @@ ++/* Copyright (c) 2010: Michal Kottman */ ++ ++#define BUILDING_ACPICA ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 14, 0) ++#include ++#endif ++#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 17, 0) ++#include ++#else ++#include ++#endif ++ ++MODULE_LICENSE("GPL"); ++ ++/* Uncomment the following line to enable debug messages */ ++/* ++#define DEBUG ++*/ ++ ++#define BUFFER_SIZE 4096 ++#define INPUT_BUFFER_SIZE (2 * BUFFER_SIZE) ++#define MAX_ACPI_ARGS 16 ++ ++#if LINUX_VERSION_CODE >= KERNEL_VERSION(3, 10, 0) ++#define HAVE_PROC_CREATE ++#endif ++ ++extern struct proc_dir_entry *acpi_root_dir; ++ ++static char input_buffer[INPUT_BUFFER_SIZE]; ++static char result_buffer[BUFFER_SIZE]; ++static char not_called_message[11] = "not called"; ++ ++static u8 temporary_buffer[BUFFER_SIZE]; ++ ++static size_t get_avail_bytes(void) { ++ return BUFFER_SIZE - strlen(result_buffer); ++} ++static char *get_buffer_end(void) { ++ return result_buffer + strlen(result_buffer); ++} ++ ++/** Appends the contents of an acpi_object to the result buffer ++@param result An acpi object holding result data ++@returns 0 if the result could fully be saved, a higher value otherwise ++*/ ++static int acpi_result_to_string(union acpi_object *result) { ++ if (result->type == ACPI_TYPE_INTEGER) { ++ snprintf(get_buffer_end(), get_avail_bytes(), ++ "0x%x", (int)result->integer.value); ++ } else if (result->type == ACPI_TYPE_STRING) { ++ snprintf(get_buffer_end(), get_avail_bytes(), ++ "\"%*s\"", result->string.length, result->string.pointer); ++ } else if (result->type == ACPI_TYPE_BUFFER) { ++ int i; ++ // do not store more than data if it does not fit. The first element is ++ // just 4 chars, but there is also two bytes from the curly brackets ++ int show_values = min((size_t)result->buffer.length, get_avail_bytes() / 6); ++ ++ snprintf(get_buffer_end(), get_avail_bytes(), "{"); ++ for (i = 0; i < show_values; i++) ++ sprintf(get_buffer_end(), ++ i == 0 ? "0x%02x" : ", 0x%02x", result->buffer.pointer[i]); ++ ++ if (result->buffer.length > show_values) { ++ // if data was truncated, show a trailing comma if there is space ++ snprintf(get_buffer_end(), get_avail_bytes(), ","); ++ return 1; ++ } else { ++ // in case show_values == 0, but the buffer is too small to hold ++ // more values (i.e. the buffer cannot have anything more than "{") ++ snprintf(get_buffer_end(), get_avail_bytes(), "}"); ++ } ++ } else if (result->type == ACPI_TYPE_PACKAGE) { ++ int i; ++ snprintf(get_buffer_end(), get_avail_bytes(), "["); ++ for (i=0; ipackage.count; i++) { ++ if (i > 0) ++ snprintf(get_buffer_end(), get_avail_bytes(), ", "); ++ ++ // abort if there is no more space available ++ if (!get_avail_bytes() || acpi_result_to_string(&result->package.elements[i])) ++ return 1; ++ } ++ snprintf(get_buffer_end(), get_avail_bytes(), "]"); ++ } else { ++ snprintf(get_buffer_end(), get_avail_bytes(), ++ "Object type 0x%x\n", result->type); ++ } ++ ++ // return 0 if there are still bytes available, 1 otherwise ++ return !get_avail_bytes(); ++} ++ ++/** ++@param method The full name of ACPI method to call ++@param argc The number of parameters ++@param argv A pre-allocated array of arguments of type acpi_object ++*/ ++static void do_acpi_call(const char * method, int argc, union acpi_object *argv) ++{ ++ acpi_status status; ++ acpi_handle handle; ++ struct acpi_object_list arg; ++ struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL }; ++ ++#ifdef DEBUG ++ printk(KERN_INFO "acpi_call: Calling %s\n", method); ++#endif ++ ++ // get the handle of the method, must be a fully qualified path ++ status = acpi_get_handle(NULL, (acpi_string) method, &handle); ++ ++ if (ACPI_FAILURE(status)) ++ { ++ snprintf(result_buffer, BUFFER_SIZE, "Error: %s", acpi_format_exception(status)); ++ printk(KERN_ERR "acpi_call: Cannot get handle: %s\n", result_buffer); ++ return; ++ } ++ ++ // prepare parameters ++ arg.count = argc; ++ arg.pointer = argv; ++ ++ // call the method ++ status = acpi_evaluate_object(handle, NULL, &arg, &buffer); ++ if (ACPI_FAILURE(status)) ++ { ++ snprintf(result_buffer, BUFFER_SIZE, "Error: %s", acpi_format_exception(status)); ++ printk(KERN_ERR "acpi_call: Method call failed: %s\n", result_buffer); ++ return; ++ } ++ ++ // reset the result buffer ++ *result_buffer = '\0'; ++ acpi_result_to_string(buffer.pointer); ++ kfree(buffer.pointer); ++ ++#ifdef DEBUG ++ printk(KERN_INFO "acpi_call: Call successful: %s\n", result_buffer); ++#endif ++} ++ ++/** Decodes 2 hex characters to an u8 int ++*/ ++u8 decodeHex(char *hex) { ++ char buf[3] = { hex[0], hex[1], 0}; ++ return (u8) simple_strtoul(buf, NULL, 16); ++} ++ ++/** Parses method name and arguments ++@param input Input string to be parsed. Modified in the process. ++@param nargs Set to number of arguments parsed (output) ++@param args ++*/ ++static char *parse_acpi_args(char *input, int *nargs, union acpi_object **args) ++{ ++ char *s = input; ++ int i; ++ ++ *nargs = 0; ++ *args = NULL; ++ ++ // the method name is separated from the arguments by a space ++ while (*s && *s != ' ') ++ s++; ++ // if no space is found, return 0 arguments ++ if (*s == 0) ++ return input; ++ ++ *args = (union acpi_object *) kmalloc(MAX_ACPI_ARGS * sizeof(union acpi_object), GFP_KERNEL); ++ if (!*args) { ++ printk(KERN_ERR "acpi_call: unable to allocate buffer\n"); ++ return NULL; ++ } ++ ++ while (*s) { ++ if (*s == ' ') { ++ if (*nargs == 0) ++ *s = 0; // change first space to nul ++ ++ *nargs; ++ ++ s; ++ } else { ++ union acpi_object *arg = (*args) + (*nargs - 1); ++ if (*s == '"') { ++ // decode string ++ arg->type = ACPI_TYPE_STRING; ++ arg->string.pointer = ++s; ++ arg->string.length = 0; ++ while (*s && *s++ != '"') ++ arg->string.length ++; ++ // skip the last " ++ if (*s == '"') ++ ++s; ++ } else if (*s == 'b') { ++ // decode buffer - bXXXX ++ char *p = ++s; ++ int len = 0, i; ++ u8 *buf = NULL; ++ ++ while (*p && *p!=' ') ++ p++; ++ ++ len = p - s; ++ if (len % 2 == 1) { ++ printk(KERN_ERR "acpi_call: buffer arg%d is not multiple of 8 bits\n", *nargs); ++ --*nargs; ++ goto err; ++ } ++ len /= 2; ++ ++ buf = (u8*) kmalloc(len, GFP_KERNEL); ++ if (!buf) { ++ printk(KERN_ERR "acpi_call: unable to allocate buffer\n"); ++ --*nargs; ++ goto err; ++ } ++ for (i=0; itype = ACPI_TYPE_BUFFER; ++ arg->buffer.pointer = buf; ++ arg->buffer.length = len; ++ } else if (*s == '{') { ++ // decode buffer - { b1, b2 ...} ++ u8 *buf = temporary_buffer; ++ arg->type = ACPI_TYPE_BUFFER; ++ arg->buffer.pointer = buf; ++ arg->buffer.length = 0; ++ while (*s && *s++ != '}') { ++ if (buf >= temporary_buffer + sizeof(temporary_buffer)) { ++ printk(KERN_ERR "acpi_call: buffer arg%d is truncated because the buffer is full\n", *nargs); ++ // clear remaining arguments ++ while (*s && *s != '}') ++ ++s; ++ break; ++ } ++ else if (*s >= '0' && *s <= '9') { ++ // decode integer into buffer ++ arg->buffer.length ++; ++ if (s[0] == '0' && s[1] == 'x') ++ *buf++ = simple_strtol(s+2, 0, 16); ++ else ++ *buf++ = simple_strtol(s, 0, 10); ++ } ++ // skip until space or comma or '}' ++ while (*s && *s != ' ' && *s != ',' && *s != '}') ++ ++s; ++ } ++ // store the result in new allocated buffer ++ buf = (u8*) kmalloc(arg->buffer.length, GFP_KERNEL); ++ if (!buf) { ++ printk(KERN_ERR "acpi_call: unable to allocate buffer\n"); ++ --*nargs; ++ goto err; ++ } ++ memcpy(buf, temporary_buffer, arg->buffer.length); ++ arg->buffer.pointer = buf; ++ } else { ++ // decode integer, N or 0xN ++ arg->type = ACPI_TYPE_INTEGER; ++ if (s[0] == '0' && s[1] == 'x') { ++ arg->integer.value = simple_strtol(s+2, 0, 16); ++ } else { ++ arg->integer.value = simple_strtol(s, 0, 10); ++ } ++ while (*s && *s != ' ') { ++ ++s; ++ } ++ } ++ } ++ } ++ ++ return input; ++ ++err: ++ for (i=0; i<*nargs; i++) ++ if ((*args)[i].type == ACPI_TYPE_BUFFER && (*args)[i].buffer.pointer) ++ kfree((*args)[i].buffer.pointer); ++ kfree(*args); ++ return NULL; ++} ++ ++/** procfs write callback. Called when writing into /proc/acpi/call. ++*/ ++#ifdef HAVE_PROC_CREATE ++static ssize_t acpi_proc_write( struct file *filp, const char __user *buff, ++ size_t len, loff_t *data ) ++#else ++static int acpi_proc_write( struct file *filp, const char __user *buff, ++ unsigned long len, void *data ) ++#endif ++{ ++ union acpi_object *args; ++ int nargs, i; ++ char *method; ++ ++ memset(input_buffer, 0, INPUT_BUFFER_SIZE); ++ if (len > sizeof(input_buffer) - 1) { ++#ifdef HAVE_PROC_CREATE ++ printk(KERN_ERR "acpi_call: Input too long! (%zu)\n", len); ++#else ++ printk(KERN_ERR "acpi_call: Input too long! (%lu)\n", len); ++#endif ++ return -ENOSPC; ++ } ++ ++ if (copy_from_user( input_buffer, buff, len )) { ++ return -EFAULT; ++ } ++ input_buffer[len] = '\0'; ++ if (input_buffer[len-1] == '\n') ++ input_buffer[len-1] = '\0'; ++ ++ method = parse_acpi_args(input_buffer, &nargs, &args); ++ if (method) { ++ do_acpi_call(method, nargs, args); ++ if (args) { ++ for (i=0; i count) { ++ // user buffer is too small ++ ret = 0; ++ } else if(*off == len + 1) { ++ // we're done ++ ret = 0; ++ result_buffer[0] = '\0'; ++ } else { ++ // output the current result buffer ++ ret = simple_read_from_buffer(buff, count, off, result_buffer, len + 1); ++ *off = ret; ++ } ++ ++ return ret; ++} ++ ++#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 6, 0) ++static struct proc_ops proc_acpi_operations = { ++ .proc_read = acpi_proc_read, ++ .proc_write = acpi_proc_write, ++#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 13, 0) ++ .proc_lseek = default_llseek, ++#endif ++}; ++#else ++static struct file_operations proc_acpi_operations = { ++ .owner = THIS_MODULE, ++ .read = acpi_proc_read, ++ .write = acpi_proc_write, ++}; ++#endif ++ ++#else ++static int acpi_proc_read(char *page, char **start, off_t off, ++ int count, int *eof, void *data) ++{ ++ int len = 0; ++ ++ if (off > 0) { ++ *eof = 1; ++ return 0; ++ } ++ ++ // output the current result buffer ++ len = strlen(result_buffer); ++ memcpy(page, result_buffer, len + 1); ++ ++ // initialize the result buffer for later ++ strcpy(result_buffer, "not called"); ++ ++ return len; ++} ++#endif ++ ++/** module initialization function */ ++static int __init init_acpi_call(void) ++{ ++#ifdef HAVE_PROC_CREATE ++ struct proc_dir_entry *acpi_entry = proc_create("call", ++ 0660, ++ acpi_root_dir, ++ &proc_acpi_operations); ++#else ++ struct proc_dir_entry *acpi_entry = create_proc_entry("call", 0660, acpi_root_dir); ++#endif ++ ++ strcpy(result_buffer, "not called"); ++ ++ if (acpi_entry == NULL) { ++ printk(KERN_ERR "acpi_call: Couldn't create proc entry\n"); ++ return -ENOMEM; ++ } ++ ++#ifndef HAVE_PROC_CREATE ++ acpi_entry->write_proc = acpi_proc_write; ++ acpi_entry->read_proc = acpi_proc_read; ++#endif ++ ++#ifdef DEBUG ++ printk(KERN_INFO "acpi_call: Module loaded successfully\n"); ++#endif ++ ++ return 0; ++} ++ ++static void __exit unload_acpi_call(void) ++{ ++ remove_proc_entry("call", acpi_root_dir); ++ ++#ifdef DEBUG ++ printk(KERN_INFO "acpi_call: Module unloaded successfully\n"); ++#endif ++} ++ ++module_init(init_acpi_call); ++module_exit(unload_acpi_call); +\ No newline at end of file +-- +2.42.0 + diff --git a/patches/nobara/0001-amd-hdr.patch b/patches/nobara/0001-amd-hdr.patch new file mode 100644 index 0000000..030317f --- /dev/null +++ b/patches/nobara/0001-amd-hdr.patch @@ -0,0 +1,2042 @@ +From af60f9afa522f5f337d9b4e24eef1fdcd0ab6c05 Mon Sep 17 00:00:00 2001 +From: Peter Jung +Date: Mon, 11 Sep 2023 14:31:43 +0200 +Subject: [PATCH 1/7] amd-hdr + +Signed-off-by: Peter Jung +--- + drivers/gpu/drm/amd/amdgpu/amdgpu_mode.h | 71 ++ + .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c | 34 +- + .../gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h | 100 +++ + .../amd/display/amdgpu_dm/amdgpu_dm_color.c | 805 ++++++++++++++++-- + .../amd/display/amdgpu_dm/amdgpu_dm_crtc.c | 72 ++ + .../amd/display/amdgpu_dm/amdgpu_dm_plane.c | 224 ++++- + .../amd/display/dc/dcn10/dcn10_cm_common.c | 95 ++- + .../drm/amd/display/dc/dcn30/dcn30_hwseq.c | 37 + + .../drm/amd/display/dc/dcn30/dcn30_hwseq.h | 3 + + .../drm/amd/display/dc/dcn301/dcn301_init.c | 2 +- + .../gpu/drm/amd/display/include/fixed31_32.h | 12 + + drivers/gpu/drm/arm/malidp_crtc.c | 2 +- + drivers/gpu/drm/drm_atomic.c | 1 + + drivers/gpu/drm/drm_atomic_state_helper.c | 1 + + drivers/gpu/drm/drm_property.c | 49 ++ + include/drm/drm_mode_object.h | 2 +- + include/drm/drm_plane.h | 7 + + include/drm/drm_property.h | 6 + + include/uapi/drm/drm_mode.h | 8 + + 19 files changed, 1441 insertions(+), 90 deletions(-) + +diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_mode.h b/drivers/gpu/drm/amd/amdgpu/amdgpu_mode.h +index 32fe05c810c6..84bf501b02f4 100644 +--- a/drivers/gpu/drm/amd/amdgpu/amdgpu_mode.h ++++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_mode.h +@@ -343,6 +343,77 @@ struct amdgpu_mode_info { + int disp_priority; + const struct amdgpu_display_funcs *funcs; + const enum drm_plane_type *plane_type; ++ ++ /* Driver-private color mgmt props */ ++ ++ /* @plane_degamma_lut_property: Plane property to set a degamma LUT to ++ * convert input space before blending. ++ */ ++ struct drm_property *plane_degamma_lut_property; ++ /* @plane_degamma_lut_size_property: Plane property to define the max ++ * size of degamma LUT as supported by the driver (read-only). ++ */ ++ struct drm_property *plane_degamma_lut_size_property; ++ /** ++ * @plane_degamma_tf_property: Plane pre-defined transfer function to ++ * to go from scanout/encoded values to linear values. ++ */ ++ struct drm_property *plane_degamma_tf_property; ++ /** ++ * @plane_hdr_mult_property: ++ */ ++ struct drm_property *plane_hdr_mult_property; ++ ++ struct drm_property *plane_ctm_property; ++ /** ++ * @shaper_lut_property: Plane property to set pre-blending shaper LUT ++ * that converts color content before 3D LUT. ++ */ ++ struct drm_property *plane_shaper_lut_property; ++ /** ++ * @shaper_lut_size_property: Plane property for the size of ++ * pre-blending shaper LUT as supported by the driver (read-only). ++ */ ++ struct drm_property *plane_shaper_lut_size_property; ++ /** ++ * @plane_shaper_tf_property: Plane property to set a predefined ++ * transfer function for pre-blending shaper (before applying 3D LUT) ++ * with or without LUT. ++ */ ++ struct drm_property *plane_shaper_tf_property; ++ /** ++ * @plane_lut3d_property: Plane property for gamma correction using a ++ * 3D LUT (pre-blending). ++ */ ++ struct drm_property *plane_lut3d_property; ++ /** ++ * @plane_degamma_lut_size_property: Plane property to define the max ++ * size of 3D LUT as supported by the driver (read-only). ++ */ ++ struct drm_property *plane_lut3d_size_property; ++ /** ++ * @plane_blend_lut_property: Plane property for output gamma before ++ * blending. Userspace set a blend LUT to convert colors after 3D LUT ++ * conversion. It works as a post-3D LUT 1D LUT, with shaper LUT, they ++ * are sandwiching 3D LUT with two 1D LUT. ++ */ ++ struct drm_property *plane_blend_lut_property; ++ /** ++ * @plane_blend_lut_size_property: Plane property to define the max ++ * size of blend LUT as supported by the driver (read-only). ++ */ ++ struct drm_property *plane_blend_lut_size_property; ++ /** ++ * @plane_blend_tf_property: Plane property to set a predefined ++ * transfer function for pre-blending blend (before applying 3D LUT) ++ * with or without LUT. ++ */ ++ struct drm_property *plane_blend_tf_property; ++ /* @regamma_tf_property: Transfer function for CRTC regamma ++ * (post-blending). Possible values are defined by `enum ++ * amdgpu_transfer_function`. ++ */ ++ struct drm_property *regamma_tf_property; + }; + + #define AMDGPU_MAX_BL_LEVEL 0xFF +diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +index 34f011cedd06..fb3400eff0b6 100644 +--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c ++++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.c +@@ -4021,6 +4021,11 @@ static int amdgpu_dm_mode_config_init(struct amdgpu_device *adev) + return r; + } + ++#ifdef AMD_PRIVATE_COLOR ++ if (amdgpu_dm_create_color_properties(adev)) ++ return -ENOMEM; ++#endif ++ + r = amdgpu_dm_audio_init(adev); + if (r) { + dc_release_state(state->context); +@@ -5093,7 +5098,9 @@ static int fill_dc_plane_attributes(struct amdgpu_device *adev, + * Always set input transfer function, since plane state is refreshed + * every time. + */ +- ret = amdgpu_dm_update_plane_color_mgmt(dm_crtc_state, dc_plane_state); ++ ret = amdgpu_dm_update_plane_color_mgmt(dm_crtc_state, ++ plane_state, ++ dc_plane_state); + if (ret) + return ret; + +@@ -8113,6 +8120,10 @@ static void amdgpu_dm_commit_planes(struct drm_atomic_state *state, + bundle->surface_updates[planes_count].gamma = dc_plane->gamma_correction; + bundle->surface_updates[planes_count].in_transfer_func = dc_plane->in_transfer_func; + bundle->surface_updates[planes_count].gamut_remap_matrix = &dc_plane->gamut_remap_matrix; ++ bundle->surface_updates[planes_count].hdr_mult = dc_plane->hdr_mult; ++ bundle->surface_updates[planes_count].func_shaper = dc_plane->in_shaper_func; ++ bundle->surface_updates[planes_count].lut3d_func = dc_plane->lut3d_func; ++ bundle->surface_updates[planes_count].blend_tf = dc_plane->blend_tf; + } + + amdgpu_dm_plane_fill_dc_scaling_info(dm->adev, new_plane_state, +@@ -8324,6 +8335,10 @@ static void amdgpu_dm_commit_planes(struct drm_atomic_state *state, + &acrtc_state->stream->csc_color_matrix; + bundle->stream_update.out_transfer_func = + acrtc_state->stream->out_transfer_func; ++ bundle->stream_update.lut3d_func = ++ (struct dc_3dlut *) acrtc_state->stream->lut3d_func; ++ bundle->stream_update.func_shaper = ++ (struct dc_transfer_func *) acrtc_state->stream->func_shaper; + } + + acrtc_state->stream->abm_level = acrtc_state->abm_level; +@@ -9512,6 +9527,7 @@ static int dm_update_crtc_state(struct amdgpu_display_manager *dm, + * when a modeset is needed, to ensure it gets reprogrammed. + */ + if (dm_new_crtc_state->base.color_mgmt_changed || ++ dm_old_crtc_state->regamma_tf != dm_new_crtc_state->regamma_tf || + drm_atomic_crtc_needs_modeset(new_crtc_state)) { + ret = amdgpu_dm_update_crtc_color_mgmt(dm_new_crtc_state); + if (ret) +@@ -9579,6 +9595,10 @@ static bool should_reset_plane(struct drm_atomic_state *state, + */ + for_each_oldnew_plane_in_state(state, other, old_other_state, new_other_state, i) { + struct amdgpu_framebuffer *old_afb, *new_afb; ++ struct dm_plane_state *dm_new_other_state, *dm_old_other_state; ++ ++ dm_new_other_state = to_dm_plane_state(new_other_state); ++ dm_old_other_state = to_dm_plane_state(old_other_state); + + if (other->type == DRM_PLANE_TYPE_CURSOR) + continue; +@@ -9615,6 +9635,18 @@ static bool should_reset_plane(struct drm_atomic_state *state, + old_other_state->color_encoding != new_other_state->color_encoding) + return true; + ++ /* HDR/Transfer Function changes. */ ++ if (dm_old_other_state->degamma_tf != dm_new_other_state->degamma_tf || ++ dm_old_other_state->degamma_lut != dm_new_other_state->degamma_lut || ++ dm_old_other_state->hdr_mult != dm_new_other_state->hdr_mult || ++ dm_old_other_state->ctm != dm_new_other_state->ctm || ++ dm_old_other_state->shaper_lut != dm_new_other_state->shaper_lut || ++ dm_old_other_state->shaper_tf != dm_new_other_state->shaper_tf || ++ dm_old_other_state->lut3d != dm_new_other_state->lut3d || ++ dm_old_other_state->blend_lut != dm_new_other_state->blend_lut || ++ dm_old_other_state->blend_tf != dm_new_other_state->blend_tf) ++ return true; ++ + /* Framebuffer checks fall at the end. */ + if (!old_other_state->fb || !new_other_state->fb) + continue; +diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h +index 9e4cc5eeda76..24c87f425afb 100644 +--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h ++++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm.h +@@ -33,6 +33,8 @@ + #include + #include "link_service_types.h" + ++#define AMDGPU_HDR_MULT_DEFAULT (0x100000000LL) ++ + /* + * This file contains the definition for amdgpu_display_manager + * and its API for amdgpu driver's use. +@@ -716,9 +718,91 @@ static inline void amdgpu_dm_set_mst_status(uint8_t *status, + + extern const struct amdgpu_ip_block_version dm_ip_block; + ++enum amdgpu_transfer_function { ++ AMDGPU_TRANSFER_FUNCTION_DEFAULT, ++ AMDGPU_TRANSFER_FUNCTION_SRGB_EOTF, ++ AMDGPU_TRANSFER_FUNCTION_BT709_EOTF, ++ AMDGPU_TRANSFER_FUNCTION_PQ_EOTF, ++ AMDGPU_TRANSFER_FUNCTION_LINEAR, ++ AMDGPU_TRANSFER_FUNCTION_UNITY, ++ AMDGPU_TRANSFER_FUNCTION_GAMMA22_EOTF, ++ AMDGPU_TRANSFER_FUNCTION_GAMMA24_EOTF, ++ AMDGPU_TRANSFER_FUNCTION_GAMMA26_EOTF, ++ AMDGPU_TRANSFER_FUNCTION_SRGB_INV_EOTF, ++ AMDGPU_TRANSFER_FUNCTION_BT709_INV_EOTF, ++ AMDGPU_TRANSFER_FUNCTION_PQ_INV_EOTF, ++ AMDGPU_TRANSFER_FUNCTION_GAMMA22_INV_EOTF, ++ AMDGPU_TRANSFER_FUNCTION_GAMMA24_INV_EOTF, ++ AMDGPU_TRANSFER_FUNCTION_GAMMA26_INV_EOTF, ++ AMDGPU_TRANSFER_FUNCTION_COUNT ++}; ++ + struct dm_plane_state { + struct drm_plane_state base; + struct dc_plane_state *dc_state; ++ ++ /* Plane color mgmt */ ++ /** ++ * @degamma_lut: ++ * ++ * 1D LUT for mapping framebuffer/plane pixel data before sampling or ++ * blending operations. It's usually applied to linearize input space. ++ * The blob (if not NULL) is an array of &struct drm_color_lut. ++ */ ++ struct drm_property_blob *degamma_lut; ++ /** ++ * @degamma_tf: ++ * ++ * Predefined transfer function to tell DC driver the input space to ++ * linearize. ++ */ ++ enum amdgpu_transfer_function degamma_tf; ++ /** ++ * @hdr_mult: ++ * ++ * Multiplier to 'gain' the plane. When PQ is decoded using the fixed ++ * func transfer function to the internal FP16 fb, 1.0 -> 80 nits (on ++ * AMD at least). When sRGB is decoded, 1.0 -> 1.0, obviously. ++ * Therefore, 1.0 multiplier = 80 nits for SDR content. So if you ++ * want, 203 nits for SDR content, pass in (203.0 / 80.0). Format is ++ * S31.32 sign-magnitude. ++ */ ++ __u64 hdr_mult; ++ /** ++ * @ctm: ++ * ++ * Color transformation matrix. See drm_crtc_enable_color_mgmt(). The ++ * blob (if not NULL) is a &struct drm_color_ctm. ++ */ ++ struct drm_property_blob *ctm; ++ /** ++ * @shaper_lut: shaper lookup table blob. The blob (if not NULL) is an ++ * array of &struct drm_color_lut. ++ */ ++ struct drm_property_blob *shaper_lut; ++ /** ++ * @shaper_tf: ++ * ++ * Predefined transfer function to delinearize color space. ++ */ ++ enum amdgpu_transfer_function shaper_tf; ++ /** ++ * @lut3d: 3D lookup table blob. The blob (if not NULL) is an array of ++ * &struct drm_color_lut. ++ */ ++ struct drm_property_blob *lut3d; ++ /** ++ * @blend_lut: blend lut lookup table blob. The blob (if not NULL) is an ++ * array of &struct drm_color_lut. ++ */ ++ struct drm_property_blob *blend_lut; ++ /** ++ * @blend_tf: ++ * ++ * Pre-defined transfer function for converting plane pixel data before ++ * applying blend LUT. ++ */ ++ enum amdgpu_transfer_function blend_tf; + }; + + struct dm_crtc_state { +@@ -743,6 +827,14 @@ struct dm_crtc_state { + struct dc_info_packet vrr_infopacket; + + int abm_level; ++ ++ /** ++ * @regamma_tf: ++ * ++ * Pre-defined transfer function for converting internal FB -> wire ++ * encoding. ++ */ ++ enum amdgpu_transfer_function regamma_tf; + }; + + #define to_dm_crtc_state(x) container_of(x, struct dm_crtc_state, base) +@@ -804,14 +896,22 @@ void amdgpu_dm_update_freesync_caps(struct drm_connector *connector, + + void amdgpu_dm_trigger_timing_sync(struct drm_device *dev); + ++/* 3D LUT max size is 17x17x17 */ ++#define MAX_COLOR_3DLUT_ENTRIES 4913 ++#define MAX_COLOR_3DLUT_BITDEPTH 12 ++int amdgpu_dm_verify_lut3d_size(struct amdgpu_device *adev, ++ struct drm_plane_state *plane_state); ++/* 1D LUT size */ + #define MAX_COLOR_LUT_ENTRIES 4096 + /* Legacy gamm LUT users such as X doesn't like large LUT sizes */ + #define MAX_COLOR_LEGACY_LUT_ENTRIES 256 + + void amdgpu_dm_init_color_mod(void); ++int amdgpu_dm_create_color_properties(struct amdgpu_device *adev); + int amdgpu_dm_verify_lut_sizes(const struct drm_crtc_state *crtc_state); + int amdgpu_dm_update_crtc_color_mgmt(struct dm_crtc_state *crtc); + int amdgpu_dm_update_plane_color_mgmt(struct dm_crtc_state *crtc, ++ struct drm_plane_state *plane_state, + struct dc_plane_state *dc_plane_state); + + void amdgpu_dm_update_connector_after_detect( +diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_color.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_color.c +index a4cb23d059bd..0442eeaa9763 100644 +--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_color.c ++++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_color.c +@@ -72,6 +72,7 @@ + */ + + #define MAX_DRM_LUT_VALUE 0xFFFF ++#define SDR_WHITE_LEVEL_INIT_VALUE 80 + + /** + * amdgpu_dm_init_color_mod - Initialize the color module. +@@ -84,6 +85,213 @@ void amdgpu_dm_init_color_mod(void) + setup_x_points_distribution(); + } + ++#ifdef AMD_PRIVATE_COLOR ++/* Pre-defined Transfer Functions (TF) ++ * ++ * AMD driver supports pre-defined mathematical functions for transferring ++ * between encoded values and optical/linear space. Depending on HW color caps, ++ * ROMs and curves built by the AMD color module support these transforms. ++ * ++ * The driver-specific color implementation exposes properties for pre-blending ++ * degamma TF, shaper TF (before 3D LUT), and blend(dpp.ogam) TF and ++ * post-blending regamma (mpc.ogam) TF. However, only pre-blending degamma ++ * supports ROM curves. AMD color module uses pre-defined coefficients to build ++ * curves for the other blocks. What can be done by each color block is ++ * described by struct dpp_color_capsand struct mpc_color_caps. ++ * ++ * AMD driver-specific color API exposes the following pre-defined transfer ++ * functions: ++ * ++ * - Linear/Unity: linear/identity relationship between pixel value and ++ * luminance value; ++ * - Gamma 2.2, Gamma 2.4, Gamma 2.6: pure gamma functions; ++ * - sRGB: 2.4 gamma with small initial linear section as standardized by IEC ++ * 61966-2-1:1999; ++ * - BT.709 (BT.1886): 2.4 gamma with differences in the dark end of the scale. ++ * Used in HD-TV and standardized by ITU-R BT.1886; ++ * - PQ (Perceptual Quantizer): used for HDR display, allows luminance range ++ * capability of 0 to 10,000 nits; standardized by SMPTE ST 2084. ++ * ++ * In the driver-specific API, color block names attached to TF properties ++ * suggest the intention regarding non-linear encoding pixel's luminance ++ * values. As some newer encodings don't use gamma curve, we make encoding and ++ * decoding explicit by defining an enum list of transfer functions supported ++ * in terms of EOTF and inverse EOTF, where: ++ * ++ * - EOTF (electro-optical transfer function): is the transfer function to go ++ * from the encoded value to an optical (linear) value. De-gamma functions ++ * traditionally do this. ++ * - Inverse EOTF (simply the inverse of the EOTF): is usually intended to go ++ * from an optical/linear space (which might have been used for blending) ++ * back to the encoded values. Gamma functions traditionally do this. ++ */ ++static const char * const ++amdgpu_transfer_function_names[] = { ++ [AMDGPU_TRANSFER_FUNCTION_DEFAULT] = "Default", ++ [AMDGPU_TRANSFER_FUNCTION_LINEAR] = "Linear", ++ [AMDGPU_TRANSFER_FUNCTION_UNITY] = "Unity", ++ [AMDGPU_TRANSFER_FUNCTION_SRGB_EOTF] = "sRGB EOTF", ++ [AMDGPU_TRANSFER_FUNCTION_BT709_EOTF] = "BT.709 EOTF", ++ [AMDGPU_TRANSFER_FUNCTION_PQ_EOTF] = "PQ EOTF", ++ [AMDGPU_TRANSFER_FUNCTION_GAMMA22_EOTF] = "Gamma 2.2 EOTF", ++ [AMDGPU_TRANSFER_FUNCTION_GAMMA24_EOTF] = "Gamma 2.4 EOTF", ++ [AMDGPU_TRANSFER_FUNCTION_GAMMA26_EOTF] = "Gamma 2.6 EOTF", ++ [AMDGPU_TRANSFER_FUNCTION_SRGB_INV_EOTF] = "sRGB inv_EOTF", ++ [AMDGPU_TRANSFER_FUNCTION_BT709_INV_EOTF] = "BT.709 inv_EOTF", ++ [AMDGPU_TRANSFER_FUNCTION_PQ_INV_EOTF] = "PQ inv_EOTF", ++ [AMDGPU_TRANSFER_FUNCTION_GAMMA22_INV_EOTF] = "Gamma 2.2 inv_EOTF", ++ [AMDGPU_TRANSFER_FUNCTION_GAMMA24_INV_EOTF] = "Gamma 2.4 inv_EOTF", ++ [AMDGPU_TRANSFER_FUNCTION_GAMMA26_INV_EOTF] = "Gamma 2.6 inv_EOTF", ++}; ++ ++static const u32 amdgpu_eotf = ++ BIT(AMDGPU_TRANSFER_FUNCTION_SRGB_EOTF) | ++ BIT(AMDGPU_TRANSFER_FUNCTION_BT709_EOTF) | ++ BIT(AMDGPU_TRANSFER_FUNCTION_PQ_EOTF) | ++ BIT(AMDGPU_TRANSFER_FUNCTION_GAMMA22_EOTF) | ++ BIT(AMDGPU_TRANSFER_FUNCTION_GAMMA24_EOTF) | ++ BIT(AMDGPU_TRANSFER_FUNCTION_GAMMA26_EOTF); ++ ++static const u32 amdgpu_inv_eotf = ++ BIT(AMDGPU_TRANSFER_FUNCTION_SRGB_INV_EOTF) | ++ BIT(AMDGPU_TRANSFER_FUNCTION_BT709_INV_EOTF) | ++ BIT(AMDGPU_TRANSFER_FUNCTION_PQ_INV_EOTF) | ++ BIT(AMDGPU_TRANSFER_FUNCTION_GAMMA22_INV_EOTF) | ++ BIT(AMDGPU_TRANSFER_FUNCTION_GAMMA24_INV_EOTF) | ++ BIT(AMDGPU_TRANSFER_FUNCTION_GAMMA26_INV_EOTF); ++ ++static struct drm_property * ++amdgpu_create_tf_property(struct drm_device *dev, ++ const char *name, ++ u32 supported_tf) ++{ ++ u32 transfer_functions = supported_tf | ++ BIT(AMDGPU_TRANSFER_FUNCTION_DEFAULT) | ++ BIT(AMDGPU_TRANSFER_FUNCTION_LINEAR) | ++ BIT(AMDGPU_TRANSFER_FUNCTION_UNITY); ++ struct drm_prop_enum_list enum_list[AMDGPU_TRANSFER_FUNCTION_COUNT]; ++ int i, len; ++ ++ len = 0; ++ for (i = 0; i < AMDGPU_TRANSFER_FUNCTION_COUNT; i++) { ++ if ((transfer_functions & BIT(i)) == 0) ++ continue; ++ ++ enum_list[len].type = i; ++ enum_list[len].name = amdgpu_transfer_function_names[i]; ++ len++; ++ } ++ ++ return drm_property_create_enum(dev, DRM_MODE_PROP_ENUM, ++ name, enum_list, len); ++} ++ ++int ++amdgpu_dm_create_color_properties(struct amdgpu_device *adev) ++{ ++ struct drm_property *prop; ++ ++ prop = drm_property_create(adev_to_drm(adev), ++ DRM_MODE_PROP_BLOB, ++ "AMD_PLANE_DEGAMMA_LUT", 0); ++ if (!prop) ++ return -ENOMEM; ++ adev->mode_info.plane_degamma_lut_property = prop; ++ ++ prop = drm_property_create_range(adev_to_drm(adev), ++ DRM_MODE_PROP_IMMUTABLE, ++ "AMD_PLANE_DEGAMMA_LUT_SIZE", 0, UINT_MAX); ++ if (!prop) ++ return -ENOMEM; ++ adev->mode_info.plane_degamma_lut_size_property = prop; ++ ++ prop = amdgpu_create_tf_property(adev_to_drm(adev), ++ "AMD_PLANE_DEGAMMA_TF", ++ amdgpu_eotf); ++ if (!prop) ++ return -ENOMEM; ++ adev->mode_info.plane_degamma_tf_property = prop; ++ ++ prop = drm_property_create_range(adev_to_drm(adev), ++ 0, "AMD_PLANE_HDR_MULT", 0, U64_MAX); ++ if (!prop) ++ return -ENOMEM; ++ adev->mode_info.plane_hdr_mult_property = prop; ++ ++ prop = drm_property_create(adev_to_drm(adev), ++ DRM_MODE_PROP_BLOB, ++ "AMD_PLANE_CTM", 0); ++ if (!prop) ++ return -ENOMEM; ++ adev->mode_info.plane_ctm_property = prop; ++ ++ prop = drm_property_create(adev_to_drm(adev), ++ DRM_MODE_PROP_BLOB, ++ "AMD_PLANE_SHAPER_LUT", 0); ++ if (!prop) ++ return -ENOMEM; ++ adev->mode_info.plane_shaper_lut_property = prop; ++ ++ prop = drm_property_create_range(adev_to_drm(adev), ++ DRM_MODE_PROP_IMMUTABLE, ++ "AMD_PLANE_SHAPER_LUT_SIZE", 0, UINT_MAX); ++ if (!prop) ++ return -ENOMEM; ++ adev->mode_info.plane_shaper_lut_size_property = prop; ++ ++ prop = amdgpu_create_tf_property(adev_to_drm(adev), ++ "AMD_PLANE_SHAPER_TF", ++ amdgpu_inv_eotf); ++ if (!prop) ++ return -ENOMEM; ++ adev->mode_info.plane_shaper_tf_property = prop; ++ ++ prop = drm_property_create(adev_to_drm(adev), ++ DRM_MODE_PROP_BLOB, ++ "AMD_PLANE_LUT3D", 0); ++ if (!prop) ++ return -ENOMEM; ++ adev->mode_info.plane_lut3d_property = prop; ++ ++ prop = drm_property_create_range(adev_to_drm(adev), ++ DRM_MODE_PROP_IMMUTABLE, ++ "AMD_PLANE_LUT3D_SIZE", 0, UINT_MAX); ++ if (!prop) ++ return -ENOMEM; ++ adev->mode_info.plane_lut3d_size_property = prop; ++ ++ prop = drm_property_create(adev_to_drm(adev), ++ DRM_MODE_PROP_BLOB, ++ "AMD_PLANE_BLEND_LUT", 0); ++ if (!prop) ++ return -ENOMEM; ++ adev->mode_info.plane_blend_lut_property = prop; ++ ++ prop = drm_property_create_range(adev_to_drm(adev), ++ DRM_MODE_PROP_IMMUTABLE, ++ "AMD_PLANE_BLEND_LUT_SIZE", 0, UINT_MAX); ++ if (!prop) ++ return -ENOMEM; ++ adev->mode_info.plane_blend_lut_size_property = prop; ++ ++ prop = amdgpu_create_tf_property(adev_to_drm(adev), ++ "AMD_PLANE_BLEND_TF", ++ amdgpu_eotf); ++ if (!prop) ++ return -ENOMEM; ++ adev->mode_info.plane_blend_tf_property = prop; ++ ++ prop = amdgpu_create_tf_property(adev_to_drm(adev), ++ "AMD_CRTC_REGAMMA_TF", ++ amdgpu_inv_eotf); ++ if (!prop) ++ return -ENOMEM; ++ adev->mode_info.regamma_tf_property = prop; ++ ++ return 0; ++} ++#endif ++ + /** + * __extract_blob_lut - Extracts the DRM lut and lut size from a blob. + * @blob: DRM color mgmt property blob +@@ -182,7 +390,6 @@ static void __drm_lut_to_dc_gamma(const struct drm_color_lut *lut, + static void __drm_ctm_to_dc_matrix(const struct drm_color_ctm *ctm, + struct fixed31_32 *matrix) + { +- int64_t val; + int i; + + /* +@@ -201,12 +408,33 @@ static void __drm_ctm_to_dc_matrix(const struct drm_color_ctm *ctm, + } + + /* gamut_remap_matrix[i] = ctm[i - floor(i/4)] */ +- val = ctm->matrix[i - (i / 4)]; +- /* If negative, convert to 2's complement. */ +- if (val & (1ULL << 63)) +- val = -(val & ~(1ULL << 63)); ++ matrix[i] = dc_fixpt_from_s3132(ctm->matrix[i - (i / 4)]); ++ } ++} + +- matrix[i].value = val; ++/** ++ * __drm_ctm2_to_dc_matrix - converts a DRM CTM2 to a DC CSC float matrix ++ * @ctm: DRM color transformation matrix ++ * @matrix: DC CSC float matrix ++ * ++ * The matrix needs to be a 3x4 (12 entry) matrix. ++ */ ++static void __drm_ctm2_to_dc_matrix(const struct drm_color_ctm2 *ctm, ++ struct fixed31_32 *matrix) ++{ ++ int i; ++ ++ /* ++ * DRM gives a 3x3 matrix, but DC wants 3x4. Assuming we're operating ++ * with homogeneous coordinates, augment the matrix with 0's. ++ * ++ * The format provided is S31.32, using signed-magnitude representation. ++ * Our fixed31_32 is also S31.32, but is using 2's complement. We have ++ * to convert from signed-magnitude to 2's complement. ++ */ ++ for (i = 0; i < 12; i++) { ++ /* gamut_remap_matrix[i] = ctm[i - floor(i/4)] */ ++ matrix[i] = dc_fixpt_from_s3132(ctm->matrix[i]); + } + } + +@@ -268,16 +496,18 @@ static int __set_output_tf(struct dc_transfer_func *func, + struct calculate_buffer cal_buffer = {0}; + bool res; + +- ASSERT(lut && lut_size == MAX_COLOR_LUT_ENTRIES); +- + cal_buffer.buffer_index = -1; + +- gamma = dc_create_gamma(); +- if (!gamma) +- return -ENOMEM; ++ if (lut_size) { ++ ASSERT(lut && lut_size == MAX_COLOR_LUT_ENTRIES); + +- gamma->num_entries = lut_size; +- __drm_lut_to_dc_gamma(lut, gamma, false); ++ gamma = dc_create_gamma(); ++ if (!gamma) ++ return -ENOMEM; ++ ++ gamma->num_entries = lut_size; ++ __drm_lut_to_dc_gamma(lut, gamma, false); ++ } + + if (func->tf == TRANSFER_FUNCTION_LINEAR) { + /* +@@ -285,27 +515,63 @@ static int __set_output_tf(struct dc_transfer_func *func, + * on top of a linear input. But degamma params can be used + * instead to simulate this. + */ +- gamma->type = GAMMA_CUSTOM; ++ if (gamma) ++ gamma->type = GAMMA_CUSTOM; + res = mod_color_calculate_degamma_params(NULL, func, +- gamma, true); ++ gamma, gamma != NULL); + } else { + /* + * Assume sRGB. The actual mapping will depend on whether the + * input was legacy or not. + */ +- gamma->type = GAMMA_CS_TFM_1D; +- res = mod_color_calculate_regamma_params(func, gamma, false, ++ if (gamma) ++ gamma->type = GAMMA_CS_TFM_1D; ++ res = mod_color_calculate_regamma_params(func, gamma, gamma != NULL, + has_rom, NULL, &cal_buffer); + } + +- dc_gamma_release(&gamma); ++ if (gamma) ++ dc_gamma_release(&gamma); + + return res ? 0 : -ENOMEM; + } + ++static int amdgpu_dm_set_atomic_regamma(struct dc_stream_state *stream, ++ const struct drm_color_lut *regamma_lut, ++ uint32_t regamma_size, bool has_rom, ++ enum dc_transfer_func_predefined tf) ++{ ++ struct dc_transfer_func *out_tf = stream->out_transfer_func; ++ int ret = 0; ++ ++ if (regamma_size || tf != TRANSFER_FUNCTION_LINEAR) { ++ /* CRTC RGM goes into RGM LUT. ++ * ++ * Note: there is no implicit sRGB regamma here. We are using ++ * degamma calculation from color module to calculate the curve ++ * from a linear base. ++ */ ++ out_tf->type = TF_TYPE_DISTRIBUTED_POINTS; ++ out_tf->tf = tf; ++ out_tf->sdr_ref_white_level = SDR_WHITE_LEVEL_INIT_VALUE; ++ ++ ret = __set_output_tf(out_tf, regamma_lut, regamma_size, has_rom); ++ } else { ++ /* ++ * No CRTC RGM means we can just put the block into bypass ++ * since we don't have any plane level adjustments using it. ++ */ ++ out_tf->type = TF_TYPE_BYPASS; ++ out_tf->tf = TRANSFER_FUNCTION_LINEAR; ++ } ++ ++ return ret; ++} ++ + /** + * __set_input_tf - calculates the input transfer function based on expected + * input space. ++ * @caps: dc color capabilities + * @func: transfer function + * @lut: lookup table that defines the color space + * @lut_size: size of respective lut. +@@ -313,27 +579,249 @@ static int __set_output_tf(struct dc_transfer_func *func, + * Returns: + * 0 in case of success. -ENOMEM if fails. + */ +-static int __set_input_tf(struct dc_transfer_func *func, ++static int __set_input_tf(struct dc_color_caps *caps, struct dc_transfer_func *func, + const struct drm_color_lut *lut, uint32_t lut_size) + { + struct dc_gamma *gamma = NULL; + bool res; + +- gamma = dc_create_gamma(); +- if (!gamma) +- return -ENOMEM; ++ if (lut_size) { ++ gamma = dc_create_gamma(); ++ if (!gamma) ++ return -ENOMEM; + +- gamma->type = GAMMA_CUSTOM; +- gamma->num_entries = lut_size; ++ gamma->type = GAMMA_CUSTOM; ++ gamma->num_entries = lut_size; + +- __drm_lut_to_dc_gamma(lut, gamma, false); ++ __drm_lut_to_dc_gamma(lut, gamma, false); ++ } + +- res = mod_color_calculate_degamma_params(NULL, func, gamma, true); +- dc_gamma_release(&gamma); ++ res = mod_color_calculate_degamma_params(caps, func, gamma, gamma != NULL); ++ ++ if (gamma) ++ dc_gamma_release(&gamma); + + return res ? 0 : -ENOMEM; + } + ++static enum dc_transfer_func_predefined ++amdgpu_tf_to_dc_tf(enum amdgpu_transfer_function tf) ++{ ++ switch (tf) ++ { ++ default: ++ case AMDGPU_TRANSFER_FUNCTION_DEFAULT: ++ case AMDGPU_TRANSFER_FUNCTION_LINEAR: ++ return TRANSFER_FUNCTION_LINEAR; ++ case AMDGPU_TRANSFER_FUNCTION_SRGB_EOTF: ++ case AMDGPU_TRANSFER_FUNCTION_SRGB_INV_EOTF: ++ return TRANSFER_FUNCTION_SRGB; ++ case AMDGPU_TRANSFER_FUNCTION_BT709_EOTF: ++ case AMDGPU_TRANSFER_FUNCTION_BT709_INV_EOTF: ++ return TRANSFER_FUNCTION_BT709; ++ case AMDGPU_TRANSFER_FUNCTION_PQ_EOTF: ++ case AMDGPU_TRANSFER_FUNCTION_PQ_INV_EOTF: ++ return TRANSFER_FUNCTION_PQ; ++ case AMDGPU_TRANSFER_FUNCTION_UNITY: ++ return TRANSFER_FUNCTION_UNITY; ++ case AMDGPU_TRANSFER_FUNCTION_GAMMA22_EOTF: ++ case AMDGPU_TRANSFER_FUNCTION_GAMMA22_INV_EOTF: ++ return TRANSFER_FUNCTION_GAMMA22; ++ case AMDGPU_TRANSFER_FUNCTION_GAMMA24_EOTF: ++ case AMDGPU_TRANSFER_FUNCTION_GAMMA24_INV_EOTF: ++ return TRANSFER_FUNCTION_GAMMA24; ++ case AMDGPU_TRANSFER_FUNCTION_GAMMA26_EOTF: ++ case AMDGPU_TRANSFER_FUNCTION_GAMMA26_INV_EOTF: ++ return TRANSFER_FUNCTION_GAMMA26; ++ } ++} ++ ++static void __to_dc_lut3d_color(struct dc_rgb *rgb, ++ const struct drm_color_lut lut, ++ int bit_precision) ++{ ++ rgb->red = drm_color_lut_extract(lut.red, bit_precision); ++ rgb->green = drm_color_lut_extract(lut.green, bit_precision); ++ rgb->blue = drm_color_lut_extract(lut.blue, bit_precision); ++} ++ ++static void __drm_3dlut_to_dc_3dlut(const struct drm_color_lut *lut, ++ uint32_t lut3d_size, ++ struct tetrahedral_params *params, ++ bool use_tetrahedral_9, ++ int bit_depth) ++{ ++ struct dc_rgb *lut0; ++ struct dc_rgb *lut1; ++ struct dc_rgb *lut2; ++ struct dc_rgb *lut3; ++ int lut_i, i; ++ ++ ++ if (use_tetrahedral_9) { ++ lut0 = params->tetrahedral_9.lut0; ++ lut1 = params->tetrahedral_9.lut1; ++ lut2 = params->tetrahedral_9.lut2; ++ lut3 = params->tetrahedral_9.lut3; ++ } else { ++ lut0 = params->tetrahedral_17.lut0; ++ lut1 = params->tetrahedral_17.lut1; ++ lut2 = params->tetrahedral_17.lut2; ++ lut3 = params->tetrahedral_17.lut3; ++ } ++ ++ for (lut_i = 0, i = 0; i < lut3d_size - 4; lut_i++, i += 4) { ++ /* We should consider the 3dlut RGB values are distributed ++ * along four arrays lut0-3 where the first sizes 1229 and the ++ * other 1228. The bit depth supported for 3dlut channel is ++ * 12-bit, but DC also supports 10-bit. ++ * ++ * TODO: improve color pipeline API to enable the userspace set ++ * bit depth and 3D LUT size/stride, as specified by VA-API. ++ */ ++ __to_dc_lut3d_color(&lut0[lut_i], lut[i], bit_depth); ++ __to_dc_lut3d_color(&lut1[lut_i], lut[i + 1], bit_depth); ++ __to_dc_lut3d_color(&lut2[lut_i], lut[i + 2], bit_depth); ++ __to_dc_lut3d_color(&lut3[lut_i], lut[i + 3], bit_depth); ++ } ++ /* lut0 has 1229 points (lut_size/4 + 1) */ ++ __to_dc_lut3d_color(&lut0[lut_i], lut[i], bit_depth); ++} ++ ++/* amdgpu_dm_atomic_lut3d - set DRM 3D LUT to DC stream ++ * @drm_lut3d: DRM CRTC (user) 3D LUT ++ * @drm_lut3d_size: size of 3D LUT ++ * @lut3d: DC 3D LUT ++ * ++ * Map DRM CRTC 3D LUT to DC 3D LUT and all necessary bits to program it ++ * on DCN MPC accordingly. ++ */ ++static void amdgpu_dm_atomic_lut3d(const struct drm_color_lut *drm_lut, ++ uint32_t drm_lut3d_size, ++ struct dc_3dlut *lut) ++{ ++ if (!drm_lut3d_size) { ++ lut->state.bits.initialized = 0; ++ } else { ++ /* Stride and bit depth are not programmable by API yet. ++ * Therefore, only supports 17x17x17 3D LUT (12-bit). ++ */ ++ lut->lut_3d.use_tetrahedral_9 = false; ++ lut->lut_3d.use_12bits = true; ++ lut->state.bits.initialized = 1; ++ __drm_3dlut_to_dc_3dlut(drm_lut, drm_lut3d_size, &lut->lut_3d, ++ lut->lut_3d.use_tetrahedral_9, ++ MAX_COLOR_3DLUT_BITDEPTH); ++ } ++} ++ ++static int amdgpu_dm_atomic_shaper_lut(const struct drm_color_lut *shaper_lut, ++ bool has_rom, ++ enum dc_transfer_func_predefined tf, ++ uint32_t shaper_size, ++ struct dc_transfer_func *func_shaper) ++{ ++ int ret = 0; ++ ++ if (shaper_size || tf != TRANSFER_FUNCTION_LINEAR) { ++ /* If DRM shaper LUT is set, we assume a linear color space ++ * (linearized by DRM degamma 1D LUT or not) ++ */ ++ func_shaper->type = TF_TYPE_DISTRIBUTED_POINTS; ++ func_shaper->tf = tf; ++ func_shaper->sdr_ref_white_level = SDR_WHITE_LEVEL_INIT_VALUE; ++ ++ ret = __set_output_tf(func_shaper, shaper_lut, shaper_size, has_rom); ++ } else { ++ func_shaper->type = TF_TYPE_BYPASS; ++ func_shaper->tf = TRANSFER_FUNCTION_LINEAR; ++ } ++ ++ return ret; ++} ++ ++static int amdgpu_dm_atomic_blend_lut(const struct drm_color_lut *blend_lut, ++ bool has_rom, ++ enum dc_transfer_func_predefined tf, ++ uint32_t blend_size, ++ struct dc_transfer_func *func_blend) ++{ ++ int ret = 0; ++ ++ if (blend_size || tf != TRANSFER_FUNCTION_LINEAR) { ++ /* DRM plane gamma LUT or TF means we are linearizing color ++ * space before blending (similar to degamma programming). As ++ * we don't have hardcoded curve support, or we use AMD color ++ * module to fill the parameters that will be translated to HW ++ * points. ++ */ ++ func_blend->type = TF_TYPE_DISTRIBUTED_POINTS; ++ func_blend->tf = tf; ++ func_blend->sdr_ref_white_level = SDR_WHITE_LEVEL_INIT_VALUE; ++ ++ ret = __set_input_tf(NULL, func_blend, blend_lut, blend_size); ++ } else { ++ func_blend->type = TF_TYPE_BYPASS; ++ func_blend->tf = TRANSFER_FUNCTION_LINEAR; ++ } ++ ++ return ret; ++} ++ ++/* amdgpu_dm_lut3d_size - get expected size according to hw color caps ++ * @adev: amdgpu device ++ * @lut_size: default size ++ * ++ * Return: ++ * lut_size if DC 3D LUT is supported, zero otherwise. ++ */ ++static uint32_t amdgpu_dm_get_lut3d_size(struct amdgpu_device *adev, ++ uint32_t lut_size) ++{ ++ return adev->dm.dc->caps.color.dpp.hw_3d_lut ? lut_size : 0; ++} ++ ++/** ++ * amdgpu_dm_verify_lut3d_size - verifies if 3D LUT is supported and if DRM 3D ++ * LUT matches the hw supported size ++ * @adev: amdgpu device ++ * @crtc_state: the DRM CRTC state ++ * ++ * Verifies if post-blending (MPC) 3D LUT is supported by the HW (DCN 3.0 or ++ * newer) and if the DRM 3D LUT matches the supported size. ++ * ++ * Returns: ++ * 0 on success. -EINVAL if lut size are invalid. ++ */ ++int amdgpu_dm_verify_lut3d_size(struct amdgpu_device *adev, ++ struct drm_plane_state *plane_state) ++{ ++ struct dm_plane_state *dm_plane_state = to_dm_plane_state(plane_state); ++ const struct drm_color_lut *shaper = NULL, *lut3d = NULL; ++ uint32_t exp_size, size; ++ ++ /* shaper LUT is only available if 3D LUT color caps*/ ++ exp_size = amdgpu_dm_get_lut3d_size(adev, MAX_COLOR_LUT_ENTRIES); ++ shaper = __extract_blob_lut(dm_plane_state->shaper_lut, &size); ++ ++ if (shaper && size != exp_size) { ++ drm_dbg(&adev->ddev, ++ "Invalid Shaper LUT size. Should be %u but got %u.\n", ++ exp_size, size); ++ } ++ ++ exp_size = amdgpu_dm_get_lut3d_size(adev, MAX_COLOR_3DLUT_ENTRIES); ++ lut3d = __extract_blob_lut(dm_plane_state->lut3d, &size); ++ ++ if (lut3d && size != exp_size) { ++ drm_dbg(&adev->ddev, "Invalid 3D LUT size. Should be %u but got %u.\n", ++ exp_size, size); ++ return -EINVAL; ++ } ++ ++ return 0; ++} ++ + /** + * amdgpu_dm_verify_lut_sizes - verifies if DRM luts match the hw supported sizes + * @crtc_state: the DRM CRTC state +@@ -401,9 +889,12 @@ int amdgpu_dm_update_crtc_color_mgmt(struct dm_crtc_state *crtc) + const struct drm_color_lut *degamma_lut, *regamma_lut; + uint32_t degamma_size, regamma_size; + bool has_regamma, has_degamma; ++ enum dc_transfer_func_predefined tf = TRANSFER_FUNCTION_LINEAR; + bool is_legacy; + int r; + ++ tf = amdgpu_tf_to_dc_tf(crtc->regamma_tf); ++ + r = amdgpu_dm_verify_lut_sizes(&crtc->base); + if (r) + return r; +@@ -440,26 +931,22 @@ int amdgpu_dm_update_crtc_color_mgmt(struct dm_crtc_state *crtc) + stream->out_transfer_func->type = TF_TYPE_DISTRIBUTED_POINTS; + stream->out_transfer_func->tf = TRANSFER_FUNCTION_SRGB; + ++ /* Note: although we pass has_rom as parameter here, we never ++ * actually use ROM because the color module only takes the ROM ++ * path if transfer_func->type == PREDEFINED. ++ * ++ * See more in mod_color_calculate_regamma_params() ++ */ + r = __set_legacy_tf(stream->out_transfer_func, regamma_lut, + regamma_size, has_rom); + if (r) + return r; +- } else if (has_regamma) { +- /* If atomic regamma, CRTC RGM goes into RGM LUT. */ +- stream->out_transfer_func->type = TF_TYPE_DISTRIBUTED_POINTS; +- stream->out_transfer_func->tf = TRANSFER_FUNCTION_LINEAR; +- +- r = __set_output_tf(stream->out_transfer_func, regamma_lut, +- regamma_size, has_rom); ++ } else { ++ regamma_size = has_regamma ? regamma_size : 0; ++ r = amdgpu_dm_set_atomic_regamma(stream, regamma_lut, ++ regamma_size, has_rom, tf); + if (r) + return r; +- } else { +- /* +- * No CRTC RGM means we can just put the block into bypass +- * since we don't have any plane level adjustments using it. +- */ +- stream->out_transfer_func->type = TF_TYPE_BYPASS; +- stream->out_transfer_func->tf = TRANSFER_FUNCTION_LINEAR; + } + + /* +@@ -495,20 +982,10 @@ int amdgpu_dm_update_crtc_color_mgmt(struct dm_crtc_state *crtc) + return 0; + } + +-/** +- * amdgpu_dm_update_plane_color_mgmt: Maps DRM color management to DC plane. +- * @crtc: amdgpu_dm crtc state +- * @dc_plane_state: target DC surface +- * +- * Update the underlying dc_stream_state's input transfer function (ITF) in +- * preparation for hardware commit. The transfer function used depends on +- * the preparation done on the stream for color management. +- * +- * Returns: +- * 0 on success. -ENOMEM if mem allocation fails. +- */ +-int amdgpu_dm_update_plane_color_mgmt(struct dm_crtc_state *crtc, +- struct dc_plane_state *dc_plane_state) ++static int ++map_crtc_degamma_to_dc_plane(struct dm_crtc_state *crtc, ++ struct dc_plane_state *dc_plane_state, ++ struct dc_color_caps *caps) + { + const struct drm_color_lut *degamma_lut; + enum dc_transfer_func_predefined tf = TRANSFER_FUNCTION_SRGB; +@@ -531,8 +1008,7 @@ int amdgpu_dm_update_plane_color_mgmt(struct dm_crtc_state *crtc, + °amma_size); + ASSERT(degamma_size == MAX_COLOR_LUT_ENTRIES); + +- dc_plane_state->in_transfer_func->type = +- TF_TYPE_DISTRIBUTED_POINTS; ++ dc_plane_state->in_transfer_func->type = TF_TYPE_DISTRIBUTED_POINTS; + + /* + * This case isn't fully correct, but also fairly +@@ -564,11 +1040,11 @@ int amdgpu_dm_update_plane_color_mgmt(struct dm_crtc_state *crtc, + dc_plane_state->in_transfer_func->tf = + TRANSFER_FUNCTION_LINEAR; + +- r = __set_input_tf(dc_plane_state->in_transfer_func, ++ r = __set_input_tf(caps, dc_plane_state->in_transfer_func, + degamma_lut, degamma_size); + if (r) + return r; +- } else if (crtc->cm_is_degamma_srgb) { ++ } else { + /* + * For legacy gamma support we need the regamma input + * in linear space. Assume that the input is sRGB. +@@ -577,14 +1053,213 @@ int amdgpu_dm_update_plane_color_mgmt(struct dm_crtc_state *crtc, + dc_plane_state->in_transfer_func->tf = tf; + + if (tf != TRANSFER_FUNCTION_SRGB && +- !mod_color_calculate_degamma_params(NULL, +- dc_plane_state->in_transfer_func, NULL, false)) ++ !mod_color_calculate_degamma_params(caps, ++ dc_plane_state->in_transfer_func, ++ NULL, false)) ++ return -ENOMEM; ++ } ++ ++ return 0; ++} ++ ++static int ++__set_dm_plane_degamma(struct drm_plane_state *plane_state, ++ struct dc_plane_state *dc_plane_state, ++ struct dc_color_caps *color_caps) ++{ ++ struct dm_plane_state *dm_plane_state = to_dm_plane_state(plane_state); ++ const struct drm_color_lut *degamma_lut; ++ enum amdgpu_transfer_function tf = AMDGPU_TRANSFER_FUNCTION_DEFAULT; ++ uint32_t degamma_size; ++ bool has_degamma_lut; ++ int ret; ++ ++ degamma_lut = __extract_blob_lut(dm_plane_state->degamma_lut, ++ °amma_size); ++ ++ has_degamma_lut = degamma_lut && ++ !__is_lut_linear(degamma_lut, degamma_size); ++ ++ tf = dm_plane_state->degamma_tf; ++ ++ /* If we don't have plane degamma LUT nor TF to set on DC, we have ++ * nothing to do here, return. ++ */ ++ if (!has_degamma_lut && tf == AMDGPU_TRANSFER_FUNCTION_DEFAULT) ++ return -EINVAL; ++ ++ dc_plane_state->in_transfer_func->tf = amdgpu_tf_to_dc_tf(tf); ++ ++ if (has_degamma_lut) { ++ ASSERT(degamma_size == MAX_COLOR_LUT_ENTRIES); ++ ++ dc_plane_state->in_transfer_func->type = ++ TF_TYPE_DISTRIBUTED_POINTS; ++ ++ ret = __set_input_tf(color_caps, dc_plane_state->in_transfer_func, ++ degamma_lut, degamma_size); ++ if (ret) ++ return ret; ++ } else { ++ dc_plane_state->in_transfer_func->type = ++ TF_TYPE_PREDEFINED; ++ ++ if (!mod_color_calculate_degamma_params(color_caps, ++ dc_plane_state->in_transfer_func, NULL, false)) + return -ENOMEM; +- } else { +- /* ...Otherwise we can just bypass the DGM block. */ +- dc_plane_state->in_transfer_func->type = TF_TYPE_BYPASS; +- dc_plane_state->in_transfer_func->tf = TRANSFER_FUNCTION_LINEAR; ++ } ++ return 0; ++} ++ ++static int ++amdgpu_dm_plane_set_color_properties(struct drm_plane_state *plane_state, ++ struct dc_plane_state *dc_plane_state, ++ struct dc_color_caps *color_caps) ++{ ++ struct dm_plane_state *dm_plane_state = to_dm_plane_state(plane_state); ++ enum amdgpu_transfer_function shaper_tf = AMDGPU_TRANSFER_FUNCTION_DEFAULT; ++ enum amdgpu_transfer_function blend_tf = AMDGPU_TRANSFER_FUNCTION_DEFAULT; ++ const struct drm_color_lut *shaper_lut, *lut3d, *blend_lut; ++ uint32_t shaper_size, lut3d_size, blend_size; ++ int ret; ++ ++ /* We have nothing to do here, return */ ++ if (!plane_state->color_mgmt_changed) ++ return 0; ++ ++ dc_plane_state->hdr_mult = dc_fixpt_from_s3132(dm_plane_state->hdr_mult); ++ ++ shaper_lut = __extract_blob_lut(dm_plane_state->shaper_lut, &shaper_size); ++ shaper_size = shaper_lut != NULL ? shaper_size : 0; ++ shaper_tf = dm_plane_state->shaper_tf; ++ lut3d = __extract_blob_lut(dm_plane_state->lut3d, &lut3d_size); ++ lut3d_size = lut3d != NULL ? lut3d_size : 0; ++ ++ amdgpu_dm_atomic_lut3d(lut3d, lut3d_size, dc_plane_state->lut3d_func); ++ ret = amdgpu_dm_atomic_shaper_lut(shaper_lut, false, ++ amdgpu_tf_to_dc_tf(shaper_tf), ++ shaper_size, ++ dc_plane_state->in_shaper_func); ++ if (ret) { ++ drm_dbg_kms(plane_state->plane->dev, ++ "setting plane %d shaper LUT failed.\n", ++ plane_state->plane->index); ++ ++ return ret; ++ } ++ ++ blend_tf = dm_plane_state->blend_tf; ++ blend_lut = __extract_blob_lut(dm_plane_state->blend_lut, &blend_size); ++ blend_size = blend_lut != NULL ? blend_size : 0; ++ ++ ret = amdgpu_dm_atomic_blend_lut(blend_lut, false, ++ amdgpu_tf_to_dc_tf(blend_tf), ++ blend_size, dc_plane_state->blend_tf); ++ if (ret) { ++ drm_dbg_kms(plane_state->plane->dev, ++ "setting plane %d gamma lut failed.\n", ++ plane_state->plane->index); ++ ++ return ret; + } + + return 0; + } ++ ++/** ++ * amdgpu_dm_update_plane_color_mgmt: Maps DRM color management to DC plane. ++ * @crtc: amdgpu_dm crtc state ++ * @plane_state: DRM plane state ++ * @dc_plane_state: target DC surface ++ * ++ * Update the underlying dc_stream_state's input transfer function (ITF) in ++ * preparation for hardware commit. The transfer function used depends on ++ * the preparation done on the stream for color management. ++ * ++ * Returns: ++ * 0 on success. -ENOMEM if mem allocation fails. ++ */ ++int amdgpu_dm_update_plane_color_mgmt(struct dm_crtc_state *crtc, ++ struct drm_plane_state *plane_state, ++ struct dc_plane_state *dc_plane_state) ++{ ++ struct amdgpu_device *adev = drm_to_adev(crtc->base.state->dev); ++ struct dm_plane_state *dm_plane_state = to_dm_plane_state(plane_state); ++ struct drm_color_ctm2 *ctm = NULL; ++ struct dc_color_caps *color_caps = NULL; ++ bool has_crtc_cm_degamma; ++ int ret; ++ ++ ret = amdgpu_dm_verify_lut3d_size(adev, plane_state); ++ if (ret) { ++ drm_dbg_driver(&adev->ddev, "amdgpu_dm_verify_lut3d_size() failed\n"); ++ return ret; ++ } ++ ++ if (dc_plane_state->ctx && dc_plane_state->ctx->dc) ++ color_caps = &dc_plane_state->ctx->dc->caps.color; ++ ++ /* Initially, we can just bypass the DGM block. */ ++ dc_plane_state->in_transfer_func->type = TF_TYPE_BYPASS; ++ dc_plane_state->in_transfer_func->tf = TRANSFER_FUNCTION_LINEAR; ++ ++ /* After, we start to update values according to color props */ ++ has_crtc_cm_degamma = (crtc->cm_has_degamma || crtc->cm_is_degamma_srgb); ++ ++ ret = __set_dm_plane_degamma(plane_state, dc_plane_state, color_caps); ++ if (ret == -ENOMEM) ++ return ret; ++ ++ /* We only have one degamma block available (pre-blending) for the ++ * whole color correction pipeline, so that we can't actually perform ++ * plane and CRTC degamma at the same time. Explicitly reject atomic ++ * updates when userspace sets both plane and CRTC degamma properties. ++ */ ++ if (has_crtc_cm_degamma && ret != -EINVAL){ ++ drm_dbg_kms(crtc->base.crtc->dev, ++ "doesn't support plane and CRTC degamma at the same time\n"); ++ return -EINVAL; ++ } ++ ++ /* If we are here, it means we don't have plane degamma settings, check ++ * if we have CRTC degamma waiting for mapping to pre-blending degamma ++ * block ++ */ ++ if (has_crtc_cm_degamma) { ++ /* AMD HW doesn't have post-blending degamma caps. When DRM ++ * CRTC atomic degamma is set, we maps it to DPP degamma block ++ * (pre-blending) or, on legacy gamma, we use DPP degamma to ++ * linearize (implicit degamma) from sRGB/BT709 according to ++ * the input space. ++ */ ++ ret = map_crtc_degamma_to_dc_plane(crtc, dc_plane_state, color_caps); ++ if (ret) ++ return ret; ++ } ++ ++ /* Setup CRTC CTM. */ ++ if (dm_plane_state->ctm) { ++ ctm = (struct drm_color_ctm2 *)dm_plane_state->ctm->data; ++ ++ /* ++ * So far, if we have both plane and CRTC CTM, plane CTM takes ++ * the priority and we discard data for CRTC CTM, as ++ * implemented in dcn10_program_gamut_remap(). However, we ++ * have MPC gamut_remap_matrix from DCN3 family, therefore we ++ * can remap MPC programing of the matrix to MPC block and ++ * provide support for both DPP and MPC matrix at the same ++ * time. ++ */ ++ __drm_ctm2_to_dc_matrix(ctm, dc_plane_state->gamut_remap_matrix.matrix); ++ ++ dc_plane_state->gamut_remap_matrix.enable_remap = true; ++ dc_plane_state->input_csc_color_matrix.enable_adjustment = false; ++ } else { ++ /* Bypass CTM. */ ++ dc_plane_state->gamut_remap_matrix.enable_remap = false; ++ dc_plane_state->input_csc_color_matrix.enable_adjustment = false; ++ } ++ ++ return amdgpu_dm_plane_set_color_properties(plane_state, ++ dc_plane_state, color_caps); ++} +diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.c +index 97b7a0b8a1c2..a05c210754d4 100644 +--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.c ++++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_crtc.c +@@ -260,6 +260,7 @@ static struct drm_crtc_state *dm_crtc_duplicate_state(struct drm_crtc *crtc) + state->freesync_config = cur->freesync_config; + state->cm_has_degamma = cur->cm_has_degamma; + state->cm_is_degamma_srgb = cur->cm_is_degamma_srgb; ++ state->regamma_tf = cur->regamma_tf; + state->crc_skip_count = cur->crc_skip_count; + state->mpo_requested = cur->mpo_requested; + /* TODO Duplicate dc_stream after objects are stream object is flattened */ +@@ -296,6 +297,70 @@ static int amdgpu_dm_crtc_late_register(struct drm_crtc *crtc) + } + #endif + ++#ifdef AMD_PRIVATE_COLOR ++/** ++ * drm_crtc_additional_color_mgmt - enable additional color properties ++ * @crtc: DRM CRTC ++ * ++ * This function lets the driver enable post-blending CRTC regamma transfer ++ * function property in addition to DRM CRTC gamma LUT. Default value means ++ * linear transfer function, which is the default CRTC gamma LUT behaviour ++ * without this property. ++ */ ++static void ++dm_crtc_additional_color_mgmt(struct drm_crtc *crtc) ++{ ++ struct amdgpu_device *adev = drm_to_adev(crtc->dev); ++ ++ if(adev->dm.dc->caps.color.mpc.ogam_ram) ++ drm_object_attach_property(&crtc->base, ++ adev->mode_info.regamma_tf_property, ++ AMDGPU_TRANSFER_FUNCTION_DEFAULT); ++} ++ ++static int ++amdgpu_dm_atomic_crtc_set_property(struct drm_crtc *crtc, ++ struct drm_crtc_state *state, ++ struct drm_property *property, ++ uint64_t val) ++{ ++ struct amdgpu_device *adev = drm_to_adev(crtc->dev); ++ struct dm_crtc_state *acrtc_state = to_dm_crtc_state(state); ++ ++ if (property == adev->mode_info.regamma_tf_property) { ++ if (acrtc_state->regamma_tf != val) { ++ acrtc_state->regamma_tf = val; ++ acrtc_state->base.color_mgmt_changed |= 1; ++ } ++ } else { ++ drm_dbg_atomic(crtc->dev, ++ "[CRTC:%d:%s] unknown property [PROP:%d:%s]]\n", ++ crtc->base.id, crtc->name, ++ property->base.id, property->name); ++ return -EINVAL; ++ } ++ ++ return 0; ++} ++ ++static int ++amdgpu_dm_atomic_crtc_get_property(struct drm_crtc *crtc, ++ const struct drm_crtc_state *state, ++ struct drm_property *property, ++ uint64_t *val) ++{ ++ struct amdgpu_device *adev = drm_to_adev(crtc->dev); ++ struct dm_crtc_state *acrtc_state = to_dm_crtc_state(state); ++ ++ if (property == adev->mode_info.regamma_tf_property) ++ *val = acrtc_state->regamma_tf; ++ else ++ return -EINVAL; ++ ++ return 0; ++} ++#endif ++ + /* Implemented only the options currently available for the driver */ + static const struct drm_crtc_funcs amdgpu_dm_crtc_funcs = { + .reset = dm_crtc_reset_state, +@@ -314,6 +379,10 @@ static const struct drm_crtc_funcs amdgpu_dm_crtc_funcs = { + #if defined(CONFIG_DEBUG_FS) + .late_register = amdgpu_dm_crtc_late_register, + #endif ++#ifdef AMD_PRIVATE_COLOR ++ .atomic_set_property = amdgpu_dm_atomic_crtc_set_property, ++ .atomic_get_property = amdgpu_dm_atomic_crtc_get_property, ++#endif + }; + + static void dm_crtc_helper_disable(struct drm_crtc *crtc) +@@ -489,6 +558,9 @@ int amdgpu_dm_crtc_init(struct amdgpu_display_manager *dm, + + drm_mode_crtc_set_gamma_size(&acrtc->base, MAX_COLOR_LEGACY_LUT_ENTRIES); + ++#ifdef AMD_PRIVATE_COLOR ++ dm_crtc_additional_color_mgmt(&acrtc->base); ++#endif + return 0; + + fail: +diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_plane.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_plane.c +index cc74dd69acf2..17719e15cbe5 100644 +--- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_plane.c ++++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_plane.c +@@ -1333,8 +1333,14 @@ static void dm_drm_plane_reset(struct drm_plane *plane) + amdgpu_state = kzalloc(sizeof(*amdgpu_state), GFP_KERNEL); + WARN_ON(amdgpu_state == NULL); + +- if (amdgpu_state) +- __drm_atomic_helper_plane_reset(plane, &amdgpu_state->base); ++ if (!amdgpu_state) ++ return; ++ ++ __drm_atomic_helper_plane_reset(plane, &amdgpu_state->base); ++ amdgpu_state->degamma_tf = AMDGPU_TRANSFER_FUNCTION_DEFAULT; ++ amdgpu_state->hdr_mult = AMDGPU_HDR_MULT_DEFAULT; ++ amdgpu_state->shaper_tf = AMDGPU_TRANSFER_FUNCTION_DEFAULT; ++ amdgpu_state->blend_tf = AMDGPU_TRANSFER_FUNCTION_DEFAULT; + } + + static struct drm_plane_state * +@@ -1354,6 +1360,22 @@ dm_drm_plane_duplicate_state(struct drm_plane *plane) + dc_plane_state_retain(dm_plane_state->dc_state); + } + ++ if (dm_plane_state->degamma_lut) ++ drm_property_blob_get(dm_plane_state->degamma_lut); ++ if (dm_plane_state->ctm) ++ drm_property_blob_get(dm_plane_state->ctm); ++ if (dm_plane_state->shaper_lut) ++ drm_property_blob_get(dm_plane_state->shaper_lut); ++ if (dm_plane_state->lut3d) ++ drm_property_blob_get(dm_plane_state->lut3d); ++ if (dm_plane_state->blend_lut) ++ drm_property_blob_get(dm_plane_state->blend_lut); ++ ++ dm_plane_state->degamma_tf = old_dm_plane_state->degamma_tf; ++ dm_plane_state->hdr_mult = old_dm_plane_state->hdr_mult; ++ dm_plane_state->shaper_tf = old_dm_plane_state->shaper_tf; ++ dm_plane_state->blend_tf = old_dm_plane_state->blend_tf; ++ + return &dm_plane_state->base; + } + +@@ -1421,12 +1443,203 @@ static void dm_drm_plane_destroy_state(struct drm_plane *plane, + { + struct dm_plane_state *dm_plane_state = to_dm_plane_state(state); + ++ if (dm_plane_state->degamma_lut) ++ drm_property_blob_put(dm_plane_state->degamma_lut); ++ if (dm_plane_state->ctm) ++ drm_property_blob_put(dm_plane_state->ctm); ++ if (dm_plane_state->lut3d) ++ drm_property_blob_put(dm_plane_state->lut3d); ++ if (dm_plane_state->shaper_lut) ++ drm_property_blob_put(dm_plane_state->shaper_lut); ++ if (dm_plane_state->blend_lut) ++ drm_property_blob_put(dm_plane_state->blend_lut); ++ + if (dm_plane_state->dc_state) + dc_plane_state_release(dm_plane_state->dc_state); + + drm_atomic_helper_plane_destroy_state(plane, state); + } + ++#ifdef AMD_PRIVATE_COLOR ++static void ++dm_atomic_plane_attach_color_mgmt_properties(struct amdgpu_display_manager *dm, ++ struct drm_plane *plane) ++{ ++ struct amdgpu_mode_info mode_info = dm->adev->mode_info; ++ struct dpp_color_caps dpp_color_caps = dm->dc->caps.color.dpp; ++ ++ /* Check HW color pipeline capabilities for DPP (pre-blending) before expose*/ ++ if (dpp_color_caps.dgam_ram || dpp_color_caps.gamma_corr) { ++ drm_object_attach_property(&plane->base, ++ mode_info.plane_degamma_lut_property, 0); ++ drm_object_attach_property(&plane->base, ++ mode_info.plane_degamma_lut_size_property, ++ MAX_COLOR_LUT_ENTRIES); ++ drm_object_attach_property(&plane->base, ++ dm->adev->mode_info.plane_degamma_tf_property, ++ AMDGPU_TRANSFER_FUNCTION_DEFAULT); ++ } ++ /* HDR MULT is always available */ ++ drm_object_attach_property(&plane->base, ++ dm->adev->mode_info.plane_hdr_mult_property, ++ AMDGPU_HDR_MULT_DEFAULT); ++ ++ /* Only enable plane CTM if both DPP and MPC gamut remap is available. */ ++ if (dm->dc->caps.color.mpc.gamut_remap) ++ drm_object_attach_property(&plane->base, ++ dm->adev->mode_info.plane_ctm_property, 0); ++ ++ if (dpp_color_caps.hw_3d_lut) { ++ drm_object_attach_property(&plane->base, ++ mode_info.plane_shaper_lut_property, 0); ++ drm_object_attach_property(&plane->base, ++ mode_info.plane_shaper_lut_size_property, ++ MAX_COLOR_LUT_ENTRIES); ++ drm_object_attach_property(&plane->base, ++ mode_info.plane_shaper_tf_property, ++ AMDGPU_TRANSFER_FUNCTION_DEFAULT); ++ drm_object_attach_property(&plane->base, ++ mode_info.plane_lut3d_property, 0); ++ drm_object_attach_property(&plane->base, ++ mode_info.plane_lut3d_size_property, ++ MAX_COLOR_3DLUT_ENTRIES); ++ } ++ ++ if (dpp_color_caps.ogam_ram) { ++ drm_object_attach_property(&plane->base, ++ mode_info.plane_blend_lut_property, 0); ++ drm_object_attach_property(&plane->base, ++ mode_info.plane_blend_lut_size_property, ++ MAX_COLOR_LUT_ENTRIES); ++ drm_object_attach_property(&plane->base, ++ mode_info.plane_blend_tf_property, ++ AMDGPU_TRANSFER_FUNCTION_DEFAULT); ++ } ++} ++ ++static int ++dm_atomic_plane_set_property(struct drm_plane *plane, ++ struct drm_plane_state *state, ++ struct drm_property *property, ++ uint64_t val) ++{ ++ struct dm_plane_state *dm_plane_state = to_dm_plane_state(state); ++ struct amdgpu_device *adev = drm_to_adev(plane->dev); ++ bool replaced = false; ++ int ret; ++ ++ if (property == adev->mode_info.plane_degamma_lut_property) { ++ ret = drm_property_replace_blob_from_id(plane->dev, ++ &dm_plane_state->degamma_lut, ++ val, ++ -1, sizeof(struct drm_color_lut), ++ &replaced); ++ dm_plane_state->base.color_mgmt_changed |= replaced; ++ return ret; ++ } else if (property == adev->mode_info.plane_degamma_tf_property) { ++ if (dm_plane_state->degamma_tf != val) { ++ dm_plane_state->degamma_tf = val; ++ dm_plane_state->base.color_mgmt_changed = 1; ++ } ++ } else if (property == adev->mode_info.plane_hdr_mult_property) { ++ if (dm_plane_state->hdr_mult != val) { ++ dm_plane_state->hdr_mult = val; ++ dm_plane_state->base.color_mgmt_changed = 1; ++ } ++ } else if (property == adev->mode_info.plane_ctm_property) { ++ ret = drm_property_replace_blob_from_id(plane->dev, ++ &dm_plane_state->ctm, ++ val, ++ sizeof(struct drm_color_ctm2), -1, ++ &replaced); ++ dm_plane_state->base.color_mgmt_changed |= replaced; ++ return ret; ++ } else if (property == adev->mode_info.plane_shaper_lut_property) { ++ ret = drm_property_replace_blob_from_id(plane->dev, ++ &dm_plane_state->shaper_lut, ++ val, -1, ++ sizeof(struct drm_color_lut), ++ &replaced); ++ dm_plane_state->base.color_mgmt_changed |= replaced; ++ return ret; ++ } else if (property == adev->mode_info.plane_shaper_tf_property) { ++ if (dm_plane_state->shaper_tf != val) { ++ dm_plane_state->shaper_tf = val; ++ dm_plane_state->base.color_mgmt_changed = 1; ++ } ++ } else if (property == adev->mode_info.plane_lut3d_property) { ++ ret = drm_property_replace_blob_from_id(plane->dev, ++ &dm_plane_state->lut3d, ++ val, -1, ++ sizeof(struct drm_color_lut), ++ &replaced); ++ dm_plane_state->base.color_mgmt_changed |= replaced; ++ return ret; ++ } else if (property == adev->mode_info.plane_blend_lut_property) { ++ ret = drm_property_replace_blob_from_id(plane->dev, ++ &dm_plane_state->blend_lut, ++ val, -1, ++ sizeof(struct drm_color_lut), ++ &replaced); ++ dm_plane_state->base.color_mgmt_changed |= replaced; ++ return ret; ++ } else if (property == adev->mode_info.plane_blend_tf_property) { ++ if (dm_plane_state->blend_tf != val) { ++ dm_plane_state->blend_tf = val; ++ dm_plane_state->base.color_mgmt_changed = 1; ++ } ++ } else { ++ drm_dbg_atomic(plane->dev, ++ "[PLANE:%d:%s] unknown property [PROP:%d:%s]]\n", ++ plane->base.id, plane->name, ++ property->base.id, property->name); ++ return -EINVAL; ++ } ++ ++ return 0; ++} ++ ++static int ++dm_atomic_plane_get_property(struct drm_plane *plane, ++ const struct drm_plane_state *state, ++ struct drm_property *property, ++ uint64_t *val) ++{ ++ struct dm_plane_state *dm_plane_state = to_dm_plane_state(state); ++ struct amdgpu_device *adev = drm_to_adev(plane->dev); ++ ++ if (property == adev->mode_info.plane_degamma_lut_property) { ++ *val = (dm_plane_state->degamma_lut) ? ++ dm_plane_state->degamma_lut->base.id : 0; ++ } else if (property == adev->mode_info.plane_degamma_tf_property) { ++ *val = dm_plane_state->degamma_tf; ++ } else if (property == adev->mode_info.plane_hdr_mult_property) { ++ *val = dm_plane_state->hdr_mult; ++ } else if (property == adev->mode_info.plane_ctm_property) { ++ *val = (dm_plane_state->ctm) ? ++ dm_plane_state->ctm->base.id : 0; ++ } else if (property == adev->mode_info.plane_shaper_lut_property) { ++ *val = (dm_plane_state->shaper_lut) ? ++ dm_plane_state->shaper_lut->base.id : 0; ++ } else if (property == adev->mode_info.plane_shaper_tf_property) { ++ *val = dm_plane_state->shaper_tf; ++ } else if (property == adev->mode_info.plane_lut3d_property) { ++ *val = (dm_plane_state->lut3d) ? ++ dm_plane_state->lut3d->base.id : 0; ++ } else if (property == adev->mode_info.plane_blend_lut_property) { ++ *val = (dm_plane_state->blend_lut) ? ++ dm_plane_state->blend_lut->base.id : 0; ++ } else if (property == adev->mode_info.plane_blend_tf_property) { ++ *val = dm_plane_state->blend_tf; ++ ++ } else { ++ return -EINVAL; ++ } ++ ++ return 0; ++} ++#endif ++ + static const struct drm_plane_funcs dm_plane_funcs = { + .update_plane = drm_atomic_helper_update_plane, + .disable_plane = drm_atomic_helper_disable_plane, +@@ -1435,6 +1648,10 @@ static const struct drm_plane_funcs dm_plane_funcs = { + .atomic_duplicate_state = dm_drm_plane_duplicate_state, + .atomic_destroy_state = dm_drm_plane_destroy_state, + .format_mod_supported = dm_plane_format_mod_supported, ++#ifdef AMD_PRIVATE_COLOR ++ .atomic_set_property = dm_atomic_plane_set_property, ++ .atomic_get_property = dm_atomic_plane_get_property, ++#endif + }; + + int amdgpu_dm_plane_init(struct amdgpu_display_manager *dm, +@@ -1514,6 +1731,9 @@ int amdgpu_dm_plane_init(struct amdgpu_display_manager *dm, + + drm_plane_helper_add(plane, &dm_plane_helper_funcs); + ++#ifdef AMD_PRIVATE_COLOR ++ dm_atomic_plane_attach_color_mgmt_properties(dm, plane); ++#endif + /* Create (reset) the plane state */ + if (plane->funcs->reset) + plane->funcs->reset(plane); +diff --git a/drivers/gpu/drm/amd/display/dc/dcn10/dcn10_cm_common.c b/drivers/gpu/drm/amd/display/dc/dcn10/dcn10_cm_common.c +index 3538973bd0c6..04b2e04b68f3 100644 +--- a/drivers/gpu/drm/amd/display/dc/dcn10/dcn10_cm_common.c ++++ b/drivers/gpu/drm/amd/display/dc/dcn10/dcn10_cm_common.c +@@ -349,20 +349,37 @@ bool cm_helper_translate_curve_to_hw_format(struct dc_context *ctx, + * segment is from 2^-10 to 2^1 + * There are less than 256 points, for optimization + */ +- seg_distr[0] = 3; +- seg_distr[1] = 4; +- seg_distr[2] = 4; +- seg_distr[3] = 4; +- seg_distr[4] = 4; +- seg_distr[5] = 4; +- seg_distr[6] = 4; +- seg_distr[7] = 4; +- seg_distr[8] = 4; +- seg_distr[9] = 4; +- seg_distr[10] = 1; +- +- region_start = -10; +- region_end = 1; ++ if (output_tf->tf == TRANSFER_FUNCTION_LINEAR) { ++ seg_distr[0] = 0; /* 2 */ ++ seg_distr[1] = 1; /* 4 */ ++ seg_distr[2] = 2; /* 4 */ ++ seg_distr[3] = 3; /* 8 */ ++ seg_distr[4] = 4; /* 16 */ ++ seg_distr[5] = 5; /* 32 */ ++ seg_distr[6] = 6; /* 64 */ ++ seg_distr[7] = 7; /* 128 */ ++ ++ region_start = -8; ++ region_end = 1; ++ } else { ++ seg_distr[0] = 3; /* 8 */ ++ seg_distr[1] = 4; /* 16 */ ++ seg_distr[2] = 4; ++ seg_distr[3] = 4; ++ seg_distr[4] = 4; ++ seg_distr[5] = 4; ++ seg_distr[6] = 4; ++ seg_distr[7] = 4; ++ seg_distr[8] = 4; ++ seg_distr[9] = 4; ++ seg_distr[10] = 1; /* 2 */ ++ /* total = 8*16 + 8 + 64 + 2 = */ ++ ++ region_start = -10; ++ region_end = 1; ++ } ++ ++ + } + + for (i = region_end - region_start; i < MAX_REGIONS_NUMBER ; i++) +@@ -375,16 +392,56 @@ bool cm_helper_translate_curve_to_hw_format(struct dc_context *ctx, + + j = 0; + for (k = 0; k < (region_end - region_start); k++) { +- increment = NUMBER_SW_SEGMENTS / (1 << seg_distr[k]); ++ /* ++ * We're using an ugly-ish hack here. Our HW allows for ++ * 256 segments per region but SW_SEGMENTS is 16. ++ * SW_SEGMENTS has some undocumented relationship to ++ * the number of points in the tf_pts struct, which ++ * is 512, unlike what's suggested TRANSFER_FUNC_POINTS. ++ * ++ * In order to work past this dilemma we'll scale our ++ * increment by (1 << 4) and then do the inverse (1 >> 4) ++ * when accessing the elements in tf_pts. ++ * ++ * TODO: find a better way using SW_SEGMENTS and ++ * TRANSFER_FUNC_POINTS definitions ++ */ ++ increment = (NUMBER_SW_SEGMENTS << 4) / (1 << seg_distr[k]); + start_index = (region_start + k + MAX_LOW_POINT) * + NUMBER_SW_SEGMENTS; +- for (i = start_index; i < start_index + NUMBER_SW_SEGMENTS; ++ for (i = (start_index << 4); i < (start_index << 4) + (NUMBER_SW_SEGMENTS << 4); + i += increment) { ++ struct fixed31_32 in_plus_one, in; ++ struct fixed31_32 value, red_value, green_value, blue_value; ++ uint32_t t = i & 0xf; ++ + if (j == hw_points - 1) + break; +- rgb_resulted[j].red = output_tf->tf_pts.red[i]; +- rgb_resulted[j].green = output_tf->tf_pts.green[i]; +- rgb_resulted[j].blue = output_tf->tf_pts.blue[i]; ++ ++ in_plus_one = output_tf->tf_pts.red[(i >> 4) + 1]; ++ in = output_tf->tf_pts.red[i >> 4]; ++ value = dc_fixpt_sub(in_plus_one, in); ++ value = dc_fixpt_shr(dc_fixpt_mul_int(value, t), 4); ++ value = dc_fixpt_add(in, value); ++ red_value = value; ++ ++ in_plus_one = output_tf->tf_pts.green[(i >> 4) + 1]; ++ in = output_tf->tf_pts.green[i >> 4]; ++ value = dc_fixpt_sub(in_plus_one, in); ++ value = dc_fixpt_shr(dc_fixpt_mul_int(value, t), 4); ++ value = dc_fixpt_add(in, value); ++ green_value = value; ++ ++ in_plus_one = output_tf->tf_pts.blue[(i >> 4) + 1]; ++ in = output_tf->tf_pts.blue[i >> 4]; ++ value = dc_fixpt_sub(in_plus_one, in); ++ value = dc_fixpt_shr(dc_fixpt_mul_int(value, t), 4); ++ value = dc_fixpt_add(in, value); ++ blue_value = value; ++ ++ rgb_resulted[j].red = red_value; ++ rgb_resulted[j].green = green_value; ++ rgb_resulted[j].blue = blue_value; + j++; + } + } +diff --git a/drivers/gpu/drm/amd/display/dc/dcn30/dcn30_hwseq.c b/drivers/gpu/drm/amd/display/dc/dcn30/dcn30_hwseq.c +index 255713ec29bb..fce9b33c0f88 100644 +--- a/drivers/gpu/drm/amd/display/dc/dcn30/dcn30_hwseq.c ++++ b/drivers/gpu/drm/amd/display/dc/dcn30/dcn30_hwseq.c +@@ -186,6 +186,43 @@ bool dcn30_set_input_transfer_func(struct dc *dc, + return result; + } + ++void dcn30_program_gamut_remap(struct pipe_ctx *pipe_ctx) ++{ ++ int i = 0; ++ struct dpp_grph_csc_adjustment dpp_adjust; ++ struct mpc_grph_gamut_adjustment mpc_adjust; ++ int mpcc_id = pipe_ctx->plane_res.hubp->inst; ++ struct mpc *mpc = pipe_ctx->stream_res.opp->ctx->dc->res_pool->mpc; ++ ++ memset(&dpp_adjust, 0, sizeof(dpp_adjust)); ++ dpp_adjust.gamut_adjust_type = GRAPHICS_GAMUT_ADJUST_TYPE_BYPASS; ++ ++ if (pipe_ctx->plane_state && ++ pipe_ctx->plane_state->gamut_remap_matrix.enable_remap == true) { ++ dpp_adjust.gamut_adjust_type = GRAPHICS_GAMUT_ADJUST_TYPE_SW; ++ for (i = 0; i < CSC_TEMPERATURE_MATRIX_SIZE; i++) ++ dpp_adjust.temperature_matrix[i] = ++ pipe_ctx->plane_state->gamut_remap_matrix.matrix[i]; ++ } ++ ++ pipe_ctx->plane_res.dpp->funcs->dpp_set_gamut_remap(pipe_ctx->plane_res.dpp, ++ &dpp_adjust); ++ ++ memset(&mpc_adjust, 0, sizeof(mpc_adjust)); ++ mpc_adjust.gamut_adjust_type = GRAPHICS_GAMUT_ADJUST_TYPE_BYPASS; ++ ++ if (pipe_ctx->top_pipe == NULL) { ++ if (pipe_ctx->stream->gamut_remap_matrix.enable_remap == true) { ++ mpc_adjust.gamut_adjust_type = GRAPHICS_GAMUT_ADJUST_TYPE_SW; ++ for (i = 0; i < CSC_TEMPERATURE_MATRIX_SIZE; i++) ++ mpc_adjust.temperature_matrix[i] = ++ pipe_ctx->stream->gamut_remap_matrix.matrix[i]; ++ } ++ } ++ ++ mpc->funcs->set_gamut_remap(mpc, mpcc_id, &mpc_adjust); ++} ++ + bool dcn30_set_output_transfer_func(struct dc *dc, + struct pipe_ctx *pipe_ctx, + const struct dc_stream_state *stream) +diff --git a/drivers/gpu/drm/amd/display/dc/dcn30/dcn30_hwseq.h b/drivers/gpu/drm/amd/display/dc/dcn30/dcn30_hwseq.h +index ce19c54097f8..e557e2b98618 100644 +--- a/drivers/gpu/drm/amd/display/dc/dcn30/dcn30_hwseq.h ++++ b/drivers/gpu/drm/amd/display/dc/dcn30/dcn30_hwseq.h +@@ -58,6 +58,9 @@ bool dcn30_set_blend_lut(struct pipe_ctx *pipe_ctx, + bool dcn30_set_input_transfer_func(struct dc *dc, + struct pipe_ctx *pipe_ctx, + const struct dc_plane_state *plane_state); ++ ++void dcn30_program_gamut_remap(struct pipe_ctx *pipe_ctx); ++ + bool dcn30_set_output_transfer_func(struct dc *dc, + struct pipe_ctx *pipe_ctx, + const struct dc_stream_state *stream); +diff --git a/drivers/gpu/drm/amd/display/dc/dcn301/dcn301_init.c b/drivers/gpu/drm/amd/display/dc/dcn301/dcn301_init.c +index 61205cdbe2d5..fdbe3d42cd7b 100644 +--- a/drivers/gpu/drm/amd/display/dc/dcn301/dcn301_init.c ++++ b/drivers/gpu/drm/amd/display/dc/dcn301/dcn301_init.c +@@ -33,7 +33,7 @@ + #include "dcn301_init.h" + + static const struct hw_sequencer_funcs dcn301_funcs = { +- .program_gamut_remap = dcn10_program_gamut_remap, ++ .program_gamut_remap = dcn30_program_gamut_remap, + .init_hw = dcn10_init_hw, + .power_down_on_boot = dcn10_power_down_on_boot, + .apply_ctx_to_hw = dce110_apply_ctx_to_hw, +diff --git a/drivers/gpu/drm/amd/display/include/fixed31_32.h b/drivers/gpu/drm/amd/display/include/fixed31_32.h +index d4cf7ead1d87..84da1dd34efd 100644 +--- a/drivers/gpu/drm/amd/display/include/fixed31_32.h ++++ b/drivers/gpu/drm/amd/display/include/fixed31_32.h +@@ -69,6 +69,18 @@ static const struct fixed31_32 dc_fixpt_epsilon = { 1LL }; + static const struct fixed31_32 dc_fixpt_half = { 0x80000000LL }; + static const struct fixed31_32 dc_fixpt_one = { 0x100000000LL }; + ++static inline struct fixed31_32 dc_fixpt_from_s3132(__u64 x) ++{ ++ struct fixed31_32 val; ++ ++ /* If negative, convert to 2's complement. */ ++ if (x & (1ULL << 63)) ++ x = -(x & ~(1ULL << 63)); ++ ++ val.value = x; ++ return val; ++} ++ + /* + * @brief + * Initialization routines +diff --git a/drivers/gpu/drm/arm/malidp_crtc.c b/drivers/gpu/drm/arm/malidp_crtc.c +index dc01c43f6193..d72c22dcf685 100644 +--- a/drivers/gpu/drm/arm/malidp_crtc.c ++++ b/drivers/gpu/drm/arm/malidp_crtc.c +@@ -221,7 +221,7 @@ static int malidp_crtc_atomic_check_ctm(struct drm_crtc *crtc, + + /* + * The size of the ctm is checked in +- * drm_atomic_replace_property_blob_from_id. ++ * drm_property_replace_blob_from_id. + */ + ctm = (struct drm_color_ctm *)state->ctm->data; + for (i = 0; i < ARRAY_SIZE(ctm->matrix); ++i) { +diff --git a/drivers/gpu/drm/drm_atomic.c b/drivers/gpu/drm/drm_atomic.c +index c277b198fa3f..c3df45f90145 100644 +--- a/drivers/gpu/drm/drm_atomic.c ++++ b/drivers/gpu/drm/drm_atomic.c +@@ -733,6 +733,7 @@ static void drm_atomic_plane_print_state(struct drm_printer *p, + drm_get_color_encoding_name(state->color_encoding)); + drm_printf(p, "\tcolor-range=%s\n", + drm_get_color_range_name(state->color_range)); ++ drm_printf(p, "\tcolor_mgmt_changed=%d\n", state->color_mgmt_changed); + + if (plane->funcs->atomic_print_state) + plane->funcs->atomic_print_state(p, state); +diff --git a/drivers/gpu/drm/drm_atomic_state_helper.c b/drivers/gpu/drm/drm_atomic_state_helper.c +index 784e63d70a42..25bb0859fda7 100644 +--- a/drivers/gpu/drm/drm_atomic_state_helper.c ++++ b/drivers/gpu/drm/drm_atomic_state_helper.c +@@ -338,6 +338,7 @@ void __drm_atomic_helper_plane_duplicate_state(struct drm_plane *plane, + state->fence = NULL; + state->commit = NULL; + state->fb_damage_clips = NULL; ++ state->color_mgmt_changed = false; + } + EXPORT_SYMBOL(__drm_atomic_helper_plane_duplicate_state); + +diff --git a/drivers/gpu/drm/drm_property.c b/drivers/gpu/drm/drm_property.c +index dfec479830e4..f72ef6493340 100644 +--- a/drivers/gpu/drm/drm_property.c ++++ b/drivers/gpu/drm/drm_property.c +@@ -751,6 +751,55 @@ bool drm_property_replace_blob(struct drm_property_blob **blob, + } + EXPORT_SYMBOL(drm_property_replace_blob); + ++/** ++ * drm_property_replace_blob_from_id - replace a blob property taking a reference ++ * @dev: DRM device ++ * @blob: a pointer to the member blob to be replaced ++ * @blob_id: the id of the new blob to replace with ++ * @expected_size: expected size of the blob property ++ * @expected_elem_size: expected size of an element in the blob property ++ * @replaced: if the blob was in fact replaced ++ * ++ * Look up the new blob from id, take its reference, check expected sizes of ++ * the blob and its element and replace the old blob by the new one. Advertise ++ * if the replacement operation was successful. ++ * ++ * Return: true if the blob was in fact replaced. -EINVAL if the new blob was ++ * not found or sizes don't match. ++ */ ++int drm_property_replace_blob_from_id(struct drm_device *dev, ++ struct drm_property_blob **blob, ++ uint64_t blob_id, ++ ssize_t expected_size, ++ ssize_t expected_elem_size, ++ bool *replaced) ++{ ++ struct drm_property_blob *new_blob = NULL; ++ ++ if (blob_id != 0) { ++ new_blob = drm_property_lookup_blob(dev, blob_id); ++ if (new_blob == NULL) ++ return -EINVAL; ++ ++ if (expected_size > 0 && ++ new_blob->length != expected_size) { ++ drm_property_blob_put(new_blob); ++ return -EINVAL; ++ } ++ if (expected_elem_size > 0 && ++ new_blob->length % expected_elem_size != 0) { ++ drm_property_blob_put(new_blob); ++ return -EINVAL; ++ } ++ } ++ ++ *replaced |= drm_property_replace_blob(blob, new_blob); ++ drm_property_blob_put(new_blob); ++ ++ return 0; ++} ++EXPORT_SYMBOL(drm_property_replace_blob_from_id); ++ + int drm_mode_getblob_ioctl(struct drm_device *dev, + void *data, struct drm_file *file_priv) + { +diff --git a/include/drm/drm_mode_object.h b/include/drm/drm_mode_object.h +index 912f1e415685..08d7a7f0188f 100644 +--- a/include/drm/drm_mode_object.h ++++ b/include/drm/drm_mode_object.h +@@ -60,7 +60,7 @@ struct drm_mode_object { + void (*free_cb)(struct kref *kref); + }; + +-#define DRM_OBJECT_MAX_PROPERTY 24 ++#define DRM_OBJECT_MAX_PROPERTY 64 + /** + * struct drm_object_properties - property tracking for &drm_mode_object + */ +diff --git a/include/drm/drm_plane.h b/include/drm/drm_plane.h +index 79d62856defb..4f87803b3ea1 100644 +--- a/include/drm/drm_plane.h ++++ b/include/drm/drm_plane.h +@@ -237,6 +237,13 @@ struct drm_plane_state { + + /** @state: backpointer to global drm_atomic_state */ + struct drm_atomic_state *state; ++ ++ /** ++ * @color_mgmt_changed: Color management properties have changed. Used ++ * by the atomic helpers and drivers to steer the atomic commit control ++ * flow. ++ */ ++ bool color_mgmt_changed : 1; + }; + + static inline struct drm_rect +diff --git a/include/drm/drm_property.h b/include/drm/drm_property.h +index 65bc9710a470..082f29156b3e 100644 +--- a/include/drm/drm_property.h ++++ b/include/drm/drm_property.h +@@ -279,6 +279,12 @@ struct drm_property_blob *drm_property_create_blob(struct drm_device *dev, + const void *data); + struct drm_property_blob *drm_property_lookup_blob(struct drm_device *dev, + uint32_t id); ++int drm_property_replace_blob_from_id(struct drm_device *dev, ++ struct drm_property_blob **blob, ++ uint64_t blob_id, ++ ssize_t expected_size, ++ ssize_t expected_elem_size, ++ bool *replaced); + int drm_property_replace_global_blob(struct drm_device *dev, + struct drm_property_blob **replace, + size_t length, +diff --git a/include/uapi/drm/drm_mode.h b/include/uapi/drm/drm_mode.h +index ea1b639bcb28..cea5653e4020 100644 +--- a/include/uapi/drm/drm_mode.h ++++ b/include/uapi/drm/drm_mode.h +@@ -846,6 +846,14 @@ struct drm_color_ctm { + __u64 matrix[9]; + }; + ++struct drm_color_ctm2 { ++ /* ++ * Conversion matrix in S31.32 sign-magnitude ++ * (not two's complement!) format. ++ */ ++ __u64 matrix[12]; ++}; ++ + struct drm_color_lut { + /* + * Values are mapped linearly to 0.0 - 1.0 range, with 0x0 == 0.0 and +-- +2.43.0.rc2 + diff --git a/patches/nobara/0001-hid-asus-nero-patches-rogue.patch b/patches/nobara/0001-hid-asus-nero-patches-rogue.patch new file mode 100644 index 0000000..2ca98bd --- /dev/null +++ b/patches/nobara/0001-hid-asus-nero-patches-rogue.patch @@ -0,0 +1,972 @@ +diff --git a/drivers/hid/hid-asus.c b/drivers/hid/hid-asus.c +index fd61dba88..3220d96fc 100644 +--- a/drivers/hid/hid-asus.c ++++ b/drivers/hid/hid-asus.c +@@ -26,7 +26,9 @@ + #include + #include + #include ++#include + #include ++#include + #include + #include /* For to_usb_interface for T100 touchpad intf check */ + #include +@@ -94,6 +96,435 @@ MODULE_DESCRIPTION("Asus HID Keyboard and TouchPad"); + + #define TRKID_SGN ((TRKID_MAX + 1) >> 1) + ++/* ++ * USB buffers to be used in a control transfer to make the joystick change buttons mode and scancodes ++ * 0 is default (game_mode with back buttons sending F17 and F18 instead of F15 for both as when unconfigured) ++ * 1 is mouse mode: back buttons still are F17 and F18 ++ * 2 is macro mode ++ */ ++static const u8 rc71l_mode_switch_commands[][23][64] = { ++ { ++ { ++ 0x5A, 0xD1, 0x01, 0x01, 0x01, 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 ++ }, ++ { ++ 0x5A, 0xD1, 0x0A, 0x01, 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 ++ }, ++ { ++ 0x5A, 0xD1, 0x02, 0x01, 0x2C, 0x01, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ++ 0x05, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x0A, 0x00, 0x00, 0x00, ++ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x03, 0x8C, 0x88, 0x76, 0x00, ++ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 ++ }, ++ { ++ 0x5A, 0xD1, 0x0A, 0x01, 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 ++ }, ++ { ++ 0x5A, 0xD1, 0x02, 0x02, 0x2C, 0x01, 0x0B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ++ 0x04, 0x00, 0x00, 0x00, 0x00, 0x02, 0x82, 0x23, 0x00, 0x00, 0x00, 0x01, 0x0C, 0x00, 0x00, 0x00, ++ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x02, 0x82, 0x0D, 0x00, 0x00, ++ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 ++ }, ++ { ++ 0x5A, 0xD1, 0x0A, 0x01, 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 ++ }, ++ { ++ 0x5A, 0xD1, 0x02, 0x03, 0x2C, 0x01, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ++ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x08, 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 ++ }, ++ { ++ 0x5A, 0xD1, 0x0A, 0x01, 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 ++ }, ++ { ++ 0x5A, 0xD1, 0x02, 0x04, 0x2C, 0x01, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ++ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x06, 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 ++ }, ++ { ++ 0x5A, 0xD1, 0x0A, 0x01, 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 ++ }, ++ { ++ 0x5A, 0xD1, 0x02, 0x05, 0x2C, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ++ 0x05, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x00, 0x00, 0x00, ++ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x02, 0x82, 0x31, 0x00, 0x00, ++ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 ++ }, ++ { ++ 0x5A, 0xD1, 0x0A, 0x01, 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 ++ }, ++ { ++ 0x5A, 0xD1, 0x02, 0x06, 0x2C, 0x01, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ++ 0x04, 0x00, 0x00, 0x00, 0x00, 0x02, 0x82, 0x4D, 0x00, 0x00, 0x00, 0x01, 0x04, 0x00, 0x00, 0x00, ++ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x1E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ++ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 ++ }, ++ { ++ 0x5A, 0xD1, 0x0A, 0x01, 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 ++ }, ++ { ++ 0x5A, 0xD1, 0x02, 0x07, 0x2C, 0x01, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ++ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x12, 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 ++ }, ++ { ++ 0x5A, 0xD1, 0x0A, 0x01, 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 ++ }, ++ { ++ 0x5A, 0xD1, 0x02, 0x08, 0x2C, 0x02, 0x00, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ++ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x30, 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 ++ }, ++ { ++ 0x5A, 0xD1, 0x0A, 0x01, 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 ++ }, ++ { ++ 0x5A, 0xD1, 0x02, 0x09, 0x2C, 0x01, 0x0D, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ++ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x0E, 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 ++ }, ++ { ++ 0x5A, 0xD1, 0x0F, 0x20, 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 ++ }, ++ { ++ 0x5A, 0xD1, 0x06, 0x02, 0x64, 0x64, 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 ++ }, ++ { ++ 0x5A, 0xD1, 0x04, 0x04, 0x00, 0x64, 0x00, 0x64, 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 ++ }, ++ { ++ 0x5A, 0xD1, 0x05, 0x04, 0x00, 0x64, 0x00, 0x64, 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 ++ } ++ }, ++ { ++ { ++ 0x5A, 0xD1, 0x01, 0x01, 0x03, 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 ++ }, ++ { ++ 0x5A, 0xD1, 0x0A, 0x01, 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 ++ }, ++ { ++ 0x5A, 0xD1, 0x02, 0x01, 0x2C, 0x02, 0x00, 0x98, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ++ 0x05, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x99, 0x00, 0x00, ++ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x03, 0x8C, 0x88, 0x76, 0x00, ++ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 ++ }, ++ { ++ 0x5A, 0xD1, 0x0A, 0x01, 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 ++ }, ++ { ++ 0x5A, 0xD1, 0x02, 0x02, 0x2C, 0x02, 0x00, 0x9A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ++ 0x04, 0x00, 0x00, 0x00, 0x00, 0x02, 0x82, 0x23, 0x00, 0x00, 0x00, 0x02, 0x00, 0x9B, 0x00, 0x00, ++ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x02, 0x82, 0x0D, 0x00, 0x00, ++ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 ++ }, ++ { ++ 0x5A, 0xD1, 0x0A, 0x01, 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 ++ }, ++ { ++ 0x5A, 0xD1, 0x02, 0x03, 0x2C, 0x02, 0x00, 0x88, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ++ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, ++ 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 ++ }, ++ { ++ 0x5A, 0xD1, 0x0A, 0x01, 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 ++ }, ++ { ++ 0x5A, 0xD1, 0x02, 0x04, 0x2C, 0x02, 0x00, 0x0D, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ++ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x01, ++ 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 ++ }, ++ { ++ 0x5A, 0xD1, 0x0A, 0x01, 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 ++ }, ++ { ++ 0x5A, 0xD1, 0x02, 0x05, 0x2C, 0x02, 0x00, 0x5A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ++ 0x05, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x76, 0x00, 0x00, ++ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x02, 0x82, 0x31, 0x00, 0x00, ++ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 ++ }, ++ { ++ 0x5A, 0xD1, 0x0A, 0x01, 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 ++ }, ++ { ++ 0x5A, 0xD1, 0x02, 0x06, 0x2C, 0x02, 0x00, 0x97, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ++ 0x04, 0x00, 0x00, 0x00, 0x00, 0x02, 0x82, 0x4D, 0x00, 0x00, 0x00, 0x02, 0x00, 0x96, 0x00, 0x00, ++ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x1E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ++ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 ++ }, ++ { ++ 0x5A, 0xD1, 0x0A, 0x01, 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 ++ }, ++ { ++ 0x5A, 0xD1, 0x02, 0x07, 0x2C, 0x01, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ++ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x12, 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 ++ }, ++ { ++ 0x5A, 0xD1, 0x0A, 0x01, 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 ++ }, ++ { ++ 0x5A, 0xD1, 0x02, 0x08, 0x2C, 0x02, 0x00, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ++ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x30, 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 ++ }, ++ { ++ 0x5A, 0xD1, 0x0A, 0x01, 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 ++ }, ++ { ++ 0x5A, 0xD1, 0x02, 0x09, 0x2C, 0x04, 0x00, 0x00, 0x00, 0x00, 0x02, 0x88, 0x0D, 0x00, 0x00, 0x00, ++ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 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 ++ }, ++ { ++ 0x5A, 0xD1, 0x0F, 0x20, 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 ++ }, ++ { ++ 0x5A, 0xD1, 0x06, 0x02, 0x64, 0x64, 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 ++ }, ++ { ++ 0x5A, 0xD1, 0x04, 0x04, 0x00, 0x64, 0x00, 0x64, 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 ++ }, ++ { ++ 0x5A, 0xD1, 0x05, 0x04, 0x00, 0x64, 0x00, 0x64, 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 ++ } ++ }, ++ { ++ { ++ 0x5A, 0xD1, 0x01, 0x01, 0x01, 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 ++ }, ++ { ++ 0x5A, 0xD1, 0x0A, 0x01, 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 ++ }, ++ { ++ 0x5A, 0xD1, 0x02, 0x01, 0x2C, 0x01, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ++ 0x05, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x0A, 0x00, 0x00, 0x00, ++ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x03, 0x8C, 0x88, 0x76, 0x00, ++ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 ++ }, ++ { ++ 0x5A, 0xD1, 0x0A, 0x01, 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 ++ }, ++ { ++ 0x5A, 0xD1, 0x02, 0x02, 0x2C, 0x01, 0x0B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ++ 0x04, 0x00, 0x00, 0x00, 0x00, 0x02, 0x82, 0x23, 0x00, 0x00, 0x00, 0x01, 0x0C, 0x00, 0x00, 0x00, ++ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x02, 0x82, 0x0D, 0x00, 0x00, ++ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 ++ }, ++ { ++ 0x5A, 0xD1, 0x0A, 0x01, 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 ++ }, ++ { ++ 0x5A, 0xD1, 0x02, 0x03, 0x2C, 0x01, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ++ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x08, 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 ++ }, ++ { ++ 0x5A, 0xD1, 0x0A, 0x01, 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 ++ }, ++ { ++ 0x5A, 0xD1, 0x02, 0x04, 0x2C, 0x01, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ++ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x06, 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 ++ }, ++ { ++ 0x5A, 0xD1, 0x0A, 0x01, 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 ++ }, ++ { ++ 0x5A, 0xD1, 0x02, 0x05, 0x2C, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ++ 0x05, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x00, 0x00, 0x00, ++ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x02, 0x82, 0x31, 0x00, 0x00, ++ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 ++ }, ++ { ++ 0x5A, 0xD1, 0x0A, 0x01, 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 ++ }, ++ { ++ 0x5A, 0xD1, 0x02, 0x06, 0x2C, 0x01, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ++ 0x04, 0x00, 0x00, 0x00, 0x00, 0x02, 0x82, 0x4D, 0x00, 0x00, 0x00, 0x01, 0x04, 0x00, 0x00, 0x00, ++ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x1E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ++ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 ++ }, ++ { ++ 0x5A, 0xD1, 0x0A, 0x01, 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 ++ }, ++ { ++ 0x5A, 0xD1, 0x02, 0x07, 0x2C, 0x01, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ++ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x12, 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 ++ }, ++ { ++ 0x5A, 0xD1, 0x0A, 0x01, 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 ++ }, ++ { ++ 0x5A, 0xD1, 0x02, 0x08, 0x2C, 0x02, 0x00, 0x8E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ++ 0x02, 0x00, 0x8E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x8F, 0x00, 0x00, ++ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x8F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ++ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 ++ }, ++ { ++ 0x5A, 0xD1, 0x0A, 0x01, 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 ++ }, ++ { ++ 0x5A, 0xD1, 0x02, 0x09, 0x2C, 0x01, 0x0D, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ++ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x0E, 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 ++ }, ++ { ++ 0x5A, 0xD1, 0x0F, 0x20, 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 ++ }, ++ { ++ 0x5A, 0xD1, 0x06, 0x02, 0x64, 0x64, 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 ++ }, ++ { ++ 0x5A, 0xD1, 0x04, 0x04, 0x00, 0x64, 0x00, 0x64, 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 ++ }, ++ { ++ 0x5A, 0xD1, 0x05, 0x04, 0x00, 0x64, 0x00, 0x64, 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 ++ } ++ } ++}; ++ + struct asus_kbd_leds { + struct led_classdev cdev; + struct hid_device *hdev; +@@ -103,6 +534,25 @@ struct asus_kbd_leds { + bool removed; + }; + ++enum rc71l_controller_mode { ++ rc71l_gamepad_mode, ++ rc71l_mouse_mode, ++ rc71l_macro_mode, ++}; ++ ++struct asus_rc71l { ++ unsigned int usb_pipe; ++ ++ struct platform_device *mcu_dev; ++ ++ struct mutex mutex; /* Mutex that protects everything below it */ ++ ++ enum rc71l_controller_mode mode; ++ ++ u8 usb_in_buf[32]; ++ u8 usb_out_buf[64]; /* A temporary buffer to hold data that gets sent over USB (must be accessed upon locking the appropriate mutex) */ ++}; ++ + struct asus_touchpad_info { + int max_x; + int max_y; +@@ -127,6 +577,7 @@ struct asus_drvdata { + int battery_stat; + bool battery_in_query; + unsigned long battery_next_query; ++ struct asus_rc71l *rc71l_data; + }; + + static int asus_report_battery(struct asus_drvdata *, u8 *, int); +@@ -189,6 +640,245 @@ static const struct asus_touchpad_info medion_e1239t_tp = { + .report_size = 32 /* 2 byte header + 5 * 5 + 5 byte footer */, + }; + ++/** ++ * This function reads data over the USB device on the ROG Ally. ++ * Unlike outgoing traffic the inbound always performs 32-bytes transfers. ++ * ++ * PRE: ++ * - rc71l internal mutex MUST be locked ++ */ ++static int rc71l_usb_read(struct hid_device * hdev) { ++ struct asus_drvdata *drvdata = (struct asus_drvdata*)hid_get_drvdata(hdev); ++ if (drvdata == NULL) { ++ return -EINVAL; ++ } ++ ++ struct asus_rc71l *rc71l_drvdata = drvdata->rc71l_data; ++ if (rc71l_drvdata == NULL) { ++ return -EINVAL; ++ } ++ ++ struct usb_interface *intf = to_usb_interface(hdev->dev.parent); ++ struct usb_device *dev = interface_to_usbdev(intf); ++ ++ const int retval = usb_control_msg_recv(dev, 0x80, 0x01, 0xa1, 0x035A, 0x0002, (void*)&rc71l_drvdata->usb_in_buf[0], 32, 250, GFP_KERNEL); ++ ++ if (retval < 0) { ++ hid_err(hdev, "Ally read failed performing control read, error %d\n", retval); ++ goto rc71l_usb_read_err; ++ } ++ ++ const char* b = (const u8*)&rc71l_drvdata->usb_in_buf[0]; ++ hid_info(hdev, "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x", ++ b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7], b[8], b[9], ++ b[10], b[11], b[12], b[13], b[14], b[15], b[16], b[17], b[18], b[19], ++ b[20], b[21], b[22], b[23], b[24], b[25], b[26], b[27], b[28], b[29], ++ b[30], b[31] ++ ); ++ ++rc71l_usb_read_err: ++ return retval; ++} ++ ++/** ++ * This function writes a command over the USB device on the ROG Ally. ++ * The ROG Ally accepts 64-bytes long messages as commands: as such at most 64-bytes will be sent ++ * and unused bytes will be zeroed out. ++ * ++ * PRE: ++ * - rc71l internal mutex MUST be locked ++ */ ++static int rc71l_usb_write(struct hid_device * hdev, const void* buf, size_t buf_sz) { ++ struct asus_drvdata *drvdata = (struct asus_drvdata*)hid_get_drvdata(hdev); ++ ++ if (drvdata == NULL) { ++ return -EINVAL; ++ } ++ ++ struct asus_rc71l *rc71l_drvdata = drvdata->rc71l_data; ++ if (rc71l_drvdata == NULL) { ++ return -EINVAL; ++ } ++ ++ struct usb_interface *intf = to_usb_interface(hdev->dev.parent); ++ struct usb_device *dev = interface_to_usbdev(intf); ++ ++ if (buf_sz > 64) { ++ hid_err(hdev, "Bug in the kernel: cannot write more than 64-bytes\n"); ++ ++ return -EINVAL; ++ } ++ ++ // make sure bytes in excess will be zeroes and copy the user-provided buffer ++ memset((void*)&rc71l_drvdata->usb_out_buf[0], 0, 64); ++ memcpy((void*)&rc71l_drvdata->usb_out_buf[0], buf, buf_sz); ++ ++ /* send the data out the bulk port */ ++ const int retval = usb_control_msg(dev, rc71l_drvdata->usb_pipe, 0x09, 0x21, 0x035A, 0x0002, (void*)&rc71l_drvdata->usb_out_buf[0], 64, 250); ++ if (retval < 0) { ++ hid_err(hdev, ++ "Failed submitting control write error %d\n", retval); ++ ++ goto rc71l_usb_write_err; ++ } ++ ++rc71l_usb_write_err: ++ return retval < 0 ? retval : 0; ++} ++ ++static int rc71l_mode_change(struct hid_device * hdev, enum rc71l_controller_mode new_mode) { ++ struct asus_drvdata *drvdata = (struct asus_drvdata*)hid_get_drvdata(hdev); ++ if (drvdata == NULL) { ++ return -EINVAL; ++ } ++ ++ struct asus_rc71l *rc71l_drvdata = drvdata->rc71l_data; ++ if (rc71l_drvdata == NULL) { ++ return -EINVAL; ++ } ++ ++ int ret = 0; ++ ++ size_t packets_group = 0; ++ switch (new_mode) { ++ case rc71l_gamepad_mode: ++ packets_group = 0; ++ break; ++ ++ case rc71l_mouse_mode: ++ packets_group = 1; ++ break; ++ ++ case rc71l_macro_mode: ++ packets_group = 2; ++ break; ++ ++ default: ++ return -EINVAL; ++ } ++ ++ for (int i = 0; (i < 23) && (ret == 0); ++i) { ++ ret = rc71l_usb_write(hdev, (const void*)&rc71l_mode_switch_commands[packets_group][i][0], 64); ++ if (ret > 0) { ++ hid_err(hdev, "Ally controller mode switch %d/23 error %d\n", i, ret); ++ goto rc71l_mode_change_err; ++ } ++ } ++ ++ // controller mode has been switched successfully: change that in driver data ++ if (ret == 0) { ++ hid_info(hdev, "ROG Ally [RC71L] controller mode switch succeeded\n"); ++ rc71l_drvdata->mode = new_mode; ++ } ++ ++rc71l_mode_change_err: ++ return ret; ++} ++ ++static ssize_t __maybe_unused mode_show(struct device *raw_dev, struct device_attribute *attr, char *buf) { ++ struct platform_device *const pdev = to_platform_device(raw_dev); ++ struct hid_device *const hdev = platform_get_drvdata(pdev); ++ if (hdev == NULL) { ++ return -EINVAL; ++ } ++ ++ struct asus_drvdata *const drvdata = (struct asus_drvdata*)hid_get_drvdata(hdev); ++ if (drvdata == NULL) { ++ return -EINVAL; ++ } ++ ++ struct asus_rc71l *const rc71l_drvdata = drvdata->rc71l_data; ++ if (rc71l_drvdata == NULL) { ++ return -EINVAL; ++ } ++ ++ mutex_lock(&rc71l_drvdata->mutex); ++ int current_mode = 0; ++ switch (rc71l_drvdata->mode) { ++ case rc71l_gamepad_mode: ++ current_mode = 0; ++ break; ++ ++ case rc71l_mouse_mode: ++ current_mode = 1; ++ break; ++ ++ case rc71l_macro_mode: ++ current_mode = 2; ++ break; ++ ++ default: ++ mutex_unlock(&rc71l_drvdata->mutex); ++ return -EINVAL; ++ } ++ mutex_unlock(&rc71l_drvdata->mutex); ++ ++ return sysfs_emit(buf, "%d\n", (int)current_mode); ++} ++ ++static ssize_t __maybe_unused mode_store(struct device *raw_dev, struct device_attribute *attr, const char *buf, size_t count) { ++ struct platform_device *const pdev = to_platform_device(raw_dev); ++ struct hid_device *const hdev = platform_get_drvdata(pdev); ++ if (hdev == NULL) { ++ return -EINVAL; ++ } ++ ++ struct asus_drvdata *const drvdata = (struct asus_drvdata*)hid_get_drvdata(hdev); ++ if (drvdata == NULL) { ++ return -EINVAL; ++ } ++ ++ struct asus_rc71l *const rc71l_drvdata = drvdata->rc71l_data; ++ if (rc71l_drvdata == NULL) { ++ return -EINVAL; ++ } ++ ++ int res = -EINVAL; ++ int val = -EINVAL; ++ res = kstrtoint(buf, 0, &val); ++ if (res) ++ return res; ++ ++ switch (val) { ++ case 0: ++ mutex_lock(&rc71l_drvdata->mutex); ++ res = rc71l_mode_change(hdev, rc71l_gamepad_mode); ++ mutex_unlock(&rc71l_drvdata->mutex); ++ break; ++ ++ case 1: ++ mutex_lock(&rc71l_drvdata->mutex); ++ res = rc71l_mode_change(hdev, rc71l_mouse_mode); ++ mutex_unlock(&rc71l_drvdata->mutex); ++ break; ++ ++ case 2: ++ mutex_lock(&rc71l_drvdata->mutex); ++ res = rc71l_mode_change(hdev, rc71l_macro_mode); ++ mutex_unlock(&rc71l_drvdata->mutex); ++ break; ++ ++ default: ++ return -EINVAL; ++ } ++ ++ hid_err(hdev, "Ally controller mode switch to %d mode op result: %d\n", val, res); ++ ++ return count; ++} ++ ++DEVICE_ATTR_RW(mode); ++ ++static struct attribute *rc71l_input_attrs[] = { ++ &dev_attr_mode.attr, ++ NULL ++}; ++ ++static const struct attribute_group mcu_attr_group = { ++ .name = "input", ++ .attrs = rc71l_input_attrs, ++}; ++ + static void asus_report_contact_down(struct asus_drvdata *drvdat, + int toolType, u8 *data) + { +@@ -386,7 +1076,7 @@ static int asus_kbd_set_report(struct hid_device *hdev, u8 *buf, size_t buf_size + unsigned char *dmabuf; + int ret; + +- dmabuf = kmemdup(buf, buf_size, GFP_KERNEL); ++ dmabuf = kmemdup((const void*)buf, buf_size, GFP_KERNEL); + if (!dmabuf) + return -ENOMEM; + +@@ -897,6 +1587,10 @@ static int asus_input_mapping(struct hid_device *hdev, + case 0xb3: asus_map_key_clear(KEY_PROG3); break; /* Fn+Left next aura */ + case 0x6a: asus_map_key_clear(KEY_F13); break; /* Screenpad toggle */ + case 0x4b: asus_map_key_clear(KEY_F14); break; /* Arrows/Pg-Up/Dn toggle */ ++ case 0xa5: asus_map_key_clear(KEY_F15); break; /* ROG Ally left back */ ++ case 0xa6: asus_map_key_clear(KEY_F16); break; /* ROG Ally QAM button */ ++ case 0xa7: asus_map_key_clear(KEY_F17); break; /* ROG Ally ROG long-press */ ++ case 0xa8: asus_map_key_clear(KEY_F18); break; /* ROG Ally ROG long-press-release */ + + + default: +@@ -1000,16 +1694,108 @@ static int asus_start_multitouch(struct hid_device *hdev) + return 0; + } + ++#ifdef CONFIG_PM + static int __maybe_unused asus_reset_resume(struct hid_device *hdev) + { ++ int ret = 0; ++ + struct asus_drvdata *drvdata = hid_get_drvdata(hdev); ++ if (drvdata != NULL) { ++ return -EINVAL; ++ } + + if (drvdata->tp) + return asus_start_multitouch(hdev); + +- return 0; ++ return ret; + } + ++static int __maybe_unused asus_resume(struct hid_device *hdev) ++{ ++ int ret = 0; ++ struct asus_drvdata *drvdata = hid_get_drvdata(hdev); ++/* ++ // Controller mode is kept on device sleep ++ if (dmi_match(DMI_PRODUCT_NAME, "ROG Ally RC71L_RC71L")) ++ { ++ // Apply the joystick mode switch ++ ret = rog_ally_controller_mode_change(hdev, game_mode); ++ ++ hid_err(hdev, "Asus wake, restore controller %d\n", ret); ++ } ++*/ ++ ++ struct asus_rc71l *rc71l_drvdata = drvdata->rc71l_data; ++ if (rc71l_drvdata != NULL) { ++ mutex_lock(&rc71l_drvdata->mutex); ++ ret = rc71l_mode_change(hdev, rc71l_drvdata->mode); ++ mutex_unlock(&rc71l_drvdata->mutex); ++ ++ if (ret < 0) { ++ hid_err(hdev, "ROG Ally [RC71L] failed to reset controller mode: %d\n", ret); ++ goto asus_resume_err; ++ } ++ } ++ ++ ++ /* ++ * On some devices such as the Asus RC71L leds are reset to default after sleep and sysfs attribute will report ++ * something that won't be true: resetting the user-provided value is necessary to maintain coherency and avoid ++ * flashing full brightness leds in face of the user. ++ */ ++ if (drvdata->kbd_backlight) { ++ const u8 buf[] = { FEATURE_KBD_REPORT_ID, 0xba, 0xc5, 0xc4, drvdata->kbd_backlight->cdev.brightness }; ++ ret = asus_kbd_set_report(hdev, buf, sizeof(buf)); ++ if (ret < 0) { ++ hid_err(hdev, "Asus failed to set keyboard backlight: %d\n", ret); ++ goto asus_resume_err; ++ } ++ ++ hid_err(hdev, "Asus ROG Ally asus_reset_resume, leds reset: %d at brightness %d\n", ret, (int)drvdata->kbd_backlight->cdev.brightness); ++ } ++ ++ asus_resume_err: ++ return ret; ++} ++ ++static int __maybe_unused asus_suspend(struct hid_device *hdev, struct pm_message) ++ { ++ struct asus_drvdata *drvdata = hid_get_drvdata(hdev); ++ ++ if (drvdata == NULL) { ++ return 0; ++ } ++ ++ struct usb_interface *intf = to_usb_interface(hdev->dev.parent); ++ struct usb_device *dev = interface_to_usbdev(intf); ++ ++ int ret = 0; ++ ++ if (dmi_match(DMI_PRODUCT_NAME, "ROG Ally RC71L_RC71L")) { ++ // Send the USB ABORT_PIPE command ++ int result = usb_control_msg( ++ dev, usb_sndctrlpipe(dev, 0), USB_REQ_SET_FEATURE, ++ USB_DIR_OUT | USB_TYPE_STANDARD | USB_RECIP_ENDPOINT, ++ USB_ENDPOINT_HALT, 0x02, NULL, 0, 1000); ++ ++ if (result < 0) { ++ printk("USB ABORT_PIPE failed: %d\n", result); ++ } else { ++ printk("USB ABORT_PIPE succeeded\n"); ++ } ++ } ++ ++ struct asus_rc71l *rc71l_drvdata = drvdata->rc71l_data; ++ if (rc71l_drvdata != NULL) { ++ mutex_lock(&rc71l_drvdata->mutex); ++ // TODO: send ABORT_PIPE here ++ mutex_unlock(&rc71l_drvdata->mutex); ++ } ++ ++ return ret; ++} ++#endif ++ + static int asus_probe(struct hid_device *hdev, const struct hid_device_id *id) + { + int ret; +@@ -1021,6 +1807,8 @@ static int asus_probe(struct hid_device *hdev, const struct hid_device_id *id) + return -ENOMEM; + } + ++ drvdata->rc71l_data = NULL; ++ + hid_set_drvdata(hdev, drvdata); + + drvdata->quirks = id->driver_data; +@@ -1109,6 +1897,51 @@ static int asus_probe(struct hid_device *hdev, const struct hid_device_id *id) + goto err_stop_hw; + } + ++ if ((dmi_match(DMI_PRODUCT_NAME, "ROG Ally RC71L_RC71L")) && (hdev->rsize > 9) && (hdev->rdesc[7] == 0x85) && (hdev->rdesc[8] == 0x5a)) ++ { ++ drvdata->rc71l_data = devm_kzalloc(&hdev->dev, sizeof(*drvdata->rc71l_data), GFP_KERNEL); ++ if (drvdata->rc71l_data == NULL) { ++ hid_err(hdev, "Can't alloc Asus ROG Ally [RC71L] descriptor\n"); ++ ret = -ENOMEM; ++ goto err_stop_hw; ++ } ++ ++ mutex_init(&drvdata->rc71l_data->mutex); ++ ++ struct usb_interface *intf = to_usb_interface(hdev->dev.parent); ++ struct usb_device *dev = interface_to_usbdev(intf); ++ ++ // default controller mode ++ drvdata->rc71l_data->mode = rc71l_gamepad_mode; ++ ++ // usb_device and endpoint ++ drvdata->rc71l_data->usb_pipe = usb_sndctrlpipe(dev, 0); ++ ++ // apply the default controller mode ++ mutex_lock(&drvdata->rc71l_data->mutex); ++ ret = rc71l_mode_change(hdev, drvdata->rc71l_data->mode); ++ mutex_unlock(&drvdata->rc71l_data->mutex); ++ ++ if (ret < 0) { ++ hid_err(hdev, "Asus ROG Ally [RC71L] error setting the default controller mode: %d\n", ret); ++ goto err_stop_hw; ++ } ++ ++ drvdata->rc71l_data->mcu_dev = platform_device_register_simple("asus-mcu", 0, NULL, 0); ++ if (IS_ERR(drvdata->rc71l_data->mcu_dev)) { ++ hid_err(hdev, "Error registering MCU platform device: %ld\n", PTR_ERR(drvdata->rc71l_data->mcu_dev)); ++ goto err_stop_hw; ++ } ++ ++ platform_set_drvdata(drvdata->rc71l_data->mcu_dev, hdev); ++ ++ ret = devm_device_add_group(&drvdata->rc71l_data->mcu_dev->dev, &mcu_attr_group); ++ if (ret != 0) { ++ platform_device_unregister(drvdata->rc71l_data->mcu_dev); ++ goto err_stop_hw; ++ } ++ } ++ + if (drvdata->tp) { + drvdata->input->name = "Asus TouchPad"; + } else { +@@ -1140,6 +1973,16 @@ static void asus_remove(struct hid_device *hdev) + cancel_work_sync(&drvdata->kbd_backlight->work); + } + ++ struct asus_rc71l *rc71l_drvdata = drvdata->rc71l_data; ++ if (rc71l_drvdata != NULL) { ++ platform_device_unregister(rc71l_drvdata->mcu_dev); ++ ++ mutex_lock(&rc71l_drvdata->mutex); ++ platform_device_unregister(rc71l_drvdata->mcu_dev); ++ // TODO: perform cleanup operations ++ mutex_unlock(&rc71l_drvdata->mutex); ++ } ++ + hid_hw_stop(hdev); + } + +@@ -1258,6 +2101,9 @@ static const struct hid_device_id asus_devices[] = { + { HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK, + USB_DEVICE_ID_ASUSTEK_ROG_NKEY_KEYBOARD3), + QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD }, ++ { HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK, ++ USB_DEVICE_ID_ASUSTEK_ROG_NKEY_ALLY), ++ QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD }, + { HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK, + USB_DEVICE_ID_ASUSTEK_ROG_CLAYMORE_II_KEYBOARD), + QUIRK_ROG_CLAYMORE_II_KEYBOARD }, +@@ -1294,6 +2140,8 @@ static struct hid_driver asus_driver = { + .input_configured = asus_input_configured, + #ifdef CONFIG_PM + .reset_resume = asus_reset_resume, ++ .resume = asus_resume, ++ .suspend = asus_suspend, + #endif + .event = asus_event, + .raw_event = asus_raw_event +diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h +index d10ccfa17..213492ee8 100644 +--- a/drivers/hid/hid-ids.h ++++ b/drivers/hid/hid-ids.h +@@ -208,6 +208,7 @@ + #define USB_DEVICE_ID_ASUSTEK_ROG_NKEY_KEYBOARD 0x1866 + #define USB_DEVICE_ID_ASUSTEK_ROG_NKEY_KEYBOARD2 0x19b6 + #define USB_DEVICE_ID_ASUSTEK_ROG_NKEY_KEYBOARD3 0x1a30 ++#define USB_DEVICE_ID_ASUSTEK_ROG_NKEY_ALLY 0x1abe + #define USB_DEVICE_ID_ASUSTEK_ROG_CLAYMORE_II_KEYBOARD 0x196b + #define USB_DEVICE_ID_ASUSTEK_FX503VD_KEYBOARD 0x1869 + +-- +2.43.0 + diff --git a/patches/nobara/OpenRGB.patch b/patches/nobara/OpenRGB.patch new file mode 100644 index 0000000..0ca0401 --- /dev/null +++ b/patches/nobara/OpenRGB.patch @@ -0,0 +1,719 @@ +From 309712fae7491a876359ddda6e4cf8944f454731 Mon Sep 17 00:00:00 2001 +From: GloriousEggroll +Date: Wed, 13 Sep 2023 17:59:59 -0600 +Subject: [PATCH] OpenRGB + +--- + drivers/i2c/busses/Kconfig | 9 + + drivers/i2c/busses/Makefile | 1 + + drivers/i2c/busses/i2c-nct6775.c | 647 +++++++++++++++++++++++++++++++ + drivers/i2c/busses/i2c-piix4.c | 4 +- + 4 files changed, 659 insertions(+), 2 deletions(-) + create mode 100644 drivers/i2c/busses/i2c-nct6775.c + +diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig +index 9cfe8fc50..efc3b0c0b 100644 +--- a/drivers/i2c/busses/Kconfig ++++ b/drivers/i2c/busses/Kconfig +@@ -229,6 +229,15 @@ config I2C_CHT_WC + combined with a FUSB302 Type-C port-controller as such it is advised + to also select CONFIG_TYPEC_FUSB302=m. + ++config I2C_NCT6775 ++ tristate "Nuvoton NCT6775 and compatible SMBus controller" ++ help ++ If you say yes to this option, support will be included for the ++ Nuvoton NCT6775 and compatible SMBus controllers. ++ ++ This driver can also be built as a module. If so, the module ++ will be called i2c-nct6775. ++ + config I2C_NFORCE2 + tristate "Nvidia nForce2, nForce3 and nForce4" + depends on PCI +diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile +index af56fe2c7..76be74584 100644 +--- a/drivers/i2c/busses/Makefile ++++ b/drivers/i2c/busses/Makefile +@@ -20,6 +20,7 @@ obj-$(CONFIG_I2C_CHT_WC) += i2c-cht-wc.o + obj-$(CONFIG_I2C_I801) += i2c-i801.o + obj-$(CONFIG_I2C_ISCH) += i2c-isch.o + obj-$(CONFIG_I2C_ISMT) += i2c-ismt.o ++obj-$(CONFIG_I2C_NCT6775) += i2c-nct6775.o + obj-$(CONFIG_I2C_NFORCE2) += i2c-nforce2.o + obj-$(CONFIG_I2C_NFORCE2_S4985) += i2c-nforce2-s4985.o + obj-$(CONFIG_I2C_NVIDIA_GPU) += i2c-nvidia-gpu.o +diff --git a/drivers/i2c/busses/i2c-nct6775.c b/drivers/i2c/busses/i2c-nct6775.c +new file mode 100644 +index 000000000..0462f0952 +--- /dev/null ++++ b/drivers/i2c/busses/i2c-nct6775.c +@@ -0,0 +1,647 @@ ++/* ++ * i2c-nct6775 - Driver for the SMBus master functionality of ++ * Nuvoton NCT677x Super-I/O chips ++ * ++ * Copyright (C) 2019 Adam Honse ++ * ++ * Derived from nct6775 hwmon driver ++ * Copyright (C) 2012 Guenter Roeck ++ * ++ * This program is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation; either version 2 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program; if not, write to the Free Software ++ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. ++ * ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#define DRVNAME "i2c-nct6775" ++ ++/* Nuvoton SMBus address offsets */ ++#define SMBHSTDAT (0 + nuvoton_nct6793d_smba) ++#define SMBBLKSZ (1 + nuvoton_nct6793d_smba) ++#define SMBHSTCMD (2 + nuvoton_nct6793d_smba) ++#define SMBHSTIDX (3 + nuvoton_nct6793d_smba) //Index field is the Command field on other controllers ++#define SMBHSTCTL (4 + nuvoton_nct6793d_smba) ++#define SMBHSTADD (5 + nuvoton_nct6793d_smba) ++#define SMBHSTERR (9 + nuvoton_nct6793d_smba) ++#define SMBHSTSTS (0xE + nuvoton_nct6793d_smba) ++ ++/* Command register */ ++#define NCT6793D_READ_BYTE 0 ++#define NCT6793D_READ_WORD 1 ++#define NCT6793D_READ_BLOCK 2 ++#define NCT6793D_BLOCK_WRITE_READ_PROC_CALL 3 ++#define NCT6793D_PROC_CALL 4 ++#define NCT6793D_WRITE_BYTE 8 ++#define NCT6793D_WRITE_WORD 9 ++#define NCT6793D_WRITE_BLOCK 10 ++ ++/* Control register */ ++#define NCT6793D_MANUAL_START 128 ++#define NCT6793D_SOFT_RESET 64 ++ ++/* Error register */ ++#define NCT6793D_NO_ACK 32 ++ ++/* Status register */ ++#define NCT6793D_FIFO_EMPTY 1 ++#define NCT6793D_FIFO_FULL 2 ++#define NCT6793D_MANUAL_ACTIVE 4 ++ ++#define NCT6775_LD_SMBUS 0x0B ++ ++/* Other settings */ ++#define MAX_RETRIES 400 ++ ++enum kinds { nct6106, nct6775, nct6776, nct6779, nct6791, nct6792, nct6793, ++ nct6795, nct6796, nct6798 }; ++ ++struct nct6775_sio_data { ++ int sioreg; ++ enum kinds kind; ++}; ++ ++/* used to set data->name = nct6775_device_names[data->sio_kind] */ ++static const char * const nct6775_device_names[] = { ++ "nct6106", ++ "nct6775", ++ "nct6776", ++ "nct6779", ++ "nct6791", ++ "nct6792", ++ "nct6793", ++ "nct6795", ++ "nct6796", ++ "nct6798", ++}; ++ ++static const char * const nct6775_sio_names[] __initconst = { ++ "NCT6106D", ++ "NCT6775F", ++ "NCT6776D/F", ++ "NCT6779D", ++ "NCT6791D", ++ "NCT6792D", ++ "NCT6793D", ++ "NCT6795D", ++ "NCT6796D", ++ "NCT6798D", ++}; ++ ++#define SIO_REG_LDSEL 0x07 /* Logical device select */ ++#define SIO_REG_DEVID 0x20 /* Device ID (2 bytes) */ ++#define SIO_REG_SMBA 0x62 /* SMBus base address register */ ++ ++#define SIO_NCT6106_ID 0xc450 ++#define SIO_NCT6775_ID 0xb470 ++#define SIO_NCT6776_ID 0xc330 ++#define SIO_NCT6779_ID 0xc560 ++#define SIO_NCT6791_ID 0xc800 ++#define SIO_NCT6792_ID 0xc910 ++#define SIO_NCT6793_ID 0xd120 ++#define SIO_NCT6795_ID 0xd350 ++#define SIO_NCT6796_ID 0xd420 ++#define SIO_NCT6798_ID 0xd428 ++#define SIO_ID_MASK 0xFFF0 ++ ++static inline void ++superio_outb(int ioreg, int reg, int val) ++{ ++ outb(reg, ioreg); ++ outb(val, ioreg + 1); ++} ++ ++static inline int ++superio_inb(int ioreg, int reg) ++{ ++ outb(reg, ioreg); ++ return inb(ioreg + 1); ++} ++ ++static inline void ++superio_select(int ioreg, int ld) ++{ ++ outb(SIO_REG_LDSEL, ioreg); ++ outb(ld, ioreg + 1); ++} ++ ++static inline int ++superio_enter(int ioreg) ++{ ++ /* ++ * Try to reserve and for exclusive access. ++ */ ++ if (!request_muxed_region(ioreg, 2, DRVNAME)) ++ return -EBUSY; ++ ++ outb(0x87, ioreg); ++ outb(0x87, ioreg); ++ ++ return 0; ++} ++ ++static inline void ++superio_exit(int ioreg) ++{ ++ outb(0xaa, ioreg); ++ outb(0x02, ioreg); ++ outb(0x02, ioreg + 1); ++ release_region(ioreg, 2); ++} ++ ++/* ++ * ISA constants ++ */ ++ ++#define IOREGION_ALIGNMENT (~7) ++#define IOREGION_LENGTH 2 ++#define ADDR_REG_OFFSET 0 ++#define DATA_REG_OFFSET 1 ++ ++#define NCT6775_REG_BANK 0x4E ++#define NCT6775_REG_CONFIG 0x40 ++ ++static struct i2c_adapter *nct6775_adapter; ++ ++struct i2c_nct6775_adapdata { ++ unsigned short smba; ++}; ++ ++/* Return negative errno on error. */ ++static s32 nct6775_access(struct i2c_adapter * adap, u16 addr, ++ unsigned short flags, char read_write, ++ u8 command, int size, union i2c_smbus_data * data) ++{ ++ struct i2c_nct6775_adapdata *adapdata = i2c_get_adapdata(adap); ++ unsigned short nuvoton_nct6793d_smba = adapdata->smba; ++ int i, len, cnt; ++ union i2c_smbus_data tmp_data; ++ int timeout = 0; ++ ++ tmp_data.word = 0; ++ cnt = 0; ++ len = 0; ++ ++ outb_p(NCT6793D_SOFT_RESET, SMBHSTCTL); ++ ++ switch (size) { ++ case I2C_SMBUS_QUICK: ++ outb_p((addr << 1) | read_write, ++ SMBHSTADD); ++ break; ++ case I2C_SMBUS_BYTE_DATA: ++ tmp_data.byte = data->byte; ++ case I2C_SMBUS_BYTE: ++ outb_p((addr << 1) | read_write, ++ SMBHSTADD); ++ outb_p(command, SMBHSTIDX); ++ if (read_write == I2C_SMBUS_WRITE) { ++ outb_p(tmp_data.byte, SMBHSTDAT); ++ outb_p(NCT6793D_WRITE_BYTE, SMBHSTCMD); ++ } ++ else { ++ outb_p(NCT6793D_READ_BYTE, SMBHSTCMD); ++ } ++ break; ++ case I2C_SMBUS_WORD_DATA: ++ outb_p((addr << 1) | read_write, ++ SMBHSTADD); ++ outb_p(command, SMBHSTIDX); ++ if (read_write == I2C_SMBUS_WRITE) { ++ outb_p(data->word & 0xff, SMBHSTDAT); ++ outb_p((data->word & 0xff00) >> 8, SMBHSTDAT); ++ outb_p(NCT6793D_WRITE_WORD, SMBHSTCMD); ++ } ++ else { ++ outb_p(NCT6793D_READ_WORD, SMBHSTCMD); ++ } ++ break; ++ case I2C_SMBUS_BLOCK_DATA: ++ outb_p((addr << 1) | read_write, ++ SMBHSTADD); ++ outb_p(command, SMBHSTIDX); ++ if (read_write == I2C_SMBUS_WRITE) { ++ len = data->block[0]; ++ if (len == 0 || len > I2C_SMBUS_BLOCK_MAX) ++ return -EINVAL; ++ outb_p(len, SMBBLKSZ); ++ ++ cnt = 1; ++ if (len >= 4) { ++ for (i = cnt; i <= 4; i++) { ++ outb_p(data->block[i], SMBHSTDAT); ++ } ++ ++ len -= 4; ++ cnt += 4; ++ } ++ else { ++ for (i = cnt; i <= len; i++ ) { ++ outb_p(data->block[i], SMBHSTDAT); ++ } ++ ++ len = 0; ++ } ++ ++ outb_p(NCT6793D_WRITE_BLOCK, SMBHSTCMD); ++ } ++ else { ++ return -ENOTSUPP; ++ } ++ break; ++ default: ++ dev_warn(&adap->dev, "Unsupported transaction %d\n", size); ++ return -EOPNOTSUPP; ++ } ++ ++ outb_p(NCT6793D_MANUAL_START, SMBHSTCTL); ++ ++ while ((size == I2C_SMBUS_BLOCK_DATA) && (len > 0)) { ++ if (read_write == I2C_SMBUS_WRITE) { ++ timeout = 0; ++ while ((inb_p(SMBHSTSTS) & NCT6793D_FIFO_EMPTY) == 0) ++ { ++ if(timeout > MAX_RETRIES) ++ { ++ return -ETIMEDOUT; ++ } ++ usleep_range(250, 500); ++ timeout++; ++ } ++ ++ //Load more bytes into FIFO ++ if (len >= 4) { ++ for (i = cnt; i <= (cnt + 4); i++) { ++ outb_p(data->block[i], SMBHSTDAT); ++ } ++ ++ len -= 4; ++ cnt += 4; ++ } ++ else { ++ for (i = cnt; i <= (cnt + len); i++) { ++ outb_p(data->block[i], SMBHSTDAT); ++ } ++ ++ len = 0; ++ } ++ } ++ else { ++ return -ENOTSUPP; ++ } ++ ++ } ++ ++ //wait for manual mode to complete ++ timeout = 0; ++ while ((inb_p(SMBHSTSTS) & NCT6793D_MANUAL_ACTIVE) != 0) ++ { ++ if(timeout > MAX_RETRIES) ++ { ++ return -ETIMEDOUT; ++ } ++ usleep_range(250, 500); ++ timeout++; ++ } ++ ++ if ((inb_p(SMBHSTERR) & NCT6793D_NO_ACK) != 0) { ++ return -ENXIO; ++ } ++ else if ((read_write == I2C_SMBUS_WRITE) || (size == I2C_SMBUS_QUICK)) { ++ return 0; ++ } ++ ++ switch (size) { ++ case I2C_SMBUS_QUICK: ++ case I2C_SMBUS_BYTE_DATA: ++ data->byte = inb_p(SMBHSTDAT); ++ break; ++ case I2C_SMBUS_WORD_DATA: ++ data->word = inb_p(SMBHSTDAT) + (inb_p(SMBHSTDAT) << 8); ++ break; ++ } ++ return 0; ++} ++ ++static u32 nct6775_func(struct i2c_adapter *adapter) ++{ ++ return I2C_FUNC_SMBUS_QUICK | I2C_FUNC_SMBUS_BYTE | ++ I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_WORD_DATA | ++ I2C_FUNC_SMBUS_BLOCK_DATA; ++} ++ ++static const struct i2c_algorithm smbus_algorithm = { ++ .smbus_xfer = nct6775_access, ++ .functionality = nct6775_func, ++}; ++ ++static int nct6775_add_adapter(unsigned short smba, const char *name, struct i2c_adapter **padap) ++{ ++ struct i2c_adapter *adap; ++ struct i2c_nct6775_adapdata *adapdata; ++ int retval; ++ ++ adap = kzalloc(sizeof(*adap), GFP_KERNEL); ++ if (adap == NULL) { ++ return -ENOMEM; ++ } ++ ++ adap->owner = THIS_MODULE; ++ adap->class = I2C_CLASS_HWMON | I2C_CLASS_SPD; ++ adap->algo = &smbus_algorithm; ++ ++ adapdata = kzalloc(sizeof(*adapdata), GFP_KERNEL); ++ if (adapdata == NULL) { ++ kfree(adap); ++ return -ENOMEM; ++ } ++ ++ adapdata->smba = smba; ++ ++ snprintf(adap->name, sizeof(adap->name), ++ "SMBus NCT67xx adapter%s at %04x", name, smba); ++ ++ i2c_set_adapdata(adap, adapdata); ++ ++ retval = i2c_add_adapter(adap); ++ if (retval) { ++ kfree(adapdata); ++ kfree(adap); ++ return retval; ++ } ++ ++ *padap = adap; ++ return 0; ++} ++ ++static void nct6775_remove_adapter(struct i2c_adapter *adap) ++{ ++ struct i2c_nct6775_adapdata *adapdata = i2c_get_adapdata(adap); ++ ++ if (adapdata->smba) { ++ i2c_del_adapter(adap); ++ kfree(adapdata); ++ kfree(adap); ++ } ++} ++ ++//static SIMPLE_DEV_PM_OPS(nct6775_dev_pm_ops, nct6775_suspend, nct6775_resume); ++ ++/* ++ * when Super-I/O functions move to a separate file, the Super-I/O ++ * bus will manage the lifetime of the device and this module will only keep ++ * track of the nct6775 driver. But since we use platform_device_alloc(), we ++ * must keep track of the device ++ */ ++static struct platform_device *pdev[2]; ++ ++static int nct6775_probe(struct platform_device *pdev) ++{ ++ struct device *dev = &pdev->dev; ++ struct nct6775_sio_data *sio_data = dev_get_platdata(dev); ++ struct resource *res; ++ ++ res = platform_get_resource(pdev, IORESOURCE_IO, 0); ++ if (!devm_request_region(&pdev->dev, res->start, IOREGION_LENGTH, ++ DRVNAME)) ++ return -EBUSY; ++ ++ switch (sio_data->kind) { ++ case nct6791: ++ case nct6792: ++ case nct6793: ++ case nct6795: ++ case nct6796: ++ case nct6798: ++ nct6775_add_adapter(res->start, "", &nct6775_adapter); ++ break; ++ default: ++ return -ENODEV; ++ } ++ ++ return 0; ++} ++/* ++static void nct6791_enable_io_mapping(int sioaddr) ++{ ++ int val; ++ ++ val = superio_inb(sioaddr, NCT6791_REG_HM_IO_SPACE_LOCK_ENABLE); ++ if (val & 0x10) { ++ pr_info("Enabling hardware monitor logical device mappings.\n"); ++ superio_outb(sioaddr, NCT6791_REG_HM_IO_SPACE_LOCK_ENABLE, ++ val & ~0x10); ++ } ++}*/ ++ ++static struct platform_driver i2c_nct6775_driver = { ++ .driver = { ++ .name = DRVNAME, ++// .pm = &nct6775_dev_pm_ops, ++ }, ++ .probe = nct6775_probe, ++}; ++ ++static void __exit i2c_nct6775_exit(void) ++{ ++ int i; ++ ++ if(nct6775_adapter) ++ nct6775_remove_adapter(nct6775_adapter); ++ ++ for (i = 0; i < ARRAY_SIZE(pdev); i++) { ++ if (pdev[i]) ++ platform_device_unregister(pdev[i]); ++ } ++ platform_driver_unregister(&i2c_nct6775_driver); ++} ++ ++/* nct6775_find() looks for a '627 in the Super-I/O config space */ ++static int __init nct6775_find(int sioaddr, struct nct6775_sio_data *sio_data) ++{ ++ u16 val; ++ int err; ++ int addr; ++ ++ err = superio_enter(sioaddr); ++ if (err) ++ return err; ++ ++ val = (superio_inb(sioaddr, SIO_REG_DEVID) << 8) | ++ superio_inb(sioaddr, SIO_REG_DEVID + 1); ++ ++ switch (val & SIO_ID_MASK) { ++ case SIO_NCT6106_ID: ++ sio_data->kind = nct6106; ++ break; ++ case SIO_NCT6775_ID: ++ sio_data->kind = nct6775; ++ break; ++ case SIO_NCT6776_ID: ++ sio_data->kind = nct6776; ++ break; ++ case SIO_NCT6779_ID: ++ sio_data->kind = nct6779; ++ break; ++ case SIO_NCT6791_ID: ++ sio_data->kind = nct6791; ++ break; ++ case SIO_NCT6792_ID: ++ sio_data->kind = nct6792; ++ break; ++ case SIO_NCT6793_ID: ++ sio_data->kind = nct6793; ++ break; ++ case SIO_NCT6795_ID: ++ sio_data->kind = nct6795; ++ break; ++ case SIO_NCT6796_ID: ++ sio_data->kind = nct6796; ++ break; ++ case SIO_NCT6798_ID: ++ sio_data->kind = nct6798; ++ break; ++ default: ++ if (val != 0xffff) ++ pr_debug("unsupported chip ID: 0x%04x\n", val); ++ superio_exit(sioaddr); ++ return -ENODEV; ++ } ++ ++ /* We have a known chip, find the SMBus I/O address */ ++ superio_select(sioaddr, NCT6775_LD_SMBUS); ++ val = (superio_inb(sioaddr, SIO_REG_SMBA) << 8) ++ | superio_inb(sioaddr, SIO_REG_SMBA + 1); ++ addr = val & IOREGION_ALIGNMENT; ++ if (addr == 0) { ++ pr_err("Refusing to enable a Super-I/O device with a base I/O port 0\n"); ++ superio_exit(sioaddr); ++ return -ENODEV; ++ } ++ ++ //if (sio_data->kind == nct6791 || sio_data->kind == nct6792 || ++ // sio_data->kind == nct6793 || sio_data->kind == nct6795 || ++ // sio_data->kind == nct6796) ++ // nct6791_enable_io_mapping(sioaddr); ++ ++ superio_exit(sioaddr); ++ pr_info("Found %s or compatible chip at %#x:%#x\n", ++ nct6775_sio_names[sio_data->kind], sioaddr, addr); ++ sio_data->sioreg = sioaddr; ++ ++ return addr; ++} ++ ++static int __init i2c_nct6775_init(void) ++{ ++ int i, err; ++ bool found = false; ++ int address; ++ struct resource res; ++ struct nct6775_sio_data sio_data; ++ int sioaddr[2] = { 0x2e, 0x4e }; ++ ++ err = platform_driver_register(&i2c_nct6775_driver); ++ if (err) ++ return err; ++ ++ /* ++ * initialize sio_data->kind and sio_data->sioreg. ++ * ++ * when Super-I/O functions move to a separate file, the Super-I/O ++ * driver will probe 0x2e and 0x4e and auto-detect the presence of a ++ * nct6775 hardware monitor, and call probe() ++ */ ++ for (i = 0; i < ARRAY_SIZE(pdev); i++) { ++ address = nct6775_find(sioaddr[i], &sio_data); ++ if (address <= 0) ++ continue; ++ ++ found = true; ++ ++ pdev[i] = platform_device_alloc(DRVNAME, address); ++ if (!pdev[i]) { ++ err = -ENOMEM; ++ goto exit_device_unregister; ++ } ++ ++ err = platform_device_add_data(pdev[i], &sio_data, ++ sizeof(struct nct6775_sio_data)); ++ if (err) ++ goto exit_device_put; ++ ++ memset(&res, 0, sizeof(res)); ++ res.name = DRVNAME; ++ res.start = address; ++ res.end = address + IOREGION_LENGTH - 1; ++ res.flags = IORESOURCE_IO; ++ ++ err = acpi_check_resource_conflict(&res); ++ if (err) { ++ platform_device_put(pdev[i]); ++ pdev[i] = NULL; ++ continue; ++ } ++ ++ err = platform_device_add_resources(pdev[i], &res, 1); ++ if (err) ++ goto exit_device_put; ++ ++ /* platform_device_add calls probe() */ ++ err = platform_device_add(pdev[i]); ++ if (err) ++ goto exit_device_put; ++ } ++ if (!found) { ++ err = -ENODEV; ++ goto exit_unregister; ++ } ++ ++ return 0; ++ ++exit_device_put: ++ platform_device_put(pdev[i]); ++exit_device_unregister: ++ while (--i >= 0) { ++ if (pdev[i]) ++ platform_device_unregister(pdev[i]); ++ } ++exit_unregister: ++ platform_driver_unregister(&i2c_nct6775_driver); ++ return err; ++} ++ ++MODULE_AUTHOR("Adam Honse "); ++MODULE_DESCRIPTION("SMBus driver for NCT6775F and compatible chips"); ++MODULE_LICENSE("GPL"); ++ ++module_init(i2c_nct6775_init); ++module_exit(i2c_nct6775_exit); +diff --git a/drivers/i2c/busses/i2c-piix4.c b/drivers/i2c/busses/i2c-piix4.c +index 809fbd014..d54b35b14 100644 +--- a/drivers/i2c/busses/i2c-piix4.c ++++ b/drivers/i2c/busses/i2c-piix4.c +@@ -568,11 +568,11 @@ static int piix4_transaction(struct i2c_adapter *piix4_adapter) + if (srvrworks_csb5_delay) /* Extra delay for SERVERWORKS_CSB5 */ + usleep_range(2000, 2100); + else +- usleep_range(250, 500); ++ usleep_range(25, 50); + + while ((++timeout < MAX_TIMEOUT) && + ((temp = inb_p(SMBHSTSTS)) & 0x01)) +- usleep_range(250, 500); ++ usleep_range(25, 50); + + /* If the SMBus is still busy, we give up */ + if (timeout == MAX_TIMEOUT) { +-- +2.41.0 + diff --git a/patches/nobara-rebased/amdgpu-si-cik-default.patch b/patches/nobara/amdgpu-si-cik-default.patch similarity index 60% rename from patches/nobara-rebased/amdgpu-si-cik-default.patch rename to patches/nobara/amdgpu-si-cik-default.patch index f303205..d2d3178 100644 --- a/patches/nobara-rebased/amdgpu-si-cik-default.patch +++ b/patches/nobara/amdgpu-si-cik-default.patch @@ -1,10 +1,22 @@ -diff '--color=auto' -uraN cachyos/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c cachyos-amdgpu/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c ---- cachyos/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c 2023-10-30 05:31:08.000000000 +0300 -+++ cachyos-amdgpu/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c 2023-11-04 18:03:47.475158392 +0300 -@@ -582,15 +582,11 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jan200101 +Date: Mon, 27 Nov 2023 09:53:59 +0100 +Subject: [PATCH] drm/amdgpu: enable SI and CIK support by default + +Signed-off-by: Jan200101 +--- + drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c | 10 ---------- + drivers/gpu/drm/radeon/radeon_drv.c | 10 ++++++++++ + 2 files changed, 10 insertions(+), 10 deletions(-) + +diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c +index 81edf66dbea8..5021d03089ff 100644 +--- a/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c ++++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c +@@ -582,13 +582,8 @@ module_param_named(timeout_period, amdgpu_watchdog_timer.period, uint, 0644); */ #ifdef CONFIG_DRM_AMDGPU_SI - + -#if IS_ENABLED(CONFIG_DRM_RADEON) || IS_ENABLED(CONFIG_DRM_RADEON_MODULE) -int amdgpu_si_support = 0; -MODULE_PARM_DESC(si_support, "SI support (1 = enabled, 0 = disabled (default))"); @@ -12,16 +24,13 @@ diff '--color=auto' -uraN cachyos/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c cachyo int amdgpu_si_support = 1; MODULE_PARM_DESC(si_support, "SI support (1 = enabled (default), 0 = disabled)"); -#endif - + module_param_named(si_support, amdgpu_si_support, int, 0444); -+ #endif - - /** -@@ -601,15 +597,11 @@ +@@ -601,13 +596,8 @@ module_param_named(si_support, amdgpu_si_support, int, 0444); */ #ifdef CONFIG_DRM_AMDGPU_CIK - + -#if IS_ENABLED(CONFIG_DRM_RADEON) || IS_ENABLED(CONFIG_DRM_RADEON_MODULE) -int amdgpu_cik_support = 0; -MODULE_PARM_DESC(cik_support, "CIK support (1 = enabled, 0 = disabled (default))"); @@ -29,19 +38,17 @@ diff '--color=auto' -uraN cachyos/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c cachyo int amdgpu_cik_support = 1; MODULE_PARM_DESC(cik_support, "CIK support (1 = enabled (default), 0 = disabled)"); -#endif - + module_param_named(cik_support, amdgpu_cik_support, int, 0444); -+ #endif - - /** -diff '--color=auto' -uraN cachyos/drivers/gpu/drm/radeon/radeon_drv.c cachyos-amdgpu/drivers/gpu/drm/radeon/radeon_drv.c ---- cachyos/drivers/gpu/drm/radeon/radeon_drv.c 2023-10-30 05:31:08.000000000 +0300 -+++ cachyos-amdgpu/drivers/gpu/drm/radeon/radeon_drv.c 2023-11-04 18:00:50.099902000 +0300 -@@ -239,12 +239,22 @@ +diff --git a/drivers/gpu/drm/radeon/radeon_drv.c b/drivers/gpu/drm/radeon/radeon_drv.c +index 7bf08164140e..865f186f48c4 100644 +--- a/drivers/gpu/drm/radeon/radeon_drv.c ++++ b/drivers/gpu/drm/radeon/radeon_drv.c +@@ -239,12 +239,22 @@ module_param_named(uvd, radeon_uvd, int, 0444); MODULE_PARM_DESC(vce, "vce enable/disable vce support (1 = enable, 0 = disable)"); module_param_named(vce, radeon_vce, int, 0444); - + +#ifdef CONFIG_DRM_AMDGPU_SI +int radeon_si_support = 0; +MODULE_PARM_DESC(si_support, "SI support (1 = enabled, 0 = disabled (default))"); @@ -50,7 +57,7 @@ diff '--color=auto' -uraN cachyos/drivers/gpu/drm/radeon/radeon_drv.c cachyos-am MODULE_PARM_DESC(si_support, "SI support (1 = enabled (default), 0 = disabled)"); +#endif module_param_named(si_support, radeon_si_support, int, 0444); - + +#ifdef CONFIG_DRM_AMDGPU_CIK +int radeon_cik_support = 0; +MODULE_PARM_DESC(cik_support, "CIK support (1 = enabled, 0 = disabled (default))"); @@ -59,5 +66,5 @@ diff '--color=auto' -uraN cachyos/drivers/gpu/drm/radeon/radeon_drv.c cachyos-am MODULE_PARM_DESC(cik_support, "CIK support (1 = enabled (default), 0 = disabled)"); +#endif module_param_named(cik_support, radeon_cik_support, int, 0444); - + static struct pci_device_id pciidlist[] = { diff --git a/patches/nobara/lenovo-legion-laptop.patch b/patches/nobara/lenovo-legion-laptop.patch new file mode 100644 index 0000000..bfc51fb --- /dev/null +++ b/patches/nobara/lenovo-legion-laptop.patch @@ -0,0 +1,5913 @@ +From cc20a3c74424b7fd78a650803bc06b822e8b1e56 Mon Sep 17 00:00:00 2001 +From: John Martens +Date: Wed, 30 Aug 2023 15:42:09 +0000 +Subject: [PATCH] Add legion-laptop v0.0.7 + +Add extra support for Lenovo Legion laptops. +--- + drivers/platform/x86/Kconfig | 10 + + drivers/platform/x86/Makefile | 1 + + drivers/platform/x86/legion-laptop.c | 5858 ++++++++++++++++++++++++++ + 3 files changed, 5869 insertions(+) + create mode 100644 drivers/platform/x86/legion-laptop.c + +diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig +index 49c2c4cd8..b7d70c20e 100644 +--- a/drivers/platform/x86/Kconfig ++++ b/drivers/platform/x86/Kconfig +@@ -643,6 +643,16 @@ config THINKPAD_LMI + To compile this driver as a module, choose M here: the module will + be called think-lmi. + ++config LEGION_LAPTOP ++ tristate "Lenovo Legion Laptop Extras" ++ depends on ACPI ++ depends on ACPI_WMI || ACPI_WMI = n ++ depends on HWMON || HWMON = n ++ select ACPI_PLATFORM_PROFILE ++ help ++ This is a driver for Lenovo Legion laptops and contains drivers for ++ hotkey, fan control, and power mode. ++ + source "drivers/platform/x86/intel/Kconfig" + + config MSI_EC +diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile +index 52dfdf574..5f32dd9df 100644 +--- a/drivers/platform/x86/Makefile ++++ b/drivers/platform/x86/Makefile +@@ -65,6 +65,7 @@ obj-$(CONFIG_LENOVO_YMC) += lenovo-ymc.o + obj-$(CONFIG_SENSORS_HDAPS) += hdaps.o + obj-$(CONFIG_THINKPAD_ACPI) += thinkpad_acpi.o + obj-$(CONFIG_THINKPAD_LMI) += think-lmi.o ++obj-$(CONFIG_LEGION_LAPTOP) += legion-laptop.o + obj-$(CONFIG_YOGABOOK) += lenovo-yogabook.o + + # Intel +diff --git a/drivers/platform/x86/legion-laptop.c b/drivers/platform/x86/legion-laptop.c +new file mode 100644 +index 000000000..727510507 +--- /dev/null ++++ b/drivers/platform/x86/legion-laptop.c +@@ -0,0 +1,5858 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * legion-laptop.c - Extra Lenovo Legion laptop support, in ++ * particular for fan curve control and power mode. ++ * ++ * Copyright (C) 2022 johnfan ++ * ++ * ++ * This driver might work on other Lenovo Legion models. If you ++ * want to try it you can pass force=1 as argument ++ * to the module which will force it to load even when the DMI ++ * data doesn't match the model AND FIRMWARE. ++ * ++ * Support for other hardware of this model is already partially ++ * provided by the module ideapd-laptop. ++ * ++ * The development page for this driver is located at ++ * https://github.com/johnfanv2/LenovoLegionLinux ++ * ++ * This driver exports the files: ++ * - /sys/kernel/debug/legion/fancurve (ro) ++ * The fan curve in the form stored in the firmware in an ++ * human readable table. ++ * ++ * - /sys/module/legion_laptop/drivers/platform\:legion/PNP0C09\:00/powermode (rw) ++ * 0: balanced mode (white) ++ * 1: performance mode (red) ++ * 2: quiet mode (blue) ++ * ?: custom mode (pink) ++ * ++ * NOTE: Writing to this will load the default fan curve from ++ * the firmware for this mode, so the fan curve might ++ * have to be reconfigured if needed. ++ * ++ * It implements the usual hwmon interface to monitor fan speed and temmperature ++ * and allows to set the fan curve inside the firware. ++ * ++ * - /sys/class/hwmon/X/fan1_input or /sys/class/hwmon/X/fan2_input (ro) ++ * Current fan speed of fan1/fan2. ++ * - /sys/class/hwmon/X/temp1_input (ro) ++ * - /sys/class/hwmon/X/temp2_input (ro) ++ * - /sys/class/hwmon/X/temp3_input (ro) ++ * Temperature (Celsius) of CPU, GPU, and IC used for fan control. ++ * - /sys/class/hwmon/X/pwmY_auto_pointZ_pwm (rw) ++ * PWM (0-255) of the fan at the Y-level in the fan curve ++ * - /sys/class/hwmon/X/pwmY_auto_pointZ_temp (rw) ++ * upper temperature of tempZ (CPU, GPU, or IC) at the Y-level in the fan curve ++ * - /sys/class/hwmon/X/pwmY_auto_pointZ_temp_hyst (rw) ++ * hysteris (CPU, GPU, or IC) at the Y-level in the fan curve. The lower ++ * temperatue of the level is the upper temperature minus the hysteris ++ * ++ * ++ * Credits for reverse engineering the firmware to: ++ * - David Woodhouse: heavily inspired by lenovo_laptop.c ++ * - Luke Cama: Windows version "LegionFanControl" ++ * - SmokelessCPU: reverse engineering of custom registers in EC ++ * and commincation method with EC via ports ++ * - 0x1F9F1: additional reverse engineering for complete fan curve ++ */ ++#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++MODULE_LICENSE("GPL"); ++MODULE_AUTHOR("johnfan"); ++MODULE_DESCRIPTION("Lenovo Legion laptop extras"); ++ ++static bool force; ++module_param(force, bool, 0440); ++MODULE_PARM_DESC( ++ force, ++ "Force loading this module even if model or BIOS does not match."); ++ ++static bool ec_readonly; ++module_param(ec_readonly, bool, 0440); ++MODULE_PARM_DESC( ++ ec_readonly, ++ "Only read from embedded controller but do not write or change settings."); ++ ++static bool enable_platformprofile = true; ++module_param(enable_platformprofile, bool, 0440); ++MODULE_PARM_DESC( ++ enable_platformprofile, ++ "Enable the platform profile sysfs API to read and write the power mode."); ++ ++#define LEGIONFEATURES \ ++ "fancurve powermode platformprofile platformprofilenotify minifancurve" ++ ++//Size of fancurve stored in embedded controller ++#define MAXFANCURVESIZE 10 ++ ++#define LEGION_DRVR_SHORTNAME "legion" ++#define LEGION_HWMON_NAME LEGION_DRVR_SHORTNAME "_hwmon" ++ ++struct legion_private; ++ ++/* =============================== */ ++/* Embedded Controller Description */ ++/* =============================== */ ++ ++/* The configuration and registers to access the embedded controller ++ * depending on different the version of the software on the ++ * embedded controller or and the BIOS/UEFI firmware. ++ * ++ * To control fan curve in the embedded controller (EC) one has to ++ * write to its "RAM". There are different possibilities: ++ * - EC RAM is memory mapped (write to it with ioremap) ++ * - access EC RAM via ported mapped IO (outb/inb) ++ * - access EC RAM via ACPI methods. It is only possible to write ++ * to part of it (first 0xFF bytes?) ++ * ++ * In later models the firmware directly exposes ACPI methods to ++ * set the fan curve direclty, without writing to EC RAM. This ++ * is done inside the ACPI method. ++ */ ++ ++/** ++ * Offsets for interseting values inside the EC RAM (0 = start of ++ * EC RAM. These might change depending on the software inside of ++ * the EC, which can be updated by a BIOS update from Lenovo. ++ */ ++// TODO: same order as in initialization ++struct ec_register_offsets { ++ // Super I/O Configuration Registers ++ // 7.15 General Control (GCTRL) ++ // General Control (GCTRL) ++ // (see EC Interface Registers and 6.2 Plug and Play Configuration (PNPCFG)) in datasheet ++ // note: these are in two places saved ++ // in EC Interface Registers and in super io configuraion registers ++ // Chip ID ++ u16 ECHIPID1; ++ u16 ECHIPID2; ++ // Chip Version ++ u16 ECHIPVER; ++ u16 ECDEBUG; ++ ++ // Lenovo Custom OEM extension ++ // Firmware of ITE can be extended by ++ // custom program using its own "variables" ++ // These are the offsets to these "variables" ++ u16 EXT_FAN_CUR_POINT; ++ u16 EXT_FAN_POINTS_SIZE; ++ u16 EXT_FAN1_BASE; ++ u16 EXT_FAN2_BASE; ++ u16 EXT_FAN_ACC_BASE; ++ u16 EXT_FAN_DEC_BASE; ++ u16 EXT_CPU_TEMP; ++ u16 EXT_CPU_TEMP_HYST; ++ u16 EXT_GPU_TEMP; ++ u16 EXT_GPU_TEMP_HYST; ++ u16 EXT_VRM_TEMP; ++ u16 EXT_VRM_TEMP_HYST; ++ u16 EXT_FAN1_RPM_LSB; ++ u16 EXT_FAN1_RPM_MSB; ++ u16 EXT_FAN2_RPM_LSB; ++ u16 EXT_FAN2_RPM_MSB; ++ u16 EXT_FAN1_TARGET_RPM; ++ u16 EXT_FAN2_TARGET_RPM; ++ u16 EXT_POWERMODE; ++ u16 EXT_MINIFANCURVE_ON_COOL; ++ // values ++ // 0x04: enable mini fan curve if very long on cool level ++ // - this might be due to potential temp failure ++ // - or just because really so cool ++ // 0xA0: disable it ++ u16 EXT_LOCKFANCONTROLLER; ++ u16 EXT_MAXIMUMFANSPEED; ++ u16 EXT_WHITE_KEYBOARD_BACKLIGHT; ++ u16 EXT_IC_TEMP_INPUT; ++ u16 EXT_CPU_TEMP_INPUT; ++ u16 EXT_GPU_TEMP_INPUT; ++}; ++ ++enum access_method { ++ ACCESS_METHOD_NO_ACCESS = 0, ++ ACCESS_METHOD_EC = 1, ++ ACCESS_METHOD_ACPI = 2, ++ ACCESS_METHOD_WMI = 3, ++ ACCESS_METHOD_WMI2 = 4, ++ ACCESS_METHOD_WMI3 = 5, ++ ACCESS_METHOD_EC2 = 10, // ideapad fancurve method ++}; ++ ++struct model_config { ++ const struct ec_register_offsets *registers; ++ bool check_embedded_controller_id; ++ u16 embedded_controller_id; ++ ++ // first addr in EC we access/scan ++ phys_addr_t memoryio_physical_ec_start; ++ size_t memoryio_size; ++ ++ // TODO: maybe use bitfield ++ bool has_minifancurve; ++ bool has_custom_powermode; ++ enum access_method access_method_powermode; ++ ++ enum access_method access_method_keyboard; ++ enum access_method access_method_temperature; ++ enum access_method access_method_fanspeed; ++ enum access_method access_method_fancurve; ++ enum access_method access_method_fanfullspeed; ++ bool three_state_keyboard; ++ ++ bool acpi_check_dev; ++ ++ phys_addr_t ramio_physical_start; ++ size_t ramio_size; ++}; ++ ++/* =================================== */ ++/* Configuration for different models */ ++/* =================================== */ ++ ++// Idea by SmokelesssCPU (modified) ++// - all default names and register addresses are supported by datasheet ++// - register addresses for custom firmware by SmokelesssCPU ++static const struct ec_register_offsets ec_register_offsets_v0 = { ++ .ECHIPID1 = 0x2000, ++ .ECHIPID2 = 0x2001, ++ .ECHIPVER = 0x2002, ++ .ECDEBUG = 0x2003, ++ .EXT_FAN_CUR_POINT = 0xC534, ++ .EXT_FAN_POINTS_SIZE = 0xC535, ++ .EXT_FAN1_BASE = 0xC540, ++ .EXT_FAN2_BASE = 0xC550, ++ .EXT_FAN_ACC_BASE = 0xC560, ++ .EXT_FAN_DEC_BASE = 0xC570, ++ .EXT_CPU_TEMP = 0xC580, ++ .EXT_CPU_TEMP_HYST = 0xC590, ++ .EXT_GPU_TEMP = 0xC5A0, ++ .EXT_GPU_TEMP_HYST = 0xC5B0, ++ .EXT_VRM_TEMP = 0xC5C0, ++ .EXT_VRM_TEMP_HYST = 0xC5D0, ++ .EXT_FAN1_RPM_LSB = 0xC5E0, ++ .EXT_FAN1_RPM_MSB = 0xC5E1, ++ .EXT_FAN2_RPM_LSB = 0xC5E2, ++ .EXT_FAN2_RPM_MSB = 0xC5E3, ++ .EXT_MINIFANCURVE_ON_COOL = 0xC536, ++ .EXT_LOCKFANCONTROLLER = 0xc4AB, ++ .EXT_CPU_TEMP_INPUT = 0xc538, ++ .EXT_GPU_TEMP_INPUT = 0xc539, ++ .EXT_IC_TEMP_INPUT = 0xC5E8, ++ .EXT_POWERMODE = 0xc420, ++ .EXT_FAN1_TARGET_RPM = 0xc600, ++ .EXT_FAN2_TARGET_RPM = 0xc601, ++ .EXT_MAXIMUMFANSPEED = 0xBD, ++ .EXT_WHITE_KEYBOARD_BACKLIGHT = (0x3B + 0xC400) ++}; ++ ++static const struct ec_register_offsets ec_register_offsets_v1 = { ++ .ECHIPID1 = 0x2000, ++ .ECHIPID2 = 0x2001, ++ .ECHIPVER = 0x2002, ++ .ECDEBUG = 0x2003, ++ .EXT_FAN_CUR_POINT = 0xC534, ++ .EXT_FAN_POINTS_SIZE = 0xC535, ++ .EXT_FAN1_BASE = 0xC540, ++ .EXT_FAN2_BASE = 0xC550, ++ .EXT_FAN_ACC_BASE = 0xC560, ++ .EXT_FAN_DEC_BASE = 0xC570, ++ .EXT_CPU_TEMP = 0xC580, ++ .EXT_CPU_TEMP_HYST = 0xC590, ++ .EXT_GPU_TEMP = 0xC5A0, ++ .EXT_GPU_TEMP_HYST = 0xC5B0, ++ .EXT_VRM_TEMP = 0xC5C0, ++ .EXT_VRM_TEMP_HYST = 0xC5D0, ++ .EXT_FAN1_RPM_LSB = 0xC5E0, ++ .EXT_FAN1_RPM_MSB = 0xC5E1, ++ .EXT_FAN2_RPM_LSB = 0xC5E2, ++ .EXT_FAN2_RPM_MSB = 0xC5E3, ++ .EXT_MINIFANCURVE_ON_COOL = 0xC536, ++ .EXT_LOCKFANCONTROLLER = 0xc4AB, ++ .EXT_CPU_TEMP_INPUT = 0xc538, ++ .EXT_GPU_TEMP_INPUT = 0xc539, ++ .EXT_IC_TEMP_INPUT = 0xC5E8, ++ .EXT_POWERMODE = 0xc41D, ++ .EXT_FAN1_TARGET_RPM = 0xc600, ++ .EXT_FAN2_TARGET_RPM = 0xc601, ++ .EXT_MAXIMUMFANSPEED = 0xBD, ++ .EXT_WHITE_KEYBOARD_BACKLIGHT = (0x3B + 0xC400) ++}; ++ ++static const struct ec_register_offsets ec_register_offsets_ideapad_v0 = { ++ .ECHIPID1 = 0x2000, ++ .ECHIPID2 = 0x2001, ++ .ECHIPVER = 0x2002, ++ .ECDEBUG = 0x2003, ++ .EXT_FAN_CUR_POINT = 0xC5a0, // not found yet ++ .EXT_FAN_POINTS_SIZE = 0xC5a0, // constant 0 ++ .EXT_FAN1_BASE = 0xC5a0, ++ .EXT_FAN2_BASE = 0xC5a8, ++ .EXT_FAN_ACC_BASE = 0xC5a0, // not found yet ++ .EXT_FAN_DEC_BASE = 0xC5a0, // not found yet ++ .EXT_CPU_TEMP = 0xC550, // and repeated after 8 bytes ++ .EXT_CPU_TEMP_HYST = 0xC590, // and repeated after 8 bytes ++ .EXT_GPU_TEMP = 0xC5C0, // and repeated after 8 bytes ++ .EXT_GPU_TEMP_HYST = 0xC5D0, // and repeated after 8 bytes ++ .EXT_VRM_TEMP = 0xC5a0, // does not exists or not found ++ .EXT_VRM_TEMP_HYST = 0xC5a0, // does not exists ot not found yet ++ .EXT_FAN1_RPM_LSB = 0xC5a0, // not found yet ++ .EXT_FAN1_RPM_MSB = 0xC5a0, // not found yet ++ .EXT_FAN2_RPM_LSB = 0xC5a0, // not found yet ++ .EXT_FAN2_RPM_MSB = 0xC5a0, // not found yet ++ .EXT_MINIFANCURVE_ON_COOL = 0xC5a0, // does not exists or not found ++ .EXT_LOCKFANCONTROLLER = 0xC5a0, // does not exists or not found ++ .EXT_CPU_TEMP_INPUT = 0xC5a0, // not found yet ++ .EXT_GPU_TEMP_INPUT = 0xC5a0, // not found yet ++ .EXT_IC_TEMP_INPUT = 0xC5a0, // not found yet ++ .EXT_POWERMODE = 0xC5a0, // not found yet ++ .EXT_FAN1_TARGET_RPM = 0xC5a0, // not found yet ++ .EXT_FAN2_TARGET_RPM = 0xC5a0, // not found yet ++ .EXT_MAXIMUMFANSPEED = 0xC5a0, // not found yet ++ .EXT_WHITE_KEYBOARD_BACKLIGHT = 0xC5a0 // not found yet ++}; ++ ++static const struct ec_register_offsets ec_register_offsets_ideapad_v1 = { ++ .ECHIPID1 = 0x2000, ++ .ECHIPID2 = 0x2001, ++ .ECHIPVER = 0x2002, ++ .ECDEBUG = 0x2003, ++ .EXT_FAN_CUR_POINT = 0xC5a0, // not found yet ++ .EXT_FAN_POINTS_SIZE = 0xC5a0, // constant 0 ++ .EXT_FAN1_BASE = 0xC5a0, ++ .EXT_FAN2_BASE = 0xC5a8, ++ .EXT_FAN_ACC_BASE = 0xC5a0, // not found yet ++ .EXT_FAN_DEC_BASE = 0xC5a0, // not found yet ++ .EXT_CPU_TEMP = 0xC550, // and repeated after 8 bytes ++ .EXT_CPU_TEMP_HYST = 0xC590, // and repeated after 8 bytes ++ .EXT_GPU_TEMP = 0xC5C0, // and repeated after 8 bytes ++ .EXT_GPU_TEMP_HYST = 0xC5D0, // and repeated after 8 bytes ++ .EXT_VRM_TEMP = 0xC5a0, // does not exists or not found ++ .EXT_VRM_TEMP_HYST = 0xC5a0, // does not exists ot not found yet ++ .EXT_FAN1_RPM_LSB = 0xC5a0, // not found yet ++ .EXT_FAN1_RPM_MSB = 0xC5a0, // not found yet ++ .EXT_FAN2_RPM_LSB = 0xC5a0, // not found yet ++ .EXT_FAN2_RPM_MSB = 0xC5a0, // not found yet ++ .EXT_MINIFANCURVE_ON_COOL = 0xC5a0, // does not exists or not found ++ .EXT_LOCKFANCONTROLLER = 0xC5a0, // does not exists or not found ++ .EXT_CPU_TEMP_INPUT = 0xC5a0, // not found yet ++ .EXT_GPU_TEMP_INPUT = 0xC5a0, // not found yet ++ .EXT_IC_TEMP_INPUT = 0xC5a0, // not found yet ++ .EXT_POWERMODE = 0xC5a0, // not found yet ++ .EXT_FAN1_TARGET_RPM = 0xC5a0, // not found yet ++ .EXT_FAN2_TARGET_RPM = 0xC5a0, // not found yet ++ .EXT_MAXIMUMFANSPEED = 0xC5a0, // not found yet ++ .EXT_WHITE_KEYBOARD_BACKLIGHT = 0xC5a0 // not found yet ++}; ++ ++static const struct model_config model_v0 = { ++ .registers = &ec_register_offsets_v0, ++ .check_embedded_controller_id = true, ++ .embedded_controller_id = 0x8227, ++ .memoryio_physical_ec_start = 0xC400, ++ .memoryio_size = 0x300, ++ .has_minifancurve = true, ++ .has_custom_powermode = true, ++ .access_method_powermode = ACCESS_METHOD_WMI, ++ .access_method_keyboard = ACCESS_METHOD_WMI, ++ .access_method_fanspeed = ACCESS_METHOD_EC, ++ .access_method_temperature = ACCESS_METHOD_EC, ++ .access_method_fancurve = ACCESS_METHOD_EC, ++ .access_method_fanfullspeed = ACCESS_METHOD_WMI, ++ .acpi_check_dev = true, ++ .ramio_physical_start = 0xFE00D400, ++ .ramio_size = 0x600 ++}; ++ ++static const struct model_config model_j2cn = { ++ .registers = &ec_register_offsets_v0, ++ .check_embedded_controller_id = true, ++ .embedded_controller_id = 0x8227, ++ .memoryio_physical_ec_start = 0xC400, ++ .memoryio_size = 0x300, ++ .has_minifancurve = true, ++ .has_custom_powermode = true, ++ .access_method_powermode = ACCESS_METHOD_WMI, ++ .access_method_keyboard = ACCESS_METHOD_WMI, ++ .access_method_fanspeed = ACCESS_METHOD_EC, ++ .access_method_temperature = ACCESS_METHOD_EC, ++ .access_method_fancurve = ACCESS_METHOD_EC, ++ .access_method_fanfullspeed = ACCESS_METHOD_WMI, ++ .acpi_check_dev = true, ++ .ramio_physical_start = 0xFE00D400, ++ .ramio_size = 0x600 ++}; ++ ++static const struct model_config model_9vcn = { ++ .registers = &ec_register_offsets_ideapad_v1, ++ .check_embedded_controller_id = true, ++ .embedded_controller_id = 0x8226, ++ .memoryio_physical_ec_start = 0xC400, ++ .memoryio_size = 0x300, ++ .has_minifancurve = true, ++ .has_custom_powermode = true, ++ .access_method_powermode = ACCESS_METHOD_WMI, ++ .access_method_keyboard = ACCESS_METHOD_WMI, ++ .access_method_fanspeed = ACCESS_METHOD_WMI, ++ .access_method_temperature = ACCESS_METHOD_WMI, ++ .access_method_fancurve = ACCESS_METHOD_EC2, ++ .access_method_fanfullspeed = ACCESS_METHOD_WMI, ++ .acpi_check_dev = false, ++ .ramio_physical_start = 0xFE00D400, ++ .ramio_size = 0x600 ++}; ++ ++static const struct model_config model_v2022 = { ++ .registers = &ec_register_offsets_v0, ++ .check_embedded_controller_id = true, ++ .embedded_controller_id = 0x8227, ++ .memoryio_physical_ec_start = 0xC400, ++ .memoryio_size = 0x300, ++ .has_minifancurve = true, ++ .has_custom_powermode = true, ++ .access_method_powermode = ACCESS_METHOD_WMI, ++ .access_method_keyboard = ACCESS_METHOD_WMI, ++ .access_method_fanspeed = ACCESS_METHOD_EC, ++ .access_method_temperature = ACCESS_METHOD_EC, ++ .access_method_fancurve = ACCESS_METHOD_EC, ++ .access_method_fanfullspeed = ACCESS_METHOD_WMI, ++ .acpi_check_dev = true, ++ .ramio_physical_start = 0xFE00D400, ++ .ramio_size = 0x600 ++}; ++ ++static const struct model_config model_4gcn = { ++ .registers = &ec_register_offsets_v0, ++ .check_embedded_controller_id = true, ++ .embedded_controller_id = 0x8226, ++ .memoryio_physical_ec_start = 0xC400, ++ .memoryio_size = 0x300, ++ .has_minifancurve = true, ++ .has_custom_powermode = true, ++ .access_method_powermode = ACCESS_METHOD_WMI, ++ .access_method_keyboard = ACCESS_METHOD_WMI, ++ .access_method_fanspeed = ACCESS_METHOD_EC, ++ .access_method_temperature = ACCESS_METHOD_EC, ++ .access_method_fancurve = ACCESS_METHOD_EC, ++ .access_method_fanfullspeed = ACCESS_METHOD_WMI, ++ .acpi_check_dev = true, ++ .ramio_physical_start = 0xFE00D400, ++ .ramio_size = 0x600 ++}; ++ ++static const struct model_config model_bvcn = { ++ .registers = &ec_register_offsets_v0, ++ .check_embedded_controller_id = false, ++ .embedded_controller_id = 0x8226, ++ .memoryio_physical_ec_start = 0xC400, ++ .memoryio_size = 0x300, ++ .has_minifancurve = true, ++ .has_custom_powermode = true, ++ .access_method_powermode = ACCESS_METHOD_WMI, ++ .access_method_keyboard = ACCESS_METHOD_WMI, ++ .access_method_fanspeed = ACCESS_METHOD_WMI, ++ .access_method_temperature = ACCESS_METHOD_WMI, ++ .access_method_fancurve = ACCESS_METHOD_NO_ACCESS, ++ .access_method_fanfullspeed = ACCESS_METHOD_WMI, ++ .acpi_check_dev = false, ++ .ramio_physical_start = 0xFC7E0800, ++ .ramio_size = 0x600 ++}; ++ ++static const struct model_config model_bhcn = { ++ .registers = &ec_register_offsets_v0, ++ .check_embedded_controller_id = true, ++ .embedded_controller_id = 0x8226, ++ .memoryio_physical_ec_start = 0xC400, ++ .memoryio_size = 0x300, ++ .has_minifancurve = true, ++ .has_custom_powermode = false, ++ .access_method_powermode = ACCESS_METHOD_WMI, ++ .access_method_keyboard = ACCESS_METHOD_ACPI, ++ .access_method_fanspeed = ACCESS_METHOD_WMI, ++ .access_method_temperature = ACCESS_METHOD_ACPI, ++ .access_method_fancurve = ACCESS_METHOD_EC, ++ .access_method_fanfullspeed = ACCESS_METHOD_WMI, ++ .acpi_check_dev = true, ++ .ramio_physical_start = 0xFF00D400, ++ .ramio_size = 0x600 ++}; ++ ++static const struct model_config model_kwcn = { ++ .registers = &ec_register_offsets_v0, ++ .check_embedded_controller_id = true, ++ .embedded_controller_id = 0x5507, ++ .memoryio_physical_ec_start = 0xC400, ++ .memoryio_size = 0x300, ++ .has_minifancurve = false, ++ .has_custom_powermode = true, ++ .access_method_powermode = ACCESS_METHOD_WMI, ++ .access_method_keyboard = ACCESS_METHOD_WMI, ++ .access_method_fanspeed = ACCESS_METHOD_WMI3, ++ .access_method_temperature = ACCESS_METHOD_WMI3, ++ .access_method_fancurve = ACCESS_METHOD_WMI3, ++ .access_method_fanfullspeed = ACCESS_METHOD_WMI, ++ .acpi_check_dev = true, ++ .ramio_physical_start = 0xFE0B0400, ++ .ramio_size = 0x600 ++}; ++ ++static const struct model_config model_m2cn = { ++ .registers = &ec_register_offsets_v0, ++ .check_embedded_controller_id = true, ++ .embedded_controller_id = 0x8227, ++ .memoryio_physical_ec_start = 0xC400, ++ .memoryio_size = 0x300, ++ .has_minifancurve = false, ++ .has_custom_powermode = true, ++ .access_method_powermode = ACCESS_METHOD_WMI, ++ .access_method_keyboard = ACCESS_METHOD_WMI, ++ .access_method_fanspeed = ACCESS_METHOD_WMI3, ++ .access_method_temperature = ACCESS_METHOD_WMI3, ++ .access_method_fancurve = ACCESS_METHOD_WMI3, ++ .access_method_fanfullspeed = ACCESS_METHOD_WMI, ++ .acpi_check_dev = false, ++ .ramio_physical_start = 0xFE0B0400, ++ .ramio_size = 0x600 ++}; ++ ++static const struct model_config model_k1cn = { ++ .registers = &ec_register_offsets_v0, ++ .check_embedded_controller_id = true, ++ .embedded_controller_id = 0x5263, ++ .memoryio_physical_ec_start = 0xC400, ++ .memoryio_size = 0x300, ++ .has_minifancurve = false, ++ .has_custom_powermode = true, ++ .access_method_powermode = ACCESS_METHOD_WMI, ++ .access_method_keyboard = ACCESS_METHOD_WMI, ++ .access_method_fanspeed = ACCESS_METHOD_WMI3, ++ .access_method_temperature = ACCESS_METHOD_WMI3, ++ .access_method_fancurve = ACCESS_METHOD_WMI3, ++ .access_method_fanfullspeed = ACCESS_METHOD_WMI, ++ .acpi_check_dev = true, ++ .ramio_physical_start = 0xFE0B0400, ++ .ramio_size = 0x600 ++}; ++ ++static const struct model_config model_lpcn = { ++ .registers = &ec_register_offsets_v0, ++ .check_embedded_controller_id = true, ++ .embedded_controller_id = 0x5507, ++ .memoryio_physical_ec_start = 0xC400, ++ .memoryio_size = 0x300, ++ .has_minifancurve = false, ++ .has_custom_powermode = true, ++ .access_method_powermode = ACCESS_METHOD_WMI, ++ .access_method_keyboard = ACCESS_METHOD_WMI, ++ .access_method_fanspeed = ACCESS_METHOD_WMI3, ++ .access_method_temperature = ACCESS_METHOD_WMI3, ++ .access_method_fancurve = ACCESS_METHOD_WMI3, ++ .access_method_fanfullspeed = ACCESS_METHOD_WMI, ++ .acpi_check_dev = true, ++ .ramio_physical_start = 0xFE0B0400, ++ .ramio_size = 0x600 ++}; ++ ++static const struct model_config model_kfcn = { ++ .registers = &ec_register_offsets_v0, ++ .check_embedded_controller_id = true, ++ .embedded_controller_id = 0x8227, ++ .memoryio_physical_ec_start = 0xC400, ++ .memoryio_size = 0x300, ++ .has_minifancurve = false, ++ .has_custom_powermode = true, ++ .access_method_powermode = ACCESS_METHOD_WMI, ++ .access_method_keyboard = ACCESS_METHOD_WMI, ++ .access_method_fanspeed = ACCESS_METHOD_EC, ++ .access_method_temperature = ACCESS_METHOD_EC, ++ .access_method_fancurve = ACCESS_METHOD_EC, ++ .access_method_fanfullspeed = ACCESS_METHOD_WMI, ++ .acpi_check_dev = true, ++ .ramio_physical_start = 0xFE00D400, ++ .ramio_size = 0x600 ++}; ++ ++static const struct model_config model_hacn = { ++ .registers = &ec_register_offsets_v0, ++ .check_embedded_controller_id = false, ++ .embedded_controller_id = 0x8227, ++ .memoryio_physical_ec_start = 0xC400, ++ .memoryio_size = 0x300, ++ .has_minifancurve = true, ++ .has_custom_powermode = true, ++ .access_method_powermode = ACCESS_METHOD_WMI, ++ .access_method_keyboard = ACCESS_METHOD_WMI, ++ .access_method_fanspeed = ACCESS_METHOD_EC, ++ .access_method_temperature = ACCESS_METHOD_EC, ++ .access_method_fancurve = ACCESS_METHOD_EC, ++ .access_method_fanfullspeed = ACCESS_METHOD_WMI, ++ .acpi_check_dev = true, ++ .ramio_physical_start = 0xFE00D400, ++ .ramio_size = 0x600 ++}; ++ ++static const struct model_config model_k9cn = { ++ .registers = &ec_register_offsets_v0, ++ .check_embedded_controller_id = false, ++ .embedded_controller_id = 0x8227, ++ .memoryio_physical_ec_start = 0xC400, // or replace 0xC400 by 0x0400 ? ++ .memoryio_size = 0x300, ++ .has_minifancurve = true, ++ .has_custom_powermode = true, ++ .access_method_powermode = ACCESS_METHOD_WMI, ++ .access_method_keyboard = ACCESS_METHOD_WMI, ++ .access_method_fanspeed = ACCESS_METHOD_EC, ++ .access_method_temperature = ACCESS_METHOD_EC, ++ .access_method_fancurve = ACCESS_METHOD_EC, ++ .access_method_fanfullspeed = ACCESS_METHOD_WMI, ++ .acpi_check_dev = true, ++ .ramio_physical_start = 0xFE00D400, ++ .ramio_size = 0x600 ++}; ++ ++static const struct model_config model_eucn = { ++ .registers = &ec_register_offsets_v1, ++ .check_embedded_controller_id = true, ++ .embedded_controller_id = 0x8227, ++ .memoryio_physical_ec_start = 0xC400, ++ .memoryio_size = 0x300, ++ .has_minifancurve = true, ++ .has_custom_powermode = true, ++ .access_method_powermode = ACCESS_METHOD_WMI, ++ .access_method_keyboard = ACCESS_METHOD_WMI, ++ .access_method_fanspeed = ACCESS_METHOD_EC, ++ .access_method_temperature = ACCESS_METHOD_EC, ++ .access_method_fancurve = ACCESS_METHOD_EC, ++ .access_method_fanfullspeed = ACCESS_METHOD_WMI, ++ .acpi_check_dev = true, ++ .ramio_physical_start = 0xFE00D400, ++ .ramio_size = 0x600 ++}; ++ ++static const struct model_config model_fccn = { ++ .registers = &ec_register_offsets_ideapad_v0, ++ .check_embedded_controller_id = true, ++ .embedded_controller_id = 0x8227, ++ .memoryio_physical_ec_start = 0xC400, ++ .memoryio_size = 0x300, ++ .has_minifancurve = false, ++ .has_custom_powermode = true, ++ .access_method_powermode = ACCESS_METHOD_WMI, ++ .access_method_keyboard = ACCESS_METHOD_WMI, ++ .access_method_fanspeed = ACCESS_METHOD_WMI, ++ .access_method_temperature = ACCESS_METHOD_ACPI, ++ .access_method_fancurve = ACCESS_METHOD_EC2, ++ .access_method_fanfullspeed = ACCESS_METHOD_WMI, ++ .acpi_check_dev = true, ++ .ramio_physical_start = 0xFE00D400, ++ .ramio_size = 0x600 ++}; ++ ++static const struct model_config model_h3cn = { ++ //0xFE0B0800 ++ .registers = &ec_register_offsets_v1, ++ .check_embedded_controller_id = false, ++ .embedded_controller_id = 0x8227, ++ .memoryio_physical_ec_start = 0xC400, ++ .memoryio_size = 0x300, ++ .has_minifancurve = false, ++ .has_custom_powermode = false, ++ .access_method_powermode = ACCESS_METHOD_WMI, ++ // not implemented (properly) in WMI, RGB conrolled by USB ++ .access_method_keyboard = ACCESS_METHOD_NO_ACCESS, ++ // accessing fan speed is not implemented in ACPI ++ // a variable in the operation region (or not found) ++ // and not per WMI (methods returns constant 0) ++ .access_method_fanspeed = ACCESS_METHOD_NO_ACCESS, ++ .access_method_temperature = ACCESS_METHOD_WMI, ++ .access_method_fancurve = ACCESS_METHOD_NO_ACCESS, ++ .access_method_fanfullspeed = ACCESS_METHOD_WMI, ++ .acpi_check_dev = false, ++ .ramio_physical_start = 0xFE0B0800, ++ .ramio_size = 0x600 ++}; ++ ++static const struct model_config model_e9cn = { ++ //0xFE0B0800 ++ .registers = &ec_register_offsets_v1, ++ .check_embedded_controller_id = false, ++ .embedded_controller_id = 0x8227, ++ .memoryio_physical_ec_start = 0xC400, //0xFC7E0800 ++ .memoryio_size = 0x300, ++ .has_minifancurve = false, ++ .has_custom_powermode = false, ++ .access_method_powermode = ACCESS_METHOD_WMI, ++ // not implemented (properly) in WMI, RGB conrolled by USB ++ .access_method_keyboard = ACCESS_METHOD_NO_ACCESS, ++ // accessing fan speed is not implemented in ACPI ++ // a variable in the operation region (or not found) ++ // and not per WMI (methods returns constant 0) ++ .access_method_fanspeed = ACCESS_METHOD_WMI, ++ .access_method_temperature = ACCESS_METHOD_WMI, ++ .access_method_fancurve = ACCESS_METHOD_NO_ACCESS, ++ .access_method_fanfullspeed = ACCESS_METHOD_WMI, ++ .acpi_check_dev = false, ++ .ramio_physical_start = 0xFC7E0800, ++ .ramio_size = 0x600 ++}; ++ ++static const struct model_config model_8jcn = { ++ .registers = &ec_register_offsets_v0, ++ .check_embedded_controller_id = true, ++ .embedded_controller_id = 0x8226, ++ .memoryio_physical_ec_start = 0xC400, ++ .memoryio_size = 0x300, ++ .has_minifancurve = true, ++ .has_custom_powermode = true, ++ .access_method_powermode = ACCESS_METHOD_WMI, ++ .access_method_keyboard = ACCESS_METHOD_WMI, ++ .access_method_fanspeed = ACCESS_METHOD_WMI, ++ .access_method_temperature = ACCESS_METHOD_WMI, ++ .access_method_fancurve = ACCESS_METHOD_EC, ++ .access_method_fanfullspeed = ACCESS_METHOD_WMI, ++ .acpi_check_dev = false, ++ .ramio_physical_start = 0xFE00D400, ++ .ramio_size = 0x600 ++}; ++ ++static const struct model_config model_jncn = { ++ .registers = &ec_register_offsets_v1, ++ .check_embedded_controller_id = false, ++ .embedded_controller_id = 0x8227, ++ .memoryio_physical_ec_start = 0xC400, ++ .memoryio_size = 0x300, ++ .has_minifancurve = false, ++ .has_custom_powermode = false, ++ .access_method_powermode = ACCESS_METHOD_WMI, ++ .access_method_keyboard = ACCESS_METHOD_NO_ACCESS, ++ .access_method_fanspeed = ACCESS_METHOD_WMI, ++ .access_method_temperature = ACCESS_METHOD_WMI, ++ .access_method_fancurve = ACCESS_METHOD_NO_ACCESS, ++ .access_method_fanfullspeed = ACCESS_METHOD_WMI, ++ .acpi_check_dev = false, ++ .ramio_physical_start = 0xFC7E0800, ++ .ramio_size = 0x600 ++}; ++ ++// Yoga Model! ++static const struct model_config model_j1cn = { ++ .registers = &ec_register_offsets_v0, ++ .check_embedded_controller_id = true, ++ .embedded_controller_id = 0x8227, ++ .memoryio_physical_ec_start = 0xC400, ++ .memoryio_size = 0x300, ++ .has_minifancurve = true, ++ .has_custom_powermode = true, ++ .access_method_powermode = ACCESS_METHOD_WMI, ++ .access_method_keyboard = ACCESS_METHOD_WMI, ++ .access_method_fanspeed = ACCESS_METHOD_EC, ++ .access_method_temperature = ACCESS_METHOD_EC, ++ .access_method_fancurve = ACCESS_METHOD_EC, ++ .access_method_fanfullspeed = ACCESS_METHOD_WMI, ++ .acpi_check_dev = true, ++ .ramio_physical_start = 0xFE0B0400, ++ .ramio_size = 0x600 ++}; ++ ++// Yoga Model! ++static const struct model_config model_dmcn = { ++ .registers = &ec_register_offsets_v0, ++ .check_embedded_controller_id = true, ++ .embedded_controller_id = 0x8227, ++ .memoryio_physical_ec_start = 0xC400, ++ .memoryio_size = 0x300, ++ .has_minifancurve = true, ++ .has_custom_powermode = true, ++ .access_method_powermode = ACCESS_METHOD_WMI, ++ .access_method_keyboard = ACCESS_METHOD_WMI, ++ .access_method_fanspeed = ACCESS_METHOD_EC, ++ .access_method_temperature = ACCESS_METHOD_EC, ++ .access_method_fancurve = ACCESS_METHOD_EC, ++ .access_method_fanfullspeed = ACCESS_METHOD_WMI, ++ .acpi_check_dev = true, ++ .ramio_physical_start = 0xFE700D00, ++ .ramio_size = 0x600 ++}; ++ ++// Yoga Model! ++static const struct model_config model_khcn = { ++ .registers = &ec_register_offsets_v0, ++ .check_embedded_controller_id = false, ++ .embedded_controller_id = 0x8227, ++ .memoryio_physical_ec_start = 0xC400, ++ .memoryio_size = 0x300, ++ .has_minifancurve = true, ++ .has_custom_powermode = true, ++ .access_method_powermode = ACCESS_METHOD_EC, ++ .access_method_keyboard = ACCESS_METHOD_WMI, ++ .access_method_fanspeed = ACCESS_METHOD_EC, ++ .access_method_temperature = ACCESS_METHOD_EC, ++ .access_method_fancurve = ACCESS_METHOD_EC, ++ .access_method_fanfullspeed = ACCESS_METHOD_WMI, ++ .acpi_check_dev = false, ++ .ramio_physical_start = 0xFE0B0400, ++ .ramio_size = 0x600 ++}; ++ ++ ++static const struct dmi_system_id denylist[] = { {} }; ++ ++static const struct dmi_system_id optimistic_allowlist[] = { ++ { ++ // modelyear: 2021 ++ // generation: 6 ++ // name: Legion 5, Legion 5 pro, Legion 7 ++ // Family: Legion 5 15ACH6H, ... ++ .ident = "GKCN", ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), ++ DMI_MATCH(DMI_BIOS_VERSION, "GKCN"), ++ }, ++ .driver_data = (void *)&model_v0 ++ }, ++ { ++ // modelyear: 2020 ++ .ident = "EUCN", ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), ++ DMI_MATCH(DMI_BIOS_VERSION, "EUCN"), ++ }, ++ .driver_data = (void *)&model_eucn ++ }, ++ { ++ // modelyear: 2020 ++ .ident = "EFCN", ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), ++ DMI_MATCH(DMI_BIOS_VERSION, "EFCN"), ++ }, ++ .driver_data = (void *)&model_v0 ++ }, ++ { ++ // modelyear: 2020 ++ .ident = "FSCN", ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), ++ DMI_MATCH(DMI_BIOS_VERSION, "FSCN"), ++ }, ++ .driver_data = (void *)&model_v0 ++ }, ++ { ++ // modelyear: 2021 ++ .ident = "HHCN", ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), ++ DMI_MATCH(DMI_BIOS_VERSION, "HHCN"), ++ }, ++ .driver_data = (void *)&model_v0 ++ }, ++ { ++ // modelyear: 2022 ++ .ident = "H1CN", ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), ++ DMI_MATCH(DMI_BIOS_VERSION, "H1CN"), ++ }, ++ .driver_data = (void *)&model_v0 ++ }, ++ { ++ // modelyear: 2022 ++ .ident = "J2CN", ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), ++ DMI_MATCH(DMI_BIOS_VERSION, "J2CN"), ++ }, ++ .driver_data = (void *)&model_v0 ++ }, ++ { ++ // modelyear: 2022 ++ .ident = "JUCN", ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), ++ DMI_MATCH(DMI_BIOS_VERSION, "JUCN"), ++ }, ++ .driver_data = (void *)&model_v0 ++ }, ++ { ++ // modelyear: 2022 ++ .ident = "KFCN", ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), ++ DMI_MATCH(DMI_BIOS_VERSION, "KFCN"), ++ }, ++ .driver_data = (void *)&model_kfcn ++ }, ++ { ++ // modelyear: 2021 ++ .ident = "HACN", ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), ++ DMI_MATCH(DMI_BIOS_VERSION, "HACN"), ++ }, ++ .driver_data = (void *)&model_hacn ++ }, ++ { ++ // modelyear: 2021 ++ .ident = "G9CN", ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), ++ DMI_MATCH(DMI_BIOS_VERSION, "G9CN"), ++ }, ++ .driver_data = (void *)&model_v0 ++ }, ++ { ++ // modelyear: 2022 ++ .ident = "K9CN", ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), ++ DMI_MATCH(DMI_BIOS_VERSION, "K9CN"), ++ }, ++ .driver_data = (void *)&model_k9cn ++ }, ++ { ++ // e.g. IdeaPad Gaming 3 15ARH05 ++ .ident = "FCCN", ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), ++ DMI_MATCH(DMI_BIOS_VERSION, "FCCN"), ++ }, ++ .driver_data = (void *)&model_fccn ++ }, ++ { ++ // e.g. Ideapad Gaming 3 15ACH6 ++ .ident = "H3CN", ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), ++ DMI_MATCH(DMI_BIOS_VERSION, "H3CN"), ++ }, ++ .driver_data = (void *)&model_h3cn ++ }, ++ { ++ // e.g. IdeaPad Gaming 3 15ARH7 (2022) ++ .ident = "JNCN", ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), ++ DMI_MATCH(DMI_BIOS_VERSION, "JNCN"), ++ }, ++ .driver_data = (void *)&model_jncn ++ }, ++ { ++ // 2020, seems very different in ACPI dissassembly ++ .ident = "E9CN", ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), ++ DMI_MATCH(DMI_BIOS_VERSION, "E9CN"), ++ }, ++ .driver_data = (void *)&model_e9cn ++ }, ++ { ++ // e.g. Legion Y7000 (older version) ++ .ident = "8JCN", ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), ++ DMI_MATCH(DMI_BIOS_VERSION, "8JCN"), ++ }, ++ .driver_data = (void *)&model_8jcn ++ }, ++ { ++ // e.g. Legion 7i Pro 2023 ++ .ident = "KWCN", ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), ++ DMI_MATCH(DMI_BIOS_VERSION, "KWCN"), ++ }, ++ .driver_data = (void *)&model_kwcn ++ }, ++ { ++ // e.g. Legion Pro 5 2023 or R9000P ++ .ident = "LPCN", ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), ++ DMI_MATCH(DMI_BIOS_VERSION, "LPCN"), ++ }, ++ .driver_data = (void *)&model_lpcn ++ }, ++ { ++ // e.g. Lenovo Legion 5i/Y7000 2019 PG0 ++ .ident = "BHCN", ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), ++ DMI_MATCH(DMI_BIOS_VERSION, "BHCN"), ++ }, ++ .driver_data = (void *)&model_bhcn ++ }, ++ { ++ // e.g. Lenovo 7 16IAX7 ++ .ident = "K1CN", ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), ++ DMI_MATCH(DMI_BIOS_VERSION, "K1CN"), ++ }, ++ .driver_data = (void *)&model_k1cn ++ }, ++ { ++ // e.g. Legion Y720 ++ .ident = "4GCN", ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), ++ DMI_MATCH(DMI_BIOS_VERSION, "4GCN"), ++ }, ++ .driver_data = (void *)&model_4gcn ++ }, ++ { ++ // e.g. Legion Slim 5 16APH8 2023 ++ .ident = "M3CN", ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), ++ DMI_MATCH(DMI_BIOS_VERSION, "M3CN"), ++ }, ++ .driver_data = (void *)&model_lpcn ++ }, ++ { ++ // e.g. Legion Y7000p-1060 ++ .ident = "9VCN", ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), ++ DMI_MATCH(DMI_BIOS_VERSION, "9VCN"), ++ }, ++ .driver_data = (void *)&model_9vcn ++ }, ++ { ++ // e.g. Legion Y9000X ++ .ident = "JYCN", ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), ++ DMI_MATCH(DMI_BIOS_VERSION, "JYCN"), ++ }, ++ .driver_data = (void *)&model_v2022 ++ }, ++ { ++ // e.g. Legion Y740-15IRH, older model e.g. with GTX 1660 ++ .ident = "BVCN", ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), ++ DMI_MATCH(DMI_BIOS_VERSION, "BVCN"), ++ }, ++ .driver_data = (void *)&model_bvcn ++ }, ++ { ++ // e.g. Legion 5 Pro 16IAH7H with a RTX 3070 Ti ++ .ident = "J2CN", ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), ++ DMI_MATCH(DMI_BIOS_VERSION, "J2CN"), ++ }, ++ .driver_data = (void *)&model_j2cn ++ }, ++ { ++ // e.g. Lenovo Yoga 7 16IAH7 with GPU Intel DG2 Arc A370M ++ .ident = "J1CN", ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), ++ DMI_MATCH(DMI_BIOS_VERSION, "J1CN"), ++ }, ++ .driver_data = (void *)&model_j1cn ++ }, ++ { ++ // e.g. Legion Slim 5 16IRH8 (2023) with RTX 4070 ++ .ident = "M2CN", ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), ++ DMI_MATCH(DMI_BIOS_VERSION, "M2CN"), ++ }, ++ .driver_data = (void *)&model_m2cn ++ }, ++ { ++ // e.g. Yoga Slim 7-14ARE05 ++ .ident = "DMCN", ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), ++ DMI_MATCH(DMI_BIOS_VERSION, "DMCN"), ++ }, ++ .driver_data = (void *)&model_dmcn ++ }, ++ { ++ // e.g. Yoga Slim 7 Pro 14ARH7 ++ .ident = "KHCN", ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "LENOVO"), ++ DMI_MATCH(DMI_BIOS_VERSION, "KHCN"), ++ }, ++ .driver_data = (void *)&model_khcn ++ }, ++ {} ++}; ++ ++/* ================================= */ ++/* ACPI and WMI access */ ++/* ================================= */ ++ ++// function from ideapad-laptop.c ++static int eval_int(acpi_handle handle, const char *name, unsigned long *res) ++{ ++ unsigned long long result; ++ acpi_status status; ++ ++ status = acpi_evaluate_integer(handle, (char *)name, NULL, &result); ++ if (ACPI_FAILURE(status)) ++ return -EIO; ++ ++ *res = result; ++ ++ return 0; ++} ++ ++// function from ideapad-laptop.c ++static int exec_simple_method(acpi_handle handle, const char *name, ++ unsigned long arg) ++{ ++ acpi_status status = ++ acpi_execute_simple_method(handle, (char *)name, arg); ++ ++ return ACPI_FAILURE(status) ? -EIO : 0; ++} ++ ++// function from ideapad-laptop.c ++static int exec_sbmc(acpi_handle handle, unsigned long arg) ++{ ++ // \_SB.PCI0.LPC0.EC0.VPC0.SBMC ++ return exec_simple_method(handle, "VPC0.SBMC", arg); ++} ++ ++//static int eval_qcho(acpi_handle handle, unsigned long *res) ++//{ ++// // \_SB.PCI0.LPC0.EC0.QCHO ++// return eval_int(handle, "QCHO", res); ++//} ++ ++static int eval_gbmd(acpi_handle handle, unsigned long *res) ++{ ++ return eval_int(handle, "VPC0.GBMD", res); ++} ++ ++static int eval_spmo(acpi_handle handle, unsigned long *res) ++{ ++ // \_SB.PCI0.LPC0.EC0.QCHO ++ return eval_int(handle, "VPC0.BTSM", res); ++} ++ ++static int acpi_process_buffer_to_ints(const char *id_name, int id_nr, ++ acpi_status status, ++ struct acpi_buffer *out_buffer, u8 *res, ++ size_t ressize) ++{ ++ // seto to NULL call kfree on NULL if next function call fails ++ union acpi_object *out = NULL; ++ size_t i; ++ int error = 0; ++ ++ if (ACPI_FAILURE(status)) { ++ pr_info("ACPI evaluation error for: %s:%d\n", id_name, id_nr); ++ error = -EFAULT; ++ goto err; ++ } ++ ++ out = out_buffer->pointer; ++ if (!out) { ++ pr_info("Unexpected ACPI result for %s:%d\n", id_name, id_nr); ++ error = -AE_ERROR; ++ goto err; ++ } ++ ++ if (out->type != ACPI_TYPE_BUFFER || out->buffer.length != ressize) { ++ pr_info("Unexpected ACPI result for %s:%d: expected type %d but got %d; expected length %lu but got %u;\n", ++ id_name, id_nr, ACPI_TYPE_BUFFER, out->type, ressize, ++ out->buffer.length); ++ error = -AE_ERROR; ++ goto err; ++ } ++ pr_info("ACPI result for %s:%d: ACPI buffer length: %u\n", id_name, ++ id_nr, out->buffer.length); ++ ++ for (i = 0; i < ressize; ++i) ++ res[i] = out->buffer.pointer[i]; ++ error = 0; ++ ++err: ++ kfree(out); ++ return error; ++} ++ ++//static int exec_ints(acpi_handle handle, const char *method_name, ++// struct acpi_object_list *params, u8 *res, size_t ressize) ++//{ ++// acpi_status status; ++// struct acpi_buffer out_buffer = { ACPI_ALLOCATE_BUFFER, NULL }; ++ ++// status = acpi_evaluate_object(handle, (acpi_string)method_name, params, ++// &out_buffer); ++ ++// return acpi_process_buffer_to_ints(method_name, 0, status, &out_buffer, ++// res, ressize); ++//} ++ ++static int wmi_exec_ints(const char *guid, u8 instance, u32 method_id, ++ const struct acpi_buffer *params, u8 *res, ++ size_t ressize) ++{ ++ acpi_status status; ++ struct acpi_buffer out_buffer = { ACPI_ALLOCATE_BUFFER, NULL }; ++ ++ status = wmi_evaluate_method(guid, instance, method_id, params, ++ &out_buffer); ++ return acpi_process_buffer_to_ints(guid, method_id, status, &out_buffer, ++ res, ressize); ++} ++ ++static int wmi_exec_int(const char *guid, u8 instance, u32 method_id, ++ const struct acpi_buffer *params, unsigned long *res) ++{ ++ acpi_status status; ++ struct acpi_buffer out_buffer = { ACPI_ALLOCATE_BUFFER, NULL }; ++ // seto to NULL call kfree on NULL if next function call fails ++ union acpi_object *out = NULL; ++ int error = 0; ++ ++ status = wmi_evaluate_method(guid, instance, method_id, params, ++ &out_buffer); ++ ++ if (ACPI_FAILURE(status)) { ++ pr_info("WMI evaluation error for: %s:%d\n", guid, method_id); ++ error = -EFAULT; ++ goto err; ++ } ++ ++ out = out_buffer.pointer; ++ if (!out) { ++ pr_info("Unexpected ACPI result for %s:%d", guid, method_id); ++ error = -AE_ERROR; ++ goto err; ++ } ++ ++ if (out->type != ACPI_TYPE_INTEGER) { ++ pr_info("Unexpected ACPI result for %s:%d: expected type %d but got %d\n", ++ guid, method_id, ACPI_TYPE_INTEGER, out->type); ++ error = -AE_ERROR; ++ goto err; ++ } ++ ++ *res = out->integer.value; ++ error = 0; ++ ++err: ++ kfree(out); ++ return error; ++} ++ ++static int wmi_exec_noarg_int(const char *guid, u8 instance, u32 method_id, ++ unsigned long *res) ++{ ++ struct acpi_buffer params; ++ ++ params.length = 0; ++ params.pointer = NULL; ++ return wmi_exec_int(guid, instance, method_id, ¶ms, res); ++} ++ ++static int wmi_exec_noarg_ints(const char *guid, u8 instance, u32 method_id, ++ u8 *res, size_t ressize) ++{ ++ struct acpi_buffer params; ++ ++ params.length = 0; ++ params.pointer = NULL; ++ return wmi_exec_ints(guid, instance, method_id, ¶ms, res, ressize); ++} ++ ++static int wmi_exec_arg(const char *guid, u8 instance, u32 method_id, void *arg, ++ size_t arg_size) ++{ ++ struct acpi_buffer params; ++ acpi_status status; ++ ++ params.length = arg_size; ++ params.pointer = arg; ++ status = wmi_evaluate_method(guid, instance, method_id, ¶ms, NULL); ++ ++ if (ACPI_FAILURE(status)) ++ return -EIO; ++ return 0; ++} ++ ++/* ================================= */ ++/* Lenovo WMI config */ ++/* ================================= */ ++#define LEGION_WMI_GAMEZONE_GUID "887B54E3-DDDC-4B2C-8B88-68A26A8835D0" ++// GPU over clock ++#define WMI_METHOD_ID_ISSUPPORTGPUOC 4 ++ ++//Fan speed ++// only completely implemented only for some models here ++// often implemted also in other class and other method ++// below ++#define WMI_METHOD_ID_GETFAN1SPEED 8 ++#define WMI_METHOD_ID_GETFAN2SPEED 9 ++ ++// Version of ACPI ++#define WMI_METHOD_ID_GETVERSION 11 ++// Does it support CPU overclock? ++#define WMI_METHOD_ID_ISSUPPORTCPUOC 14 ++// Temperatures ++// only completely implemented only for some models here ++// often implemted also in other class and other method ++// below ++#define WMI_METHOD_ID_GETCPUTEMP 18 ++#define WMI_METHOD_ID_GETGPUTEMP 19 ++ ++// two state keyboard light ++#define WMI_METHOD_ID_GETKEYBOARDLIGHT 37 ++#define WMI_METHOD_ID_SETKEYBOARDLIGHT 36 ++// disable win key ++// 0 = win key enabled; 1 = win key disabled ++#define WMI_METHOD_ID_ISSUPPORTDISABLEWINKEY 21 ++#define WMI_METHOD_ID_GETWINKEYSTATUS 23 ++#define WMI_METHOD_ID_SETWINKEYSTATUS 22 ++// disable touchpad ++//0 = touchpad enabled; 1 = touchpad disabled ++#define WMI_METHOD_ID_ISSUPPORTDISABLETP 24 ++#define WMI_METHOD_ID_GETTPSTATUS 26 ++#define WMI_METHOD_ID_SETTPSTATUS 25 ++// gSync ++#define WMI_METHOD_ID_ISSUPPORTGSYNC 40 ++#define WMI_METHOD_ID_GETGSYNCSTATUS 41 ++#define WMI_METHOD_ID_SETGSYNCSTATUS 42 ++//smartFanMode = powermode ++#define WMI_METHOD_ID_ISSUPPORTSMARTFAN 49 ++#define WMI_METHOD_ID_GETSMARTFANMODE 45 ++#define WMI_METHOD_ID_SETSMARTFANMODE 44 ++// power charge mode ++#define WMI_METHOD_ID_GETPOWERCHARGEMODE 47 ++// overdrive of display to reduce latency ++// 0=off, 1=on ++#define WMI_METHOD_ID_ISSUPPORTOD 49 ++#define WMI_METHOD_ID_GETODSTATUS 50 ++#define WMI_METHOD_ID_SETODSTATUS 51 ++// thermal mode = power mode used for cooling ++#define WMI_METHOD_ID_GETTHERMALMODE 55 ++// get max frequency of core 0 ++#define WMI_METHOD_ID_GETCPUMAXFREQUENCY 60 ++// check if AC adapter has enough power to overclock ++#define WMI_METHOD_ID_ISACFITFOROC 62 ++// set iGPU (GPU packaged with CPU) state ++#define WMI_METHOD_ID_ISSUPPORTIGPUMODE 63 ++#define WMI_METHOD_ID_GETIGPUMODESTATUS 64 ++#define WMI_METHOD_ID_SETIGPUMODESTATUS 65 ++#define WMI_METHOD_ID_NOTIFYDGPUSTATUS 66 ++enum IGPUState { ++ IGPUState_default = 0, ++ IGPUState_iGPUOnly = 1, ++ IGPUState_auto = 2 ++}; ++ ++#define WMI_GUID_LENOVO_CPU_METHOD "14afd777-106f-4c9b-b334-d388dc7809be" ++#define WMI_METHOD_ID_CPU_GET_SUPPORT_OC_STATUS 15 ++#define WMI_METHOD_ID_CPU_GET_OC_STATUS 1 ++#define WMI_METHOD_ID_CPU_SET_OC_STATUS 2 ++ ++// ppt limit slow ++#define WMI_METHOD_ID_CPU_GET_SHORTTERM_POWERLIMIT 3 ++#define WMI_METHOD_ID_CPU_SET_SHORTTERM_POWERLIMIT 4 ++// ppt stapm ++#define WMI_METHOD_ID_CPU_GET_LONGTERM_POWERLIMIT 5 ++#define WMI_METHOD_ID_CPU_SET_LONGTERM_POWERLIMIT 6 ++// default power limit ++#define WMI_METHOD_ID_CPU_GET_DEFAULT_POWERLIMIT 7 ++// peak power limit ++#define WMI_METHOD_ID_CPU_GET_PEAK_POWERLIMIT 8 ++#define WMI_METHOD_ID_CPU_SET_PEAK_POWERLIMIT 9 ++// apu sppt powerlimit ++#define WMI_METHOD_ID_CPU_GET_APU_SPPT_POWERLIMIT 12 ++#define WMI_METHOD_ID_CPU_SET_APU_SPPT_POWERLIMIT 13 ++// cross loading powerlimit ++#define WMI_METHOD_ID_CPU_GET_CROSS_LOADING_POWERLIMIT 16 ++#define WMI_METHOD_ID_CPU_SET_CROSS_LOADING_POWERLIMIT 17 ++ ++#define WMI_GUID_LENOVO_GPU_METHOD "da7547f1-824d-405f-be79-d9903e29ced7" ++// overclock GPU possible ++#define WMI_METHOD_ID_GPU_GET_OC_STATUS 1 ++#define WMI_METHOD_ID_GPU_SET_OC_STATUS 2 ++// dynamic boost power ++#define WMI_METHOD_ID_GPU_GET_PPAB_POWERLIMIT 3 ++#define WMI_METHOD_ID_GPU_SET_PPAB_POWERLIMIT 4 ++// configurable TGP (power) ++#define WMI_METHOD_ID_GPU_GET_CTGP_POWERLIMIT 5 ++#define WMI_METHOD_ID_GPU_SET_CTGP_POWERLIMIT 6 ++// ppab/ctgp powerlimit ++#define WMI_METHOD_ID_GPU_GET_DEFAULT_PPAB_CTGP_POWERLIMIT 7 ++// temperature limit ++#define WMI_METHOD_ID_GPU_GET_TEMPERATURE_LIMIT 8 ++#define WMI_METHOD_ID_GPU_SET_TEMPERATURE_LIMIT 9 ++// boost clock ++#define WMI_METHOD_ID_GPU_GET_BOOST_CLOCK 10 ++ ++#define WMI_GUID_LENOVO_FAN_METHOD "92549549-4bde-4f06-ac04-ce8bf898dbaa" ++// set fan to maximal speed; dust cleaning mode ++// only works in custom power mode ++#define WMI_METHOD_ID_FAN_GET_FULLSPEED 1 ++#define WMI_METHOD_ID_FAN_SET_FULLSPEED 2 ++// max speed of fan ++#define WMI_METHOD_ID_FAN_GET_MAXSPEED 3 ++#define WMI_METHOD_ID_FAN_SET_MAXSPEED 4 ++// fan table in custom mode ++#define WMI_METHOD_ID_FAN_GET_TABLE 5 ++#define WMI_METHOD_ID_FAN_SET_TABLE 6 ++// get speed of fans ++#define WMI_METHOD_ID_FAN_GETCURRENTFANSPEED 7 ++// get temperatures of CPU and GPU used for controlling cooling ++#define WMI_METHOD_ID_FAN_GETCURRENTSENSORTEMPERATURE 8 ++ ++// do not implement following ++// #define WMI_METHOD_ID_Fan_SetCurrentFanSpeed 9 ++ ++#define LEGION_WMI_KBBACKLIGHT_GUID "8C5B9127-ECD4-4657-980F-851019F99CA5" ++// access the keyboard backlight with 3 states ++#define WMI_METHOD_ID_KBBACKLIGHTGET 0x1 ++#define WMI_METHOD_ID_KBBACKLIGHTSET 0x2 ++ ++// new method in newer methods to get or set most of the values ++// with the two methods GetFeatureValue or SetFeatureValue. ++// They are called like GetFeatureValue(feature_id) where ++// feature_id is a id for the feature ++#define LEGION_WMI_LENOVO_OTHER_METHOD_GUID \ ++ "dc2a8805-3a8c-41ba-a6f7-092e0089cd3b" ++#define WMI_METHOD_ID_GET_FEATURE_VALUE 17 ++#define WMI_METHOD_ID_SET_FEATURE_VALUE 18 ++ ++enum OtherMethodFeature { ++ OtherMethodFeature_U1 = 0x010000, //->PC00.LPCB.EC0.REJF ++ OtherMethodFeature_U2 = 0x0F0000, //->C00.PEG1.PXP._STA? ++ OtherMethodFeature_U3 = 0x030000, //->PC00.LPCB.EC0.FLBT? ++ OtherMethodFeature_CPU_SHORT_TERM_POWER_LIMIT = 0x01010000, ++ OtherMethodFeature_CPU_LONG_TERM_POWER_LIMIT = 0x01020000, ++ OtherMethodFeature_CPU_PEAK_POWER_LIMIT = 0x01030000, ++ OtherMethodFeature_CPU_TEMPERATURE_LIMIT = 0x01040000, ++ ++ OtherMethodFeature_APU_PPT_POWER_LIMIT = 0x01050000, ++ ++ OtherMethodFeature_CPU_CROSS_LOAD_POWER_LIMIT = 0x01060000, ++ OtherMethodFeature_CPU_L1_TAU = 0x01070000, ++ ++ OtherMethodFeature_GPU_POWER_BOOST = 0x02010000, ++ OtherMethodFeature_GPU_cTGP = 0x02020000, ++ OtherMethodFeature_GPU_TEMPERATURE_LIMIT = 0x02030000, ++ OtherMethodFeature_GPU_POWER_TARGET_ON_AC_OFFSET_FROM_BASELINE = ++ 0x02040000, ++ ++ OtherMethodFeature_FAN_SPEED_1 = 0x04030001, ++ OtherMethodFeature_FAN_SPEED_2 = 0x04030002, ++ ++ OtherMethodFeature_C_U1 = 0x05010000, ++ OtherMethodFeature_TEMP_CPU = 0x05040000, ++ OtherMethodFeature_TEMP_GPU = 0x05050000, ++}; ++ ++static ssize_t wmi_other_method_get_value(enum OtherMethodFeature feature_id, ++ int *value) ++{ ++ struct acpi_buffer params; ++ int error; ++ unsigned long res; ++ u32 param1 = feature_id; ++ ++ params.length = sizeof(param1); ++ params.pointer = ¶m1; ++ error = wmi_exec_int(LEGION_WMI_LENOVO_OTHER_METHOD_GUID, 0, ++ WMI_METHOD_ID_GET_FEATURE_VALUE, ¶ms, &res); ++ if (!error) ++ *value = res; ++ return error; ++} ++ ++/* =================================== */ ++/* EC RAM Access with memory mapped IO */ ++/* =================================== */ ++ ++struct ecram_memoryio { ++ // TODO: start of remapped memory in EC RAM is assumed to be 0 ++ // u16 ecram_start; ++ ++ // physical address of remapped IO, depends on model and firmware ++ phys_addr_t physical_start; ++ // start adress of region in ec memory ++ phys_addr_t physical_ec_start; ++ // virtual address of remapped IO ++ u8 *virtual_start; ++ // size of remapped access ++ size_t size; ++}; ++ ++/** ++ * physical_start : corresponds to EC RAM 0 inside EC ++ * size: size of remapped region ++ * ++ * strong exception safety ++ */ ++static ssize_t ecram_memoryio_init(struct ecram_memoryio *ec_memoryio, ++ phys_addr_t physical_start, ++ phys_addr_t physical_ec_start, size_t size) ++{ ++ void *virtual_start = ioremap(physical_start, size); ++ ++ if (!IS_ERR_OR_NULL(virtual_start)) { ++ ec_memoryio->virtual_start = virtual_start; ++ ec_memoryio->physical_start = physical_start; ++ ec_memoryio->physical_ec_start = physical_ec_start; ++ ec_memoryio->size = size; ++ pr_info("Succeffuly mapped embedded controller: 0x%llx (in RAM)/0x%llx (in EC) to virtual 0x%p\n", ++ ec_memoryio->physical_start, ++ ec_memoryio->physical_ec_start, ++ ec_memoryio->virtual_start); ++ } else { ++ pr_info("Error mapping embedded controller memory at 0x%llx\n", ++ physical_start); ++ return -ENOMEM; ++ } ++ return 0; ++} ++ ++static void ecram_memoryio_exit(struct ecram_memoryio *ec_memoryio) ++{ ++ if (ec_memoryio->virtual_start != NULL) { ++ pr_info("Unmapping embedded controller memory at 0x%llx (in RAM)/0x%llx (in EC) at virtual 0x%p\n", ++ ec_memoryio->physical_start, ++ ec_memoryio->physical_ec_start, ++ ec_memoryio->virtual_start); ++ iounmap(ec_memoryio->virtual_start); ++ ec_memoryio->virtual_start = NULL; ++ } ++} ++ ++/* Read a byte from the EC RAM. ++ * ++ * Return status because of commong signature for alle ++ * methods to access EC RAM. ++ */ ++static ssize_t ecram_memoryio_read(const struct ecram_memoryio *ec_memoryio, ++ u16 ec_offset, u8 *value) ++{ ++ if (ec_offset < ec_memoryio->physical_ec_start) { ++ pr_info("Unexpected read at offset %d into EC RAM\n", ++ ec_offset); ++ return -1; ++ } ++ *value = *(ec_memoryio->virtual_start + ++ (ec_offset - ec_memoryio->physical_ec_start)); ++ return 0; ++} ++ ++/* Write a byte to the EC RAM. ++ * ++ * Return status because of commong signature for alle ++ * methods to access EC RAM. ++ */ ++ssize_t ecram_memoryio_write(const struct ecram_memoryio *ec_memoryio, ++ u16 ec_offset, u8 value) ++{ ++ if (ec_offset < ec_memoryio->physical_ec_start) { ++ pr_info("Unexpected write at offset %d into EC RAM\n", ++ ec_offset); ++ return -1; ++ } ++ *(ec_memoryio->virtual_start + ++ (ec_offset - ec_memoryio->physical_ec_start)) = value; ++ return 0; ++} ++ ++/* ================================= */ ++/* EC RAM Access with port-mapped IO */ ++/* ================================= */ ++ ++/* ++ * See datasheet of e.g. IT8502E/F/G, e.g. ++ * 6.2 Plug and Play Configuration (PNPCFG) ++ * ++ * Depending on configured BARDSEL register ++ * the ports ++ * ECRAM_PORTIO_ADDR_PORT and ++ * ECRAM_PORTIO_DATA_PORT ++ * are configured. ++ * ++ * By performing IO on these ports one can ++ * read/write to registers in the EC. ++ * ++ * "To access a register of PNPCFG, write target index to ++ * address port and access this PNPCFG register via ++ * data port" [datasheet, 6.2 Plug and Play Configuration] ++ */ ++ ++// IO ports used to write to communicate with embedded controller ++// Start of used ports ++#define ECRAM_PORTIO_START_PORT 0x4E ++// Number of used ports ++#define ECRAM_PORTIO_PORTS_SIZE 2 ++// Port used to specify address in EC RAM to read/write ++// 0x4E/0x4F is the usual port for IO super controler ++// 0x2E/0x2F also common (ITE can also be configure to use these) ++#define ECRAM_PORTIO_ADDR_PORT 0x4E ++// Port to send/receive the value to write/read ++#define ECRAM_PORTIO_DATA_PORT 0x4F ++// Name used to request ports ++#define ECRAM_PORTIO_NAME "legion" ++ ++struct ecram_portio { ++ /* protects read/write to EC RAM performed ++ * as a certain sequence of outb, inb ++ * commands on the IO ports. There can ++ * be at most one. ++ */ ++ struct mutex io_port_mutex; ++}; ++ ++static ssize_t ecram_portio_init(struct ecram_portio *ec_portio) ++{ ++ if (!request_region(ECRAM_PORTIO_START_PORT, ECRAM_PORTIO_PORTS_SIZE, ++ ECRAM_PORTIO_NAME)) { ++ pr_info("Cannot init ecram_portio the %x ports starting at %x\n", ++ ECRAM_PORTIO_PORTS_SIZE, ECRAM_PORTIO_START_PORT); ++ return -ENODEV; ++ } ++ //pr_info("Reserved %x ports starting at %x\n", ECRAM_PORTIO_PORTS_SIZE, ECRAM_PORTIO_START_PORT); ++ mutex_init(&ec_portio->io_port_mutex); ++ return 0; ++} ++ ++static void ecram_portio_exit(struct ecram_portio *ec_portio) ++{ ++ release_region(ECRAM_PORTIO_START_PORT, ECRAM_PORTIO_PORTS_SIZE); ++} ++ ++/* Read a byte from the EC RAM. ++ * ++ * Return status because of commong signature for alle ++ * methods to access EC RAM. ++ */ ++static ssize_t ecram_portio_read(struct ecram_portio *ec_portio, u16 offset, ++ u8 *value) ++{ ++ mutex_lock(&ec_portio->io_port_mutex); ++ ++ outb(0x2E, ECRAM_PORTIO_ADDR_PORT); ++ outb(0x11, ECRAM_PORTIO_DATA_PORT); ++ outb(0x2F, ECRAM_PORTIO_ADDR_PORT); ++ // TODO: no explicit cast between types seems to be sometimes ++ // done and sometimes not ++ outb((u8)((offset >> 8) & 0xFF), ECRAM_PORTIO_DATA_PORT); ++ ++ outb(0x2E, ECRAM_PORTIO_ADDR_PORT); ++ outb(0x10, ECRAM_PORTIO_DATA_PORT); ++ outb(0x2F, ECRAM_PORTIO_ADDR_PORT); ++ outb((u8)(offset & 0xFF), ECRAM_PORTIO_DATA_PORT); ++ ++ outb(0x2E, ECRAM_PORTIO_ADDR_PORT); ++ outb(0x12, ECRAM_PORTIO_DATA_PORT); ++ outb(0x2F, ECRAM_PORTIO_ADDR_PORT); ++ *value = inb(ECRAM_PORTIO_DATA_PORT); ++ ++ mutex_unlock(&ec_portio->io_port_mutex); ++ return 0; ++} ++ ++/* Write a byte to the EC RAM. ++ * ++ * Return status because of commong signature for alle ++ * methods to access EC RAM. ++ */ ++static ssize_t ecram_portio_write(struct ecram_portio *ec_portio, u16 offset, ++ u8 value) ++{ ++ mutex_lock(&ec_portio->io_port_mutex); ++ ++ outb(0x2E, ECRAM_PORTIO_ADDR_PORT); ++ outb(0x11, ECRAM_PORTIO_DATA_PORT); ++ outb(0x2F, ECRAM_PORTIO_ADDR_PORT); ++ // TODO: no explicit cast between types seems to be sometimes ++ // done and sometimes not ++ outb((u8)((offset >> 8) & 0xFF), ECRAM_PORTIO_DATA_PORT); ++ ++ outb(0x2E, ECRAM_PORTIO_ADDR_PORT); ++ outb(0x10, ECRAM_PORTIO_DATA_PORT); ++ outb(0x2F, ECRAM_PORTIO_ADDR_PORT); ++ outb((u8)(offset & 0xFF), ECRAM_PORTIO_DATA_PORT); ++ ++ outb(0x2E, ECRAM_PORTIO_ADDR_PORT); ++ outb(0x12, ECRAM_PORTIO_DATA_PORT); ++ outb(0x2F, ECRAM_PORTIO_ADDR_PORT); ++ outb(value, ECRAM_PORTIO_DATA_PORT); ++ ++ mutex_unlock(&ec_portio->io_port_mutex); ++ // TODO: remove this ++ //pr_info("Writing %d to addr %x\n", value, offset); ++ return 0; ++} ++ ++/* =================================== */ ++/* EC RAM Access */ ++/* =================================== */ ++ ++struct ecram { ++ struct ecram_portio portio; ++}; ++ ++static ssize_t ecram_init(struct ecram *ecram, ++ phys_addr_t memoryio_ec_physical_start, ++ size_t region_size) ++{ ++ ssize_t err; ++ ++ err = ecram_portio_init(&ecram->portio); ++ if (err) { ++ pr_info("Failed ecram_portio_init\n"); ++ goto err_ecram_portio_init; ++ } ++ ++ return 0; ++ ++err_ecram_portio_init: ++ return err; ++} ++ ++static void ecram_exit(struct ecram *ecram) ++{ ++ pr_info("Unloading legion ecram\n"); ++ ecram_portio_exit(&ecram->portio); ++ pr_info("Unloading legion ecram done\n"); ++} ++ ++/** Read from EC RAM ++ * ecram_offset address on the EC ++ */ ++static u8 ecram_read(struct ecram *ecram, u16 ecram_offset) ++{ ++ u8 value; ++ int err; ++ ++ err = ecram_portio_read(&ecram->portio, ecram_offset, &value); ++ if (err) ++ pr_info("Error reading EC RAM at 0x%x\n", ecram_offset); ++ return value; ++} ++ ++static void ecram_write(struct ecram *ecram, u16 ecram_offset, u8 value) ++{ ++ int err; ++ ++ if (ec_readonly) { ++ pr_info("Skipping writing EC RAM at 0x%x because readonly.\n", ++ ecram_offset); ++ return; ++ } ++ err = ecram_portio_write(&ecram->portio, ecram_offset, value); ++ if (err) ++ pr_info("Error writing EC RAM at 0x%x\n", ecram_offset); ++} ++ ++/* =============================== */ ++/* Reads from EC */ ++/* =============================== */ ++ ++static u16 read_ec_id(struct ecram *ecram, const struct model_config *model) ++{ ++ u8 id1 = ecram_read(ecram, model->registers->ECHIPID1); ++ u8 id2 = ecram_read(ecram, model->registers->ECHIPID2); ++ ++ return (id1 << 8) + id2; ++} ++ ++static u16 read_ec_version(struct ecram *ecram, ++ const struct model_config *model) ++{ ++ u8 vers = ecram_read(ecram, model->registers->ECHIPVER); ++ u8 debug = ecram_read(ecram, model->registers->ECDEBUG); ++ ++ return (vers << 8) + debug; ++} ++ ++/* ============================= */ ++/* Data model for sensor values */ ++/* ============================= */ ++ ++struct sensor_values { ++ u16 fan1_rpm; // current speed in rpm of fan 1 ++ u16 fan2_rpm; // current speed in rpm of fan2 ++ u16 fan1_target_rpm; // target speed in rpm of fan 1 ++ u16 fan2_target_rpm; // target speed in rpm of fan 2 ++ u8 cpu_temp_celsius; // cpu temperature in celcius ++ u8 gpu_temp_celsius; // gpu temperature in celcius ++ u8 ic_temp_celsius; // ic temperature in celcius ++}; ++ ++enum SENSOR_ATTR { ++ SENSOR_CPU_TEMP_ID = 1, ++ SENSOR_GPU_TEMP_ID = 2, ++ SENSOR_IC_TEMP_ID = 3, ++ SENSOR_FAN1_RPM_ID = 4, ++ SENSOR_FAN2_RPM_ID = 5, ++ SENSOR_FAN1_TARGET_RPM_ID = 6, ++ SENSOR_FAN2_TARGET_RPM_ID = 7 ++}; ++ ++/* ============================= */ ++/* Data model for fan curve */ ++/* ============================= */ ++ ++struct fancurve_point { ++ // rpm1 devided by 100 ++ u8 rpm1_raw; ++ // rpm2 devided by 100 ++ u8 rpm2_raw; ++ // >=2 , <=5 (lower is faster); must be increasing by level ++ u8 accel; ++ // >=2 , <=5 (lower is faster); must be increasing by level ++ u8 decel; ++ ++ // min must be lower or equal than max ++ // last level max must be 127 ++ // <=127 cpu max temp for this level; must be increasing by level ++ u8 cpu_max_temp_celsius; ++ // <=127 cpu min temp for this level; must be increasing by level ++ u8 cpu_min_temp_celsius; ++ // <=127 gpu min temp for this level; must be increasing by level ++ u8 gpu_max_temp_celsius; ++ // <=127 gpu max temp for this level; must be increasing by level ++ u8 gpu_min_temp_celsius; ++ // <=127 ic max temp for this level; must be increasing by level ++ u8 ic_max_temp_celsius; ++ // <=127 ic max temp for this level; must be increasing by level ++ u8 ic_min_temp_celsius; ++}; ++ ++enum FANCURVE_ATTR { ++ FANCURVE_ATTR_PWM1 = 1, ++ FANCURVE_ATTR_PWM2 = 2, ++ FANCURVE_ATTR_CPU_TEMP = 3, ++ FANCURVE_ATTR_CPU_HYST = 4, ++ FANCURVE_ATTR_GPU_TEMP = 5, ++ FANCURVE_ATTR_GPU_HYST = 6, ++ FANCURVE_ATTR_IC_TEMP = 7, ++ FANCURVE_ATTR_IC_HYST = 8, ++ FANCURVE_ATTR_ACCEL = 9, ++ FANCURVE_ATTR_DECEL = 10, ++ FANCURVE_SIZE = 11, ++ FANCURVE_MINIFANCURVE_ON_COOL = 12 ++}; ++ ++// used for clearing table entries ++static const struct fancurve_point fancurve_point_zero = { 0, 0, 0, 0, 0, ++ 0, 0, 0, 0, 0 }; ++ ++struct fancurve { ++ struct fancurve_point points[MAXFANCURVESIZE]; ++ // number of points used; must be <= MAXFANCURVESIZE ++ size_t size; ++ // the point that at which fans are run currently ++ size_t current_point_i; ++}; ++ ++// validation functions ++ ++static bool fancurve_is_valid_min_temp(int min_temp) ++{ ++ return min_temp >= 0 && min_temp <= 127; ++} ++ ++static bool fancurve_is_valid_max_temp(int max_temp) ++{ ++ return max_temp >= 0 && max_temp <= 127; ++} ++ ++// setters with validation ++// - make hwmon implementation easier ++// - keep fancurve valid, otherwise EC will not properly control fan ++ ++static bool fancurve_set_rpm1(struct fancurve *fancurve, int point_id, int rpm) ++{ ++ bool valid = point_id == 0 ? rpm == 0 : (rpm >= 0 && rpm <= 4500); ++ ++ if (valid) ++ fancurve->points[point_id].rpm1_raw = rpm / 100; ++ return valid; ++} ++ ++static bool fancurve_set_rpm2(struct fancurve *fancurve, int point_id, int rpm) ++{ ++ bool valid = point_id == 0 ? rpm == 0 : (rpm >= 0 && rpm <= 4500); ++ ++ if (valid) ++ fancurve->points[point_id].rpm2_raw = rpm / 100; ++ return valid; ++} ++ ++// TODO: remove { ... } from single line if body ++ ++static bool fancurve_set_accel(struct fancurve *fancurve, int point_id, ++ int accel) ++{ ++ bool valid = accel >= 2 && accel <= 5; ++ ++ if (valid) ++ fancurve->points[point_id].accel = accel; ++ return valid; ++} ++ ++static bool fancurve_set_decel(struct fancurve *fancurve, int point_id, ++ int decel) ++{ ++ bool valid = decel >= 2 && decel <= 5; ++ ++ if (valid) ++ fancurve->points[point_id].decel = decel; ++ return valid; ++} ++ ++static bool fancurve_set_cpu_temp_max(struct fancurve *fancurve, int point_id, ++ int value) ++{ ++ bool valid = fancurve_is_valid_max_temp(value); ++ ++ if (valid) ++ fancurve->points[point_id].cpu_max_temp_celsius = value; ++ ++ return valid; ++} ++ ++static bool fancurve_set_gpu_temp_max(struct fancurve *fancurve, int point_id, ++ int value) ++{ ++ bool valid = fancurve_is_valid_max_temp(value); ++ ++ if (valid) ++ fancurve->points[point_id].gpu_max_temp_celsius = value; ++ return valid; ++} ++ ++static bool fancurve_set_ic_temp_max(struct fancurve *fancurve, int point_id, ++ int value) ++{ ++ bool valid = fancurve_is_valid_max_temp(value); ++ ++ if (valid) ++ fancurve->points[point_id].ic_max_temp_celsius = value; ++ return valid; ++} ++ ++static bool fancurve_set_cpu_temp_min(struct fancurve *fancurve, int point_id, ++ int value) ++{ ++ bool valid = fancurve_is_valid_max_temp(value); ++ ++ if (valid) ++ fancurve->points[point_id].cpu_min_temp_celsius = value; ++ return valid; ++} ++ ++static bool fancurve_set_gpu_temp_min(struct fancurve *fancurve, int point_id, ++ int value) ++{ ++ bool valid = fancurve_is_valid_min_temp(value); ++ ++ if (valid) ++ fancurve->points[point_id].gpu_min_temp_celsius = value; ++ return valid; ++} ++ ++static bool fancurve_set_ic_temp_min(struct fancurve *fancurve, int point_id, ++ int value) ++{ ++ bool valid = fancurve_is_valid_min_temp(value); ++ ++ if (valid) ++ fancurve->points[point_id].ic_min_temp_celsius = value; ++ return valid; ++} ++ ++static bool fancurve_set_size(struct fancurve *fancurve, int size, ++ bool init_values) ++{ ++ bool valid = size >= 1 && size <= MAXFANCURVESIZE; ++ ++ if (!valid) ++ return false; ++ if (init_values && size < fancurve->size) { ++ // fancurve size is decreased, but last etnry alwasy needs 127 temperatures ++ // Note: size >=1 ++ fancurve->points[size - 1].cpu_max_temp_celsius = 127; ++ fancurve->points[size - 1].ic_max_temp_celsius = 127; ++ fancurve->points[size - 1].gpu_max_temp_celsius = 127; ++ } ++ if (init_values && size > fancurve->size) { ++ // fancurve increased, so new entries need valid values ++ int i; ++ int last = fancurve->size > 0 ? fancurve->size - 1 : 0; ++ ++ for (i = fancurve->size; i < size; ++i) ++ fancurve->points[i] = fancurve->points[last]; ++ } ++ return true; ++} ++ ++static ssize_t fancurve_print_seqfile(const struct fancurve *fancurve, ++ struct seq_file *s) ++{ ++ int i; ++ ++ seq_printf( ++ s, ++ "rpm1|rpm2|acceleration|deceleration|cpu_min_temp|cpu_max_temp|gpu_min_temp|gpu_max_temp|ic_min_temp|ic_max_temp\n"); ++ for (i = 0; i < fancurve->size; ++i) { ++ const struct fancurve_point *point = &fancurve->points[i]; ++ ++ seq_printf( ++ s, "%d\t %d\t %d\t %d\t %d\t %d\t %d\t %d\t %d\t %d\n", ++ point->rpm1_raw * 100, point->rpm2_raw * 100, ++ point->accel, point->decel, point->cpu_min_temp_celsius, ++ point->cpu_max_temp_celsius, ++ point->gpu_min_temp_celsius, ++ point->gpu_max_temp_celsius, point->ic_min_temp_celsius, ++ point->ic_max_temp_celsius); ++ } ++ return 0; ++} ++ ++struct light { ++ bool initialized; ++ struct led_classdev led; ++ unsigned int last_brightness; ++ u8 light_id; ++ unsigned int lower_limit; ++ unsigned int upper_limit; ++}; ++ ++/* ============================= */ ++/* Global and shared data between */ ++/* all calls to this module */ ++/* ============================= */ ++// Implemented like ideapad-laptop.c but currenlty still ++// wihtout dynamic memory allocation (instead global _priv) ++struct legion_private { ++ struct platform_device *platform_device; ++ // TODO: remove or keep? init? ++ struct acpi_device *adev; ++ ++ // Method to access ECRAM ++ struct ecram ecram; ++ // Configuration with registers an ECRAM access method ++ const struct model_config *conf; ++ ++ // TODO: maybe refactor an keep only local to each function ++ // last known fan curve ++ struct fancurve fancurve; ++ // configured fan curve from user space ++ struct fancurve fancurve_configured; ++ ++ // update lock, when partial values of fancurve are changed ++ struct mutex fancurve_mutex; ++ ++ //interfaces ++ struct dentry *debugfs_dir; ++ struct device *hwmon_dev; ++ struct platform_profile_handler platform_profile_handler; ++ ++ struct light kbd_bl; ++ struct light ylogo_light; ++ struct light iport_light; ++ ++ // TODO: remove? ++ bool loaded; ++ ++ // TODO: remove, only for reverse enginnering ++ struct ecram_memoryio ec_memoryio; ++}; ++ ++// shared between different drivers: WMI, platform and proteced by mutex ++static struct legion_private *legion_shared; ++static struct legion_private _priv; ++static DEFINE_MUTEX(legion_shared_mutex); ++ ++static int legion_shared_init(struct legion_private *priv) ++{ ++ int ret; ++ ++ mutex_lock(&legion_shared_mutex); ++ ++ if (!legion_shared) { ++ legion_shared = priv; ++ mutex_init(&legion_shared->fancurve_mutex); ++ ret = 0; ++ } else { ++ pr_warn("Found multiple platform devices\n"); ++ ret = -EINVAL; ++ } ++ ++ priv->loaded = true; ++ mutex_unlock(&legion_shared_mutex); ++ ++ return ret; ++} ++ ++static void legion_shared_exit(struct legion_private *priv) ++{ ++ pr_info("Unloading legion shared\n"); ++ mutex_lock(&legion_shared_mutex); ++ ++ if (legion_shared == priv) ++ legion_shared = NULL; ++ ++ mutex_unlock(&legion_shared_mutex); ++ pr_info("Unloading legion shared done\n"); ++} ++ ++static int get_simple_wmi_attribute(struct legion_private *priv, ++ const char *guid, u8 instance, ++ u32 method_id, bool invert, ++ unsigned long scale, unsigned long *value) ++{ ++ unsigned long state = 0; ++ int err; ++ ++ if (scale == 0) { ++ pr_info("Scale cannot be 0\n"); ++ return -EINVAL; ++ } ++ err = wmi_exec_noarg_int(guid, instance, method_id, &state); ++ if (err) ++ return -EINVAL; ++ ++ // TODO: remove later ++ pr_info("%swith raw value: %ld\n", __func__, state); ++ ++ state = state * scale; ++ ++ if (invert) ++ state = !state; ++ *value = state; ++ return 0; ++} ++ ++static int get_simple_wmi_attribute_bool(struct legion_private *priv, ++ const char *guid, u8 instance, ++ u32 method_id, bool invert, ++ unsigned long scale, bool *value) ++{ ++ unsigned long int_val = *value; ++ int err = get_simple_wmi_attribute(priv, guid, instance, method_id, ++ invert, scale, &int_val); ++ *value = int_val; ++ return err; ++} ++ ++static int set_simple_wmi_attribute(struct legion_private *priv, ++ const char *guid, u8 instance, ++ u32 method_id, bool invert, int scale, ++ int state) ++{ ++ int err; ++ u8 in_param; ++ ++ if (scale == 0) { ++ pr_info("Scale cannot be 0\n"); ++ return -EINVAL; ++ } ++ ++ if (invert) ++ state = !state; ++ ++ in_param = state / scale; ++ ++ err = wmi_exec_arg(guid, instance, method_id, &in_param, ++ sizeof(in_param)); ++ return err; ++} ++ ++/* ============================= */ ++/* Sensor values reading/writing */ ++/* ============================= */ ++ ++static int ec_read_sensor_values(struct ecram *ecram, ++ const struct model_config *model, ++ struct sensor_values *values) ++{ ++ values->fan1_target_rpm = ++ 100 * ecram_read(ecram, model->registers->EXT_FAN1_TARGET_RPM); ++ values->fan2_target_rpm = ++ 100 * ecram_read(ecram, model->registers->EXT_FAN2_TARGET_RPM); ++ ++ values->fan1_rpm = ++ ecram_read(ecram, model->registers->EXT_FAN1_RPM_LSB) + ++ (((int)ecram_read(ecram, model->registers->EXT_FAN1_RPM_MSB)) ++ << 8); ++ values->fan2_rpm = ++ ecram_read(ecram, model->registers->EXT_FAN2_RPM_LSB) + ++ (((int)ecram_read(ecram, model->registers->EXT_FAN2_RPM_MSB)) ++ << 8); ++ ++ values->cpu_temp_celsius = ++ ecram_read(ecram, model->registers->EXT_CPU_TEMP_INPUT); ++ values->gpu_temp_celsius = ++ ecram_read(ecram, model->registers->EXT_GPU_TEMP_INPUT); ++ values->ic_temp_celsius = ++ ecram_read(ecram, model->registers->EXT_IC_TEMP_INPUT); ++ ++ values->cpu_temp_celsius = ecram_read(ecram, 0xC5E6); ++ values->gpu_temp_celsius = ecram_read(ecram, 0xC5E7); ++ values->ic_temp_celsius = ecram_read(ecram, 0xC5E8); ++ ++ return 0; ++} ++ ++static ssize_t ec_read_temperature(struct ecram *ecram, ++ const struct model_config *model, ++ int sensor_id, int *temperature) ++{ ++ int err = 0; ++ unsigned long res; ++ ++ if (sensor_id == 0) { ++ res = ecram_read(ecram, 0xC5E6); ++ } else if (sensor_id == 1) { ++ res = ecram_read(ecram, 0xC5E7); ++ } else { ++ // TODO: use all correct error codes ++ return -EEXIST; ++ } ++ if (!err) ++ *temperature = res; ++ return err; ++} ++ ++static ssize_t ec_read_fanspeed(struct ecram *ecram, ++ const struct model_config *model, int fan_id, ++ int *fanspeed_rpm) ++{ ++ int err = 0; ++ unsigned long res; ++ ++ if (fan_id == 0) { ++ res = ecram_read(ecram, model->registers->EXT_FAN1_RPM_LSB) + ++ (((int)ecram_read(ecram, ++ model->registers->EXT_FAN1_RPM_MSB)) ++ << 8); ++ } else if (fan_id == 1) { ++ res = ecram_read(ecram, model->registers->EXT_FAN2_RPM_LSB) + ++ (((int)ecram_read(ecram, ++ model->registers->EXT_FAN2_RPM_MSB)) ++ << 8); ++ } else { ++ // TODO: use all correct error codes ++ return -EEXIST; ++ } ++ if (!err) ++ *fanspeed_rpm = res; ++ return err; ++} ++ ++// '\_SB.PCI0.LPC0.EC0.FANS ++#define ACPI_PATH_FAN_SPEED1 "FANS" ++// '\_SB.PCI0.LPC0.EC0.FA2S ++#define ACPI_PATH_FAN_SPEED2 "FA2S" ++ ++static ssize_t acpi_read_fanspeed(struct legion_private *priv, int fan_id, ++ int *value) ++{ ++ int err; ++ unsigned long acpi_value; ++ const char *acpi_path; ++ ++ if (fan_id == 0) { ++ acpi_path = ACPI_PATH_FAN_SPEED1; ++ } else if (fan_id == 1) { ++ acpi_path = ACPI_PATH_FAN_SPEED2; ++ } else { ++ // TODO: use all correct error codes ++ return -EEXIST; ++ } ++ err = eval_int(priv->adev->handle, acpi_path, &acpi_value); ++ if (!err) ++ *value = (int)acpi_value * 100; ++ return err; ++} ++ ++// '\_SB.PCI0.LPC0.EC0.CPUT ++#define ACPI_PATH_CPU_TEMP "CPUT" ++// '\_SB.PCI0.LPC0.EC0.GPUT ++#define ACPI_PATH_GPU_TEMP "GPUT" ++ ++static ssize_t acpi_read_temperature(struct legion_private *priv, int fan_id, ++ int *value) ++{ ++ int err; ++ unsigned long acpi_value; ++ const char *acpi_path; ++ ++ if (fan_id == 0) { ++ acpi_path = ACPI_PATH_CPU_TEMP; ++ } else if (fan_id == 1) { ++ acpi_path = ACPI_PATH_GPU_TEMP; ++ } else { ++ // TODO: use all correct error codes ++ return -EEXIST; ++ } ++ err = eval_int(priv->adev->handle, acpi_path, &acpi_value); ++ if (!err) ++ *value = (int)acpi_value; ++ return err; ++} ++ ++// fan_id: 0 or 1 ++static ssize_t wmi_read_fanspeed(int fan_id, int *fanspeed_rpm) ++{ ++ int err; ++ unsigned long res; ++ struct acpi_buffer params; ++ ++ params.length = 1; ++ params.pointer = &fan_id; ++ ++ err = wmi_exec_int(WMI_GUID_LENOVO_FAN_METHOD, 0, ++ WMI_METHOD_ID_FAN_GETCURRENTFANSPEED, ¶ms, &res); ++ ++ if (!err) ++ *fanspeed_rpm = res; ++ return err; ++} ++ ++//sensor_id: cpu = 0, gpu = 1 ++static ssize_t wmi_read_temperature(int sensor_id, int *temperature) ++{ ++ int err; ++ unsigned long res; ++ struct acpi_buffer params; ++ ++ if (sensor_id == 0) ++ sensor_id = 0x03; ++ else if (sensor_id == 1) ++ sensor_id = 0x04; ++ else { ++ // TODO: use all correct error codes ++ return -EEXIST; ++ } ++ ++ params.length = 1; ++ params.pointer = &sensor_id; ++ ++ err = wmi_exec_int(WMI_GUID_LENOVO_FAN_METHOD, 0, ++ WMI_METHOD_ID_FAN_GETCURRENTSENSORTEMPERATURE, ++ ¶ms, &res); ++ ++ if (!err) ++ *temperature = res; ++ return err; ++} ++ ++// fan_id: 0 or 1 ++static ssize_t wmi_read_fanspeed_gz(int fan_id, int *fanspeed_rpm) ++{ ++ int err; ++ u32 method_id; ++ unsigned long res; ++ ++ if (fan_id == 0) ++ method_id = WMI_METHOD_ID_GETFAN1SPEED; ++ else if (fan_id == 1) ++ method_id = WMI_METHOD_ID_GETFAN2SPEED; ++ else { ++ // TODO: use all correct error codes ++ return -EEXIST; ++ } ++ err = wmi_exec_noarg_int(LEGION_WMI_GAMEZONE_GUID, 0, method_id, &res); ++ ++ if (!err) ++ *fanspeed_rpm = res; ++ return err; ++} ++ ++//sensor_id: cpu = 0, gpu = 1 ++static ssize_t wmi_read_temperature_gz(int sensor_id, int *temperature) ++{ ++ int err; ++ u32 method_id; ++ unsigned long res; ++ ++ if (sensor_id == 0) ++ method_id = WMI_METHOD_ID_GETCPUTEMP; ++ else if (sensor_id == 1) ++ method_id = WMI_METHOD_ID_GETGPUTEMP; ++ else { ++ // TODO: use all correct error codes ++ return -EEXIST; ++ } ++ ++ err = wmi_exec_noarg_int(LEGION_WMI_GAMEZONE_GUID, 0, method_id, &res); ++ ++ if (!err) ++ *temperature = res; ++ return err; ++} ++ ++// fan_id: 0 or 1 ++static ssize_t wmi_read_fanspeed_other(int fan_id, int *fanspeed_rpm) ++{ ++ int err; ++ enum OtherMethodFeature featured_id; ++ int res; ++ ++ if (fan_id == 0) ++ featured_id = OtherMethodFeature_FAN_SPEED_1; ++ else if (fan_id == 1) ++ featured_id = OtherMethodFeature_FAN_SPEED_2; ++ else { ++ // TODO: use all correct error codes ++ return -EEXIST; ++ } ++ ++ err = wmi_other_method_get_value(featured_id, &res); ++ ++ if (!err) ++ *fanspeed_rpm = res; ++ return err; ++} ++ ++//sensor_id: cpu = 0, gpu = 1 ++static ssize_t wmi_read_temperature_other(int sensor_id, int *temperature) ++{ ++ int err; ++ enum OtherMethodFeature featured_id; ++ int res; ++ ++ if (sensor_id == 0) ++ featured_id = OtherMethodFeature_TEMP_CPU; ++ else if (sensor_id == 1) ++ featured_id = OtherMethodFeature_TEMP_GPU; ++ else { ++ // TODO: use all correct error codes ++ return -EEXIST; ++ } ++ ++ err = wmi_other_method_get_value(featured_id, &res); ++ if (!err) ++ *temperature = res; ++ return err; ++} ++ ++static ssize_t read_fanspeed(struct legion_private *priv, int fan_id, ++ int *speed_rpm) ++{ ++ // TODO: use enums or function pointers? ++ switch (priv->conf->access_method_fanspeed) { ++ case ACCESS_METHOD_EC: ++ return ec_read_fanspeed(&priv->ecram, priv->conf, fan_id, ++ speed_rpm); ++ case ACCESS_METHOD_ACPI: ++ return acpi_read_fanspeed(priv, fan_id, speed_rpm); ++ case ACCESS_METHOD_WMI: ++ return wmi_read_fanspeed_gz(fan_id, speed_rpm); ++ case ACCESS_METHOD_WMI2: ++ return wmi_read_fanspeed(fan_id, speed_rpm); ++ case ACCESS_METHOD_WMI3: ++ return wmi_read_fanspeed_other(fan_id, speed_rpm); ++ default: ++ pr_info("No access method for fanspeed: %d\n", ++ priv->conf->access_method_fanspeed); ++ return -EINVAL; ++ } ++} ++ ++static ssize_t read_temperature(struct legion_private *priv, int sensor_id, ++ int *temperature) ++{ ++ // TODO: use enums or function pointers? ++ switch (priv->conf->access_method_temperature) { ++ case ACCESS_METHOD_EC: ++ return ec_read_temperature(&priv->ecram, priv->conf, sensor_id, ++ temperature); ++ case ACCESS_METHOD_ACPI: ++ return acpi_read_temperature(priv, sensor_id, temperature); ++ case ACCESS_METHOD_WMI: ++ return wmi_read_temperature_gz(sensor_id, temperature); ++ case ACCESS_METHOD_WMI2: ++ return wmi_read_temperature(sensor_id, temperature); ++ case ACCESS_METHOD_WMI3: ++ return wmi_read_temperature_other(sensor_id, temperature); ++ default: ++ pr_info("No access method for temperature: %d\n", ++ priv->conf->access_method_temperature); ++ return -EINVAL; ++ } ++} ++ ++/* ============================= */ ++/* Fancurve reading/writing */ ++/* ============================= */ ++ ++/* Fancurve from WMI ++ * This allows changing fewer parameters. ++ * It is only available on newer models. ++ */ ++ ++struct WMIFanTable { ++ u8 FSTM; //FSMD ++ u8 FSID; ++ u32 FSTL; //FSST ++ u16 FSS0; ++ u16 FSS1; ++ u16 FSS2; ++ u16 FSS3; ++ u16 FSS4; ++ u16 FSS5; ++ u16 FSS6; ++ u16 FSS7; ++ u16 FSS8; ++ u16 FSS9; ++} __packed; ++ ++struct WMIFanTableRead { ++ u32 FSFL; ++ u32 FSS0; ++ u32 FSS1; ++ u32 FSS2; ++ u32 FSS3; ++ u32 FSS4; ++ u32 FSS5; ++ u32 FSS6; ++ u32 FSS7; ++ u32 FSS8; ++ u32 FSS9; ++ u32 FSSA; ++} __packed; ++ ++static ssize_t wmi_read_fancurve_custom(const struct model_config *model, ++ struct fancurve *fancurve) ++{ ++ u8 buffer[88]; ++ int err; ++ ++ // The output buffer from the ACPI call is 88 bytes and larger ++ // than the returned object ++ pr_info("Size of object: %lu\n", sizeof(struct WMIFanTableRead)); ++ err = wmi_exec_noarg_ints(WMI_GUID_LENOVO_FAN_METHOD, 0, ++ WMI_METHOD_ID_FAN_GET_TABLE, buffer, ++ sizeof(buffer)); ++ print_hex_dump(KERN_INFO, "legion_laptop fan table wmi buffer", ++ DUMP_PREFIX_ADDRESS, 16, 1, buffer, sizeof(buffer), ++ true); ++ if (!err) { ++ struct WMIFanTableRead *fantable = ++ (struct WMIFanTableRead *)&buffer[0]; ++ fancurve->current_point_i = 0; ++ fancurve->size = 10; ++ fancurve->points[0].rpm1_raw = fantable->FSS0; ++ fancurve->points[1].rpm1_raw = fantable->FSS1; ++ fancurve->points[2].rpm1_raw = fantable->FSS2; ++ fancurve->points[3].rpm1_raw = fantable->FSS3; ++ fancurve->points[4].rpm1_raw = fantable->FSS4; ++ fancurve->points[5].rpm1_raw = fantable->FSS5; ++ fancurve->points[6].rpm1_raw = fantable->FSS6; ++ fancurve->points[7].rpm1_raw = fantable->FSS7; ++ fancurve->points[8].rpm1_raw = fantable->FSS8; ++ fancurve->points[9].rpm1_raw = fantable->FSS9; ++ //fancurve->points[10].rpm1_raw = fantable->FSSA; ++ } ++ return err; ++} ++ ++static ssize_t wmi_write_fancurve_custom(const struct model_config *model, ++ const struct fancurve *fancurve) ++{ ++ u8 buffer[0x20]; ++ int err; ++ ++ // The buffer is read like this in ACPI firmware ++ // ++ // CreateByteField (Arg2, Zero, FSTM) ++ // CreateByteField (Arg2, One, FSID) ++ // CreateDWordField (Arg2, 0x02, FSTL) ++ // CreateByteField (Arg2, 0x06, FSS0) ++ // CreateByteField (Arg2, 0x08, FSS1) ++ // CreateByteField (Arg2, 0x0A, FSS2) ++ // CreateByteField (Arg2, 0x0C, FSS3) ++ // CreateByteField (Arg2, 0x0E, FSS4) ++ // CreateByteField (Arg2, 0x10, FSS5) ++ // CreateByteField (Arg2, 0x12, FSS6) ++ // CreateByteField (Arg2, 0x14, FSS7) ++ // CreateByteField (Arg2, 0x16, FSS8) ++ // CreateByteField (Arg2, 0x18, FSS9) ++ ++ memset(buffer, 0, sizeof(buffer)); ++ buffer[0x06] = fancurve->points[0].rpm1_raw; ++ buffer[0x08] = fancurve->points[1].rpm1_raw; ++ buffer[0x0A] = fancurve->points[2].rpm1_raw; ++ buffer[0x0C] = fancurve->points[3].rpm1_raw; ++ buffer[0x0E] = fancurve->points[4].rpm1_raw; ++ buffer[0x10] = fancurve->points[5].rpm1_raw; ++ buffer[0x12] = fancurve->points[6].rpm1_raw; ++ buffer[0x14] = fancurve->points[7].rpm1_raw; ++ buffer[0x16] = fancurve->points[8].rpm1_raw; ++ buffer[0x18] = fancurve->points[9].rpm1_raw; ++ ++ print_hex_dump(KERN_INFO, "legion_laptop fan table wmi write buffer", ++ DUMP_PREFIX_ADDRESS, 16, 1, buffer, sizeof(buffer), ++ true); ++ err = wmi_exec_arg(WMI_GUID_LENOVO_FAN_METHOD, 0, ++ WMI_METHOD_ID_FAN_SET_TABLE, buffer, sizeof(buffer)); ++ return err; ++} ++ ++/* Read the fan curve from the EC. ++ * ++ * In newer models (>=2022) there is an ACPI/WMI to read fan curve as ++ * a whole. So read/write fan table as a whole to use ++ * same interface for both cases. ++ * ++ * It reads all points from EC memory, even if stored fancurve is smaller, so ++ * it can contain 0 entries. ++ */ ++static int ec_read_fancurve_legion(struct ecram *ecram, ++ const struct model_config *model, ++ struct fancurve *fancurve) ++{ ++ size_t i = 0; ++ ++ for (i = 0; i < MAXFANCURVESIZE; ++i) { ++ struct fancurve_point *point = &fancurve->points[i]; ++ ++ point->rpm1_raw = ++ ecram_read(ecram, model->registers->EXT_FAN1_BASE + i); ++ point->rpm2_raw = ++ ecram_read(ecram, model->registers->EXT_FAN2_BASE + i); ++ ++ point->accel = ecram_read( ++ ecram, model->registers->EXT_FAN_ACC_BASE + i); ++ point->decel = ecram_read( ++ ecram, model->registers->EXT_FAN_DEC_BASE + i); ++ point->cpu_max_temp_celsius = ++ ecram_read(ecram, model->registers->EXT_CPU_TEMP + i); ++ point->cpu_min_temp_celsius = ecram_read( ++ ecram, model->registers->EXT_CPU_TEMP_HYST + i); ++ point->gpu_max_temp_celsius = ++ ecram_read(ecram, model->registers->EXT_GPU_TEMP + i); ++ point->gpu_min_temp_celsius = ecram_read( ++ ecram, model->registers->EXT_GPU_TEMP_HYST + i); ++ point->ic_max_temp_celsius = ++ ecram_read(ecram, model->registers->EXT_VRM_TEMP + i); ++ point->ic_min_temp_celsius = ecram_read( ++ ecram, model->registers->EXT_VRM_TEMP_HYST + i); ++ } ++ ++ // Do not trust that hardware; It might suddendly report ++ // a larger size, so clamp it. ++ fancurve->size = ++ ecram_read(ecram, model->registers->EXT_FAN_POINTS_SIZE); ++ fancurve->size = ++ min(fancurve->size, (typeof(fancurve->size))(MAXFANCURVESIZE)); ++ fancurve->current_point_i = ++ ecram_read(ecram, model->registers->EXT_FAN_CUR_POINT); ++ fancurve->current_point_i = ++ min(fancurve->current_point_i, fancurve->size); ++ return 0; ++} ++ ++static int ec_write_fancurve_legion(struct ecram *ecram, ++ const struct model_config *model, ++ const struct fancurve *fancurve, ++ bool write_size) ++{ ++ size_t i; ++ ++ //TODO: remove again ++ pr_info("Set fancurve\n"); ++ ++ // Reset fan update counters (try to avoid any race conditions) ++ ecram_write(ecram, 0xC5FE, 0); ++ ecram_write(ecram, 0xC5FF, 0); ++ for (i = 0; i < MAXFANCURVESIZE; ++i) { ++ // Entries for points larger than fancurve size should be cleared ++ // to 0 ++ const struct fancurve_point *point = ++ i < fancurve->size ? &fancurve->points[i] : ++ &fancurve_point_zero; ++ ++ ecram_write(ecram, model->registers->EXT_FAN1_BASE + i, ++ point->rpm1_raw); ++ ecram_write(ecram, model->registers->EXT_FAN2_BASE + i, ++ point->rpm2_raw); ++ ++ ecram_write(ecram, model->registers->EXT_FAN_ACC_BASE + i, ++ point->accel); ++ ecram_write(ecram, model->registers->EXT_FAN_DEC_BASE + i, ++ point->decel); ++ ++ ecram_write(ecram, model->registers->EXT_CPU_TEMP + i, ++ point->cpu_max_temp_celsius); ++ ecram_write(ecram, model->registers->EXT_CPU_TEMP_HYST + i, ++ point->cpu_min_temp_celsius); ++ ecram_write(ecram, model->registers->EXT_GPU_TEMP + i, ++ point->gpu_max_temp_celsius); ++ ecram_write(ecram, model->registers->EXT_GPU_TEMP_HYST + i, ++ point->gpu_min_temp_celsius); ++ ecram_write(ecram, model->registers->EXT_VRM_TEMP + i, ++ point->ic_max_temp_celsius); ++ ecram_write(ecram, model->registers->EXT_VRM_TEMP_HYST + i, ++ point->ic_min_temp_celsius); ++ } ++ ++ if (write_size) { ++ ecram_write(ecram, model->registers->EXT_FAN_POINTS_SIZE, ++ fancurve->size); ++ } ++ ++ // Reset current fan level to 0, so algorithm in EC ++ // selects fan curve point again and resetting hysterisis ++ // effects ++ ecram_write(ecram, model->registers->EXT_FAN_CUR_POINT, 0); ++ ++ // Reset internal fan levels ++ ecram_write(ecram, 0xC634, 0); // CPU ++ ecram_write(ecram, 0xC635, 0); // GPU ++ ecram_write(ecram, 0xC636, 0); // SENSOR ++ ++ return 0; ++} ++ ++#define FANCURVESIZE_IDEAPDAD 8 ++ ++static int ec_read_fancurve_ideapad(struct ecram *ecram, ++ const struct model_config *model, ++ struct fancurve *fancurve) ++{ ++ size_t i = 0; ++ ++ for (i = 0; i < FANCURVESIZE_IDEAPDAD; ++i) { ++ struct fancurve_point *point = &fancurve->points[i]; ++ ++ point->rpm1_raw = ++ ecram_read(ecram, model->registers->EXT_FAN1_BASE + i); ++ point->rpm2_raw = ++ ecram_read(ecram, model->registers->EXT_FAN2_BASE + i); ++ ++ point->accel = 0; ++ point->decel = 0; ++ point->cpu_max_temp_celsius = ++ ecram_read(ecram, model->registers->EXT_CPU_TEMP + i); ++ point->cpu_min_temp_celsius = ecram_read( ++ ecram, model->registers->EXT_CPU_TEMP_HYST + i); ++ point->gpu_max_temp_celsius = ++ ecram_read(ecram, model->registers->EXT_GPU_TEMP + i); ++ point->gpu_min_temp_celsius = ecram_read( ++ ecram, model->registers->EXT_GPU_TEMP_HYST + i); ++ point->ic_max_temp_celsius = 0; ++ point->ic_min_temp_celsius = 0; ++ } ++ ++ // Do not trust that hardware; It might suddendly report ++ // a larger size, so clamp it. ++ fancurve->size = FANCURVESIZE_IDEAPDAD; ++ fancurve->current_point_i = ++ ecram_read(ecram, model->registers->EXT_FAN_CUR_POINT); ++ fancurve->current_point_i = ++ min(fancurve->current_point_i, fancurve->size); ++ return 0; ++} ++ ++static int ec_write_fancurve_ideapad(struct ecram *ecram, ++ const struct model_config *model, ++ const struct fancurve *fancurve) ++{ ++ size_t i; ++ int valr1; ++ int valr2; ++ ++ // add this later: maybe other addresses needed ++ // therefore, fan curve might not be effective immediatley but ++ // only after temp change ++ // Reset fan update counters (try to avoid any race conditions) ++ ecram_write(ecram, 0xC5FE, 0); ++ ecram_write(ecram, 0xC5FF, 0); ++ for (i = 0; i < FANCURVESIZE_IDEAPDAD; ++i) { ++ const struct fancurve_point *point = &fancurve->points[i]; ++ ++ ecram_write(ecram, model->registers->EXT_FAN1_BASE + i, ++ point->rpm1_raw); ++ valr1 = ecram_read(ecram, model->registers->EXT_FAN1_BASE + i); ++ ecram_write(ecram, model->registers->EXT_FAN2_BASE + i, ++ point->rpm2_raw); ++ valr2 = ecram_read(ecram, model->registers->EXT_FAN2_BASE + i); ++ pr_info("Writing fan1: %d; reading fan1: %d\n", point->rpm1_raw, ++ valr1); ++ pr_info("Writing fan2: %d; reading fan2: %d\n", point->rpm2_raw, ++ valr2); ++ ++ // write to memory and repeat 8 bytes later again ++ ecram_write(ecram, model->registers->EXT_CPU_TEMP + i, ++ point->cpu_max_temp_celsius); ++ ecram_write(ecram, model->registers->EXT_CPU_TEMP + 8 + i, ++ point->cpu_max_temp_celsius); ++ // write to memory and repeat 8 bytes later again ++ ecram_write(ecram, model->registers->EXT_CPU_TEMP_HYST + i, ++ point->cpu_min_temp_celsius); ++ ecram_write(ecram, model->registers->EXT_CPU_TEMP_HYST + 8 + i, ++ point->cpu_min_temp_celsius); ++ // write to memory and repeat 8 bytes later again ++ ecram_write(ecram, model->registers->EXT_GPU_TEMP + i, ++ point->gpu_max_temp_celsius); ++ ecram_write(ecram, model->registers->EXT_GPU_TEMP + 8 + i, ++ point->gpu_max_temp_celsius); ++ // write to memory and repeat 8 bytes later again ++ ecram_write(ecram, model->registers->EXT_GPU_TEMP_HYST + i, ++ point->gpu_min_temp_celsius); ++ ecram_write(ecram, model->registers->EXT_GPU_TEMP_HYST + 8 + i, ++ point->gpu_min_temp_celsius); ++ } ++ ++ // add this later: maybe other addresses needed ++ // therefore, fan curve might not be effective immediatley but ++ // only after temp change ++ // // Reset current fan level to 0, so algorithm in EC ++ // // selects fan curve point again and resetting hysterisis ++ // // effects ++ // ecram_write(ecram, model->registers->EXT_FAN_CUR_POINT, 0); ++ ++ // // Reset internal fan levels ++ // ecram_write(ecram, 0xC634, 0); // CPU ++ // ecram_write(ecram, 0xC635, 0); // GPU ++ // ecram_write(ecram, 0xC636, 0); // SENSOR ++ ++ return 0; ++} ++ ++static int read_fancurve(struct legion_private *priv, struct fancurve *fancurve) ++{ ++ // TODO: use enums or function pointers? ++ switch (priv->conf->access_method_fancurve) { ++ case ACCESS_METHOD_EC: ++ return ec_read_fancurve_legion(&priv->ecram, priv->conf, ++ fancurve); ++ case ACCESS_METHOD_EC2: ++ return ec_read_fancurve_ideapad(&priv->ecram, priv->conf, ++ fancurve); ++ case ACCESS_METHOD_WMI3: ++ return wmi_read_fancurve_custom(priv->conf, fancurve); ++ default: ++ pr_info("No access method for fancurve:%d\n", ++ priv->conf->access_method_fancurve); ++ return -EINVAL; ++ } ++} ++ ++static int write_fancurve(struct legion_private *priv, ++ const struct fancurve *fancurve, bool write_size) ++{ ++ // TODO: use enums or function pointers? ++ switch (priv->conf->access_method_fancurve) { ++ case ACCESS_METHOD_EC: ++ return ec_write_fancurve_legion(&priv->ecram, priv->conf, ++ fancurve, write_size); ++ case ACCESS_METHOD_EC2: ++ return ec_write_fancurve_ideapad(&priv->ecram, priv->conf, ++ fancurve); ++ case ACCESS_METHOD_WMI3: ++ return wmi_write_fancurve_custom(priv->conf, fancurve); ++ default: ++ pr_info("No access method for fancurve:%d\n", ++ priv->conf->access_method_fancurve); ++ return -EINVAL; ++ } ++} ++ ++#define MINIFANCUVE_ON_COOL_ON 0x04 ++#define MINIFANCUVE_ON_COOL_OFF 0xA0 ++ ++static int ec_read_minifancurve(struct ecram *ecram, ++ const struct model_config *model, bool *state) ++{ ++ int value = ++ ecram_read(ecram, model->registers->EXT_MINIFANCURVE_ON_COOL); ++ ++ switch (value) { ++ case MINIFANCUVE_ON_COOL_ON: ++ *state = true; ++ break; ++ case MINIFANCUVE_ON_COOL_OFF: ++ *state = false; ++ break; ++ default: ++ pr_info("Unexpected value in MINIFANCURVE register:%d\n", ++ value); ++ return -1; ++ } ++ return 0; ++} ++ ++static ssize_t ec_write_minifancurve(struct ecram *ecram, ++ const struct model_config *model, ++ bool state) ++{ ++ u8 val = state ? MINIFANCUVE_ON_COOL_ON : MINIFANCUVE_ON_COOL_OFF; ++ ++ ecram_write(ecram, model->registers->EXT_MINIFANCURVE_ON_COOL, val); ++ return 0; ++} ++ ++#define EC_LOCKFANCONTROLLER_ON 8 ++#define EC_LOCKFANCONTROLLER_OFF 0 ++ ++static ssize_t ec_write_lockfancontroller(struct ecram *ecram, ++ const struct model_config *model, ++ bool state) ++{ ++ u8 val = state ? EC_LOCKFANCONTROLLER_ON : EC_LOCKFANCONTROLLER_OFF; ++ ++ ecram_write(ecram, model->registers->EXT_LOCKFANCONTROLLER, val); ++ return 0; ++} ++ ++static int ec_read_lockfancontroller(struct ecram *ecram, ++ const struct model_config *model, ++ bool *state) ++{ ++ int value = ecram_read(ecram, model->registers->EXT_LOCKFANCONTROLLER); ++ ++ switch (value) { ++ case EC_LOCKFANCONTROLLER_ON: ++ *state = true; ++ break; ++ case EC_LOCKFANCONTROLLER_OFF: ++ *state = false; ++ break; ++ default: ++ pr_info("Unexpected value in lockfanspeed register:%d\n", ++ value); ++ return -1; ++ } ++ return 0; ++} ++ ++#define EC_FANFULLSPEED_ON 0x40 ++#define EC_FANFULLSPEED_OFF 0x00 ++ ++static int ec_read_fanfullspeed(struct ecram *ecram, ++ const struct model_config *model, bool *state) ++{ ++ int value = ecram_read(ecram, model->registers->EXT_MAXIMUMFANSPEED); ++ ++ switch (value) { ++ case EC_FANFULLSPEED_ON: ++ *state = true; ++ break; ++ case EC_FANFULLSPEED_OFF: ++ *state = false; ++ break; ++ default: ++ pr_info("Unexpected value in maximumfanspeed register:%d\n", ++ value); ++ return -1; ++ } ++ return 0; ++} ++ ++static ssize_t ec_write_fanfullspeed(struct ecram *ecram, ++ const struct model_config *model, ++ bool state) ++{ ++ u8 val = state ? EC_FANFULLSPEED_ON : EC_FANFULLSPEED_OFF; ++ ++ ecram_write(ecram, model->registers->EXT_MAXIMUMFANSPEED, val); ++ return 0; ++} ++ ++static ssize_t wmi_read_fanfullspeed(struct legion_private *priv, bool *state) ++{ ++ return get_simple_wmi_attribute_bool(priv, WMI_GUID_LENOVO_FAN_METHOD, ++ 0, WMI_METHOD_ID_FAN_GET_FULLSPEED, ++ false, 1, state); ++} ++ ++static ssize_t wmi_write_fanfullspeed(struct legion_private *priv, bool state) ++{ ++ return set_simple_wmi_attribute(priv, WMI_GUID_LENOVO_FAN_METHOD, 0, ++ WMI_METHOD_ID_FAN_SET_FULLSPEED, false, ++ 1, state); ++} ++ ++static ssize_t read_fanfullspeed(struct legion_private *priv, bool *state) ++{ ++ // TODO: use enums or function pointers? ++ switch (priv->conf->access_method_fanfullspeed) { ++ case ACCESS_METHOD_EC: ++ return ec_read_fanfullspeed(&priv->ecram, priv->conf, state); ++ case ACCESS_METHOD_WMI: ++ return wmi_read_fanfullspeed(priv, state); ++ default: ++ pr_info("No access method for fan full speed: %d\n", ++ priv->conf->access_method_fanfullspeed); ++ return -EINVAL; ++ } ++} ++ ++static ssize_t write_fanfullspeed(struct legion_private *priv, bool state) ++{ ++ ssize_t res; ++ ++ switch (priv->conf->access_method_fanfullspeed) { ++ case ACCESS_METHOD_EC: ++ res = ec_write_fanfullspeed(&priv->ecram, priv->conf, state); ++ return res; ++ case ACCESS_METHOD_WMI: ++ return wmi_write_fanfullspeed(priv, state); ++ default: ++ pr_info("No access method for fan full speed:%d\n", ++ priv->conf->access_method_fanfullspeed); ++ return -EINVAL; ++ } ++} ++ ++/* ============================= */ ++/* Power mode reading/writing */ ++/* ============================= */ ++ ++enum legion_ec_powermode { ++ LEGION_EC_POWERMODE_QUIET = 2, ++ LEGION_EC_POWERMODE_BALANCED = 0, ++ LEGION_EC_POWERMODE_PERFORMANCE = 1, ++ LEGION_EC_POWERMODE_CUSTOM = 3 ++}; ++ ++enum legion_wmi_powermode { ++ LEGION_WMI_POWERMODE_QUIET = 1, ++ LEGION_WMI_POWERMODE_BALANCED = 2, ++ LEGION_WMI_POWERMODE_PERFORMANCE = 3, ++ LEGION_WMI_POWERMODE_CUSTOM = 255 ++}; ++ ++enum legion_wmi_powermode ec_to_wmi_powermode(int ec_mode) ++{ ++ switch (ec_mode) { ++ case LEGION_EC_POWERMODE_QUIET: ++ return LEGION_WMI_POWERMODE_QUIET; ++ case LEGION_EC_POWERMODE_BALANCED: ++ return LEGION_WMI_POWERMODE_BALANCED; ++ case LEGION_EC_POWERMODE_PERFORMANCE: ++ return LEGION_WMI_POWERMODE_PERFORMANCE; ++ case LEGION_EC_POWERMODE_CUSTOM: ++ return LEGION_WMI_POWERMODE_CUSTOM; ++ default: ++ return LEGION_WMI_POWERMODE_BALANCED; ++ } ++} ++ ++enum legion_ec_powermode wmi_to_ec_powermode(enum legion_wmi_powermode wmi_mode) ++{ ++ switch (wmi_mode) { ++ case LEGION_WMI_POWERMODE_QUIET: ++ return LEGION_EC_POWERMODE_QUIET; ++ case LEGION_WMI_POWERMODE_BALANCED: ++ return LEGION_EC_POWERMODE_BALANCED; ++ case LEGION_WMI_POWERMODE_PERFORMANCE: ++ return LEGION_EC_POWERMODE_PERFORMANCE; ++ case LEGION_WMI_POWERMODE_CUSTOM: ++ return LEGION_EC_POWERMODE_CUSTOM; ++ default: ++ return LEGION_EC_POWERMODE_BALANCED; ++ } ++} ++ ++static ssize_t ec_read_powermode(struct legion_private *priv, int *powermode) ++{ ++ *powermode = ++ ecram_read(&priv->ecram, priv->conf->registers->EXT_POWERMODE); ++ return 0; ++} ++ ++static ssize_t ec_write_powermode(struct legion_private *priv, u8 value) ++{ ++ if (!((value >= 0 && value <= 2) || value == 255)) { ++ pr_info("Unexpected power mode value ignored: %d\n", value); ++ return -ENOMEM; ++ } ++ ecram_write(&priv->ecram, priv->conf->registers->EXT_POWERMODE, value); ++ return 0; ++} ++ ++static ssize_t acpi_read_powermode(struct legion_private *priv, int *powermode) ++{ ++ unsigned long acpi_powermode; ++ int err; ++ ++ // spmo method not alwasy available ++ // \_SB.PCI0.LPC0.EC0.SPMO ++ err = eval_spmo(priv->adev->handle, &acpi_powermode); ++ *powermode = (int)acpi_powermode; ++ return err; ++} ++ ++static ssize_t wmi_read_powermode(int *powermode) ++{ ++ int err; ++ unsigned long res; ++ ++ err = wmi_exec_noarg_int(LEGION_WMI_GAMEZONE_GUID, 0, ++ WMI_METHOD_ID_GETSMARTFANMODE, &res); ++ ++ if (!err) ++ *powermode = res; ++ return err; ++} ++ ++static ssize_t wmi_write_powermode(u8 value) ++{ ++ if (!((value >= LEGION_WMI_POWERMODE_QUIET && ++ value <= LEGION_WMI_POWERMODE_PERFORMANCE) || ++ value == LEGION_WMI_POWERMODE_CUSTOM)) { ++ pr_info("Unexpected power mode value ignored: %d\n", value); ++ return -ENOMEM; ++ } ++ return wmi_exec_arg(LEGION_WMI_GAMEZONE_GUID, 0, ++ WMI_METHOD_ID_SETSMARTFANMODE, &value, ++ sizeof(value)); ++} ++ ++static ssize_t read_powermode(struct legion_private *priv, int *powermode) ++{ ++ ssize_t res; ++ ++ switch (priv->conf->access_method_powermode) { ++ case ACCESS_METHOD_EC: ++ res = ec_read_powermode(priv, powermode); ++ *powermode = ec_to_wmi_powermode(*powermode); ++ return res; ++ case ACCESS_METHOD_ACPI: ++ return acpi_read_powermode(priv, powermode); ++ case ACCESS_METHOD_WMI: ++ return wmi_read_powermode(powermode); ++ default: ++ pr_info("No access method for powermode:%d\n", ++ priv->conf->access_method_powermode); ++ return -EINVAL; ++ } ++} ++ ++static ssize_t write_powermode(struct legion_private *priv, ++ enum legion_wmi_powermode value) ++{ ++ ssize_t res; ++ ++ //TODO: remove again ++ pr_info("Set powermode\n"); ++ ++ switch (priv->conf->access_method_powermode) { ++ case ACCESS_METHOD_EC: ++ res = ec_write_powermode(priv, wmi_to_ec_powermode(value)); ++ return res; ++ case ACCESS_METHOD_WMI: ++ return wmi_write_powermode(value); ++ default: ++ pr_info("No access method for powermode:%d\n", ++ priv->conf->access_method_powermode); ++ return -EINVAL; ++ } ++} ++ ++/** ++ * Shortly toggle powermode to a different mode ++ * and switch back, e.g. to reset fan curve. ++ */ ++static void toggle_powermode(struct legion_private *priv) ++{ ++ int old_powermode; ++ int next_powermode; ++ ++ read_powermode(priv, &old_powermode); ++ next_powermode = old_powermode == 0 ? 1 : 0; ++ ++ write_powermode(priv, next_powermode); ++ mdelay(1500); ++ write_powermode(priv, old_powermode); ++} ++ ++/* ============================= */ ++/* Charging mode reading/writing */ ++/* ============================- */ ++ ++#define FCT_RAPID_CHARGE_ON 0x07 ++#define FCT_RAPID_CHARGE_OFF 0x08 ++#define RAPID_CHARGE_ON 0x0 ++#define RAPID_CHARGE_OFF 0x1 ++ ++static int acpi_read_rapidcharge(struct acpi_device *adev, bool *state) ++{ ++ unsigned long result; ++ int err; ++ ++ //also works? what is better? ++ /* ++ * err = eval_qcho(adev->handle, &result); ++ * if (err) ++ * return err; ++ * state = result; ++ * return 0; ++ */ ++ ++ err = eval_gbmd(adev->handle, &result); ++ if (err) ++ return err; ++ ++ *state = result & 0x04; ++ return 0; ++} ++ ++static int acpi_write_rapidcharge(struct acpi_device *adev, bool state) ++{ ++ int err; ++ unsigned long fct_nr = state > 0 ? FCT_RAPID_CHARGE_ON : ++ FCT_RAPID_CHARGE_OFF; ++ ++ err = exec_sbmc(adev->handle, fct_nr); ++ pr_info("Set rapidcharge to %d by calling %lu: result: %d\n", state, ++ fct_nr, err); ++ return err; ++} ++ ++/* ============================= */ ++/* Keyboard backlight read/write */ ++/* ============================= */ ++ ++static ssize_t legion_kbd_bl2_brightness_get(struct legion_private *priv) ++{ ++ unsigned long state = 0; ++ int err; ++ ++ err = wmi_exec_noarg_int(LEGION_WMI_GAMEZONE_GUID, 0, ++ WMI_METHOD_ID_GETKEYBOARDLIGHT, &state); ++ if (err) ++ return -EINVAL; ++ ++ return state; ++} ++ ++//static int legion_kbd_bl2_brightness_set(struct legion_private *priv, ++// unsigned int brightness) ++//{ ++// u8 in_param = brightness; ++ ++// return wmi_exec_arg(LEGION_WMI_GAMEZONE_GUID, 0, ++// WMI_METHOD_ID_SETKEYBOARDLIGHT, &in_param, ++// sizeof(in_param)); ++//} ++ ++//min: 1, max: 3 ++#define LIGHT_ID_KEYBOARD 0x00 ++//min: 0, max: 1 ++#define LIGHT_ID_YLOGO 0x03 ++//min: 1, max: 2 ++#define LIGHT_ID_IOPORT 0x05 ++ ++static int legion_wmi_light_get(struct legion_private *priv, u8 light_id, ++ unsigned int min_value, unsigned int max_value) ++{ ++ struct acpi_buffer params; ++ u8 in; ++ u8 result[2]; ++ u8 value; ++ int err; ++ ++ params.length = 1; ++ params.pointer = ∈ ++ in = light_id; ++ err = wmi_exec_ints(LEGION_WMI_KBBACKLIGHT_GUID, 0, ++ WMI_METHOD_ID_KBBACKLIGHTGET, ¶ms, result, ++ ARRAY_SIZE(result)); ++ if (err) { ++ pr_info("Error for WMI method call to get brightness\n"); ++ return -EIO; ++ } ++ ++ value = result[1]; ++ if (!(value >= min_value && value <= max_value)) { ++ pr_info("Error WMI call for reading brightness: expected a value between %u and %u, but got %d\n", ++ min_value, max_value, value); ++ return -EFAULT; ++ } ++ ++ return value - min_value; ++} ++ ++static int legion_wmi_light_set(struct legion_private *priv, u8 light_id, ++ unsigned int min_value, unsigned int max_value, ++ unsigned int brightness) ++{ ++ struct acpi_buffer buffer; ++ u8 in_buffer_param[8]; ++ unsigned long result; ++ int err; ++ ++ buffer.length = 3; ++ buffer.pointer = &in_buffer_param[0]; ++ in_buffer_param[0] = light_id; ++ in_buffer_param[1] = 0x01; ++ in_buffer_param[2] = ++ clamp(brightness + min_value, min_value, max_value); ++ ++ err = wmi_exec_int(LEGION_WMI_KBBACKLIGHT_GUID, 0, ++ WMI_METHOD_ID_KBBACKLIGHTSET, &buffer, &result); ++ if (err) { ++ pr_info("Error for WMI method call to set brightness on light: %d\n", ++ light_id); ++ return -EIO; ++ } ++ ++ return 0; ++} ++ ++static int legion_kbd_bl_brightness_get(struct legion_private *priv) ++{ ++ return legion_wmi_light_get(priv, LIGHT_ID_KEYBOARD, 1, 3); ++} ++ ++static int legion_kbd_bl_brightness_set(struct legion_private *priv, ++ unsigned int brightness) ++{ ++ return legion_wmi_light_set(priv, LIGHT_ID_KEYBOARD, 1, 3, brightness); ++} ++ ++/* ============================= */ ++/* debugfs interface */ ++/* ============================ */ ++ ++static int debugfs_ecmemory_show(struct seq_file *s, void *unused) ++{ ++ struct legion_private *priv = s->private; ++ size_t offset; ++ ++ for (offset = 0; offset < priv->conf->memoryio_size; ++offset) { ++ char value = ecram_read(&priv->ecram, ++ priv->conf->memoryio_physical_ec_start + ++ offset); ++ ++ seq_write(s, &value, 1); ++ } ++ return 0; ++} ++ ++DEFINE_SHOW_ATTRIBUTE(debugfs_ecmemory); ++ ++static int debugfs_ecmemoryram_show(struct seq_file *s, void *unused) ++{ ++ struct legion_private *priv = s->private; ++ size_t offset; ++ ssize_t err; ++ u8 value; ++ ++ for (offset = 0; offset < priv->conf->ramio_size; ++offset) { ++ err = ecram_memoryio_read(&priv->ec_memoryio, offset, &value); ++ if (!err) ++ seq_write(s, &value, 1); ++ else ++ return -EACCES; ++ } ++ return 0; ++} ++ ++DEFINE_SHOW_ATTRIBUTE(debugfs_ecmemoryram); ++ ++//TODO: make (almost) all methods static ++ ++static void seq_file_print_with_error(struct seq_file *s, const char *name, ++ ssize_t err, int value) ++{ ++ seq_printf(s, "%s error: %ld\n", name, err); ++ seq_printf(s, "%s: %d\n", name, value); ++} ++ ++static int debugfs_fancurve_show(struct seq_file *s, void *unused) ++{ ++ struct legion_private *priv = s->private; ++ bool is_minifancurve; ++ bool is_lockfancontroller; ++ bool is_maximumfanspeed; ++ bool is_rapidcharge = false; ++ int powermode; ++ int temperature; ++ int fanspeed; ++ int err; ++ unsigned long cfg; ++ struct fancurve wmi_fancurve; ++ //int kb_backlight; ++ ++ mutex_lock(&priv->fancurve_mutex); ++ ++ seq_printf(s, "EC Chip ID: %x\n", read_ec_id(&priv->ecram, priv->conf)); ++ seq_printf(s, "EC Chip Version: %x\n", ++ read_ec_version(&priv->ecram, priv->conf)); ++ seq_printf(s, "legion_laptop features: %s\n", LEGIONFEATURES); ++ seq_printf(s, "legion_laptop ec_readonly: %d\n", ec_readonly); ++ ++ err = eval_int(priv->adev->handle, "VPC0._CFG", &cfg); ++ seq_printf(s, "ACPI CFG error: %d\n", err); ++ seq_printf(s, "ACPI CFG: %lu\n", cfg); ++ ++ seq_printf(s, "temperature access method: %d\n", ++ priv->conf->access_method_temperature); ++ err = read_temperature(priv, 0, &temperature); ++ seq_file_print_with_error(s, "CPU temperature", err, temperature); ++ err = ec_read_temperature(&priv->ecram, priv->conf, 0, &temperature); ++ seq_file_print_with_error(s, "CPU temperature EC", err, temperature); ++ err = acpi_read_temperature(priv, 0, &temperature); ++ seq_file_print_with_error(s, "CPU temperature ACPI", err, temperature); ++ err = wmi_read_temperature_gz(0, &temperature); ++ seq_file_print_with_error(s, "CPU temperature WMI", err, temperature); ++ err = wmi_read_temperature(0, &temperature); ++ seq_file_print_with_error(s, "CPU temperature WMI2", err, temperature); ++ err = wmi_read_temperature_other(0, &temperature); ++ seq_file_print_with_error(s, "CPU temperature WMI3", err, temperature); ++ ++ err = read_temperature(priv, 1, &temperature); ++ seq_file_print_with_error(s, "GPU temperature", err, temperature); ++ err = ec_read_temperature(&priv->ecram, priv->conf, 1, &temperature); ++ seq_file_print_with_error(s, "GPU temperature EC", err, temperature); ++ err = acpi_read_temperature(priv, 1, &temperature); ++ seq_file_print_with_error(s, "GPU temperature ACPI", err, temperature); ++ err = wmi_read_temperature_gz(1, &temperature); ++ seq_file_print_with_error(s, "GPU temperature WMI", err, temperature); ++ err = wmi_read_temperature(1, &temperature); ++ seq_file_print_with_error(s, "GPU temperature WMI2", err, temperature); ++ err = wmi_read_temperature_other(1, &temperature); ++ seq_file_print_with_error(s, "GPU temperature WMI3", err, temperature); ++ ++ seq_printf(s, "fan speed access method: %d\n", ++ priv->conf->access_method_fanspeed); ++ err = read_fanspeed(priv, 0, &fanspeed); ++ seq_file_print_with_error(s, "1 fanspeed", err, fanspeed); ++ err = ec_read_fanspeed(&priv->ecram, priv->conf, 0, &fanspeed); ++ seq_file_print_with_error(s, "1 fanspeed EC", err, fanspeed); ++ err = acpi_read_fanspeed(priv, 0, &fanspeed); ++ seq_file_print_with_error(s, "1 fanspeed ACPI", err, fanspeed); ++ err = wmi_read_fanspeed_gz(0, &fanspeed); ++ seq_file_print_with_error(s, "1 fanspeed WMI", err, fanspeed); ++ err = wmi_read_fanspeed(0, &fanspeed); ++ seq_file_print_with_error(s, "1 fanspeed WMI2", err, fanspeed); ++ err = wmi_read_fanspeed_other(0, &fanspeed); ++ seq_file_print_with_error(s, "1 fanspeed WMI3", err, fanspeed); ++ ++ err = read_fanspeed(priv, 1, &fanspeed); ++ seq_file_print_with_error(s, "2 fanspeed", err, fanspeed); ++ err = ec_read_fanspeed(&priv->ecram, priv->conf, 1, &fanspeed); ++ seq_file_print_with_error(s, "2 fanspeed EC", err, fanspeed); ++ err = acpi_read_fanspeed(priv, 1, &fanspeed); ++ seq_file_print_with_error(s, "2 fanspeed ACPI", err, fanspeed); ++ err = wmi_read_fanspeed_gz(1, &fanspeed); ++ seq_file_print_with_error(s, "2 fanspeed WMI", err, fanspeed); ++ err = wmi_read_fanspeed(1, &fanspeed); ++ seq_file_print_with_error(s, "2 fanspeed WMI2", err, fanspeed); ++ err = wmi_read_fanspeed_other(1, &fanspeed); ++ seq_file_print_with_error(s, "2 fanspeed WMI3", err, fanspeed); ++ ++ seq_printf(s, "powermode access method: %d\n", ++ priv->conf->access_method_powermode); ++ err = read_powermode(priv, &powermode); ++ seq_file_print_with_error(s, "powermode", err, powermode); ++ err = ec_read_powermode(priv, &powermode); ++ seq_file_print_with_error(s, "powermode EC", err, powermode); ++ err = acpi_read_powermode(priv, &powermode); ++ seq_file_print_with_error(s, "powermode ACPI", err, powermode); ++ err = wmi_read_powermode(&powermode); ++ seq_file_print_with_error(s, "powermode WMI", err, powermode); ++ seq_printf(s, "has custom powermode: %d\n", ++ priv->conf->has_custom_powermode); ++ ++ err = acpi_read_rapidcharge(priv->adev, &is_rapidcharge); ++ seq_printf(s, "ACPI rapidcharge error: %d\n", err); ++ seq_printf(s, "ACPI rapidcharge: %d\n", is_rapidcharge); ++ ++ seq_printf(s, "WMI backlight 2 state: %ld\n", ++ legion_kbd_bl2_brightness_get(priv)); ++ seq_printf(s, "WMI backlight 3 state: %d\n", ++ legion_kbd_bl_brightness_get(priv)); ++ ++ seq_printf(s, "WMI light IO port: %d\n", ++ legion_wmi_light_get(priv, LIGHT_ID_IOPORT, 0, 4)); ++ ++ seq_printf(s, "WMI light y logo/lid: %d\n", ++ legion_wmi_light_get(priv, LIGHT_ID_YLOGO, 0, 4)); ++ ++ seq_printf(s, "EC minifancurve feature enabled: %d\n", ++ priv->conf->has_minifancurve); ++ err = ec_read_minifancurve(&priv->ecram, priv->conf, &is_minifancurve); ++ seq_printf(s, "EC minifancurve on cool: %s\n", ++ err ? "error" : (is_minifancurve ? "true" : "false")); ++ ++ err = ec_read_lockfancontroller(&priv->ecram, priv->conf, ++ &is_lockfancontroller); ++ seq_printf(s, "EC lockfancontroller error: %d\n", err); ++ seq_printf(s, "EC lockfancontroller: %s\n", ++ err ? "error" : (is_lockfancontroller ? "true" : "false")); ++ ++ err = read_fanfullspeed(priv, &is_maximumfanspeed); ++ seq_file_print_with_error(s, "fanfullspeed", err, is_maximumfanspeed); ++ ++ err = ec_read_fanfullspeed(&priv->ecram, priv->conf, ++ &is_maximumfanspeed); ++ seq_file_print_with_error(s, "fanfullspeed EC", err, ++ is_maximumfanspeed); ++ ++ read_fancurve(priv, &priv->fancurve); ++ seq_printf(s, "EC fan curve current point id: %ld\n", ++ priv->fancurve.current_point_i); ++ seq_printf(s, "EC fan curve points size: %ld\n", priv->fancurve.size); ++ ++ seq_puts(s, "Current fan curve in hardware:\n"); ++ fancurve_print_seqfile(&priv->fancurve, s); ++ seq_puts(s, "=====================\n"); ++ mutex_unlock(&priv->fancurve_mutex); ++ ++ seq_puts(s, "Current fan curve in hardware (WMI; might be empty)\n"); ++ wmi_fancurve.size = 0; ++ err = wmi_read_fancurve_custom(priv->conf, &wmi_fancurve); ++ fancurve_print_seqfile(&wmi_fancurve, s); ++ seq_puts(s, "=====================\n"); ++ return 0; ++} ++ ++DEFINE_SHOW_ATTRIBUTE(debugfs_fancurve); ++ ++static void legion_debugfs_init(struct legion_private *priv) ++{ ++ struct dentry *dir; ++ ++ // TODO: remove this note ++ // Note: as other kernel modules, do not catch errors here ++ // because if kernel is build without debugfs this ++ // will return an error but module still has to ++ // work, just without debugfs ++ // TODO: what permissions; some modules do 400 ++ // other do 444 ++ dir = debugfs_create_dir(LEGION_DRVR_SHORTNAME, NULL); ++ debugfs_create_file("fancurve", 0444, dir, priv, ++ &debugfs_fancurve_fops); ++ debugfs_create_file("ecmemory", 0444, dir, priv, ++ &debugfs_ecmemory_fops); ++ debugfs_create_file("ecmemoryram", 0444, dir, priv, ++ &debugfs_ecmemoryram_fops); ++ ++ priv->debugfs_dir = dir; ++} ++ ++static void legion_debugfs_exit(struct legion_private *priv) ++{ ++ pr_info("Unloading legion dubugfs\n"); ++ // The following is does nothing if pointer is NULL ++ debugfs_remove_recursive(priv->debugfs_dir); ++ priv->debugfs_dir = NULL; ++ pr_info("Unloading legion dubugfs done\n"); ++} ++ ++/* ============================= */ ++/* sysfs interface */ ++/* ============================ */ ++ ++static int show_simple_wmi_attribute(struct device *dev, ++ struct device_attribute *attr, char *buf, ++ const char *guid, u8 instance, ++ u32 method_id, bool invert, ++ unsigned long scale) ++{ ++ unsigned long state = 0; ++ int err; ++ struct legion_private *priv = dev_get_drvdata(dev); ++ ++ mutex_lock(&priv->fancurve_mutex); ++ err = get_simple_wmi_attribute(priv, guid, instance, method_id, invert, ++ scale, &state); ++ mutex_unlock(&priv->fancurve_mutex); ++ ++ if (err) ++ return -EINVAL; ++ ++ return sysfs_emit(buf, "%lu\n", state); ++} ++ ++static int show_simple_wmi_attribute_from_buffer(struct device *dev, ++ struct device_attribute *attr, ++ char *buf, const char *guid, ++ u8 instance, u32 method_id, ++ size_t ressize, size_t i, ++ int scale) ++{ ++ u8 res[16]; ++ int err; ++ int out; ++ struct legion_private *priv = dev_get_drvdata(dev); ++ ++ if (ressize > ARRAY_SIZE(res)) { ++ pr_info("Buffer to small for WMI result\n"); ++ return -EINVAL; ++ } ++ if (i >= ressize) { ++ pr_info("Index not within buffer size\n"); ++ return -EINVAL; ++ } ++ ++ mutex_lock(&priv->fancurve_mutex); ++ err = wmi_exec_noarg_ints(guid, instance, method_id, res, ressize); ++ mutex_unlock(&priv->fancurve_mutex); ++ if (err) ++ return -EINVAL; ++ ++ out = scale * res[i]; ++ return sysfs_emit(buf, "%d\n", out); ++} ++ ++static int store_simple_wmi_attribute(struct device *dev, ++ struct device_attribute *attr, ++ const char *buf, size_t count, ++ const char *guid, u8 instance, ++ u32 method_id, bool invert, int scale) ++{ ++ int state; ++ int err; ++ struct legion_private *priv = dev_get_drvdata(dev); ++ ++ err = kstrtouint(buf, 0, &state); ++ if (err) ++ return err; ++ err = set_simple_wmi_attribute(priv, guid, instance, method_id, invert, ++ scale, state); ++ if (err) ++ return err; ++ return count; ++} ++ ++static ssize_t lockfancontroller_show(struct device *dev, ++ struct device_attribute *attr, char *buf) ++{ ++ struct legion_private *priv = dev_get_drvdata(dev); ++ bool is_lockfancontroller; ++ int err; ++ ++ mutex_lock(&priv->fancurve_mutex); ++ err = ec_read_lockfancontroller(&priv->ecram, priv->conf, ++ &is_lockfancontroller); ++ mutex_unlock(&priv->fancurve_mutex); ++ if (err) ++ return -EINVAL; ++ ++ return sysfs_emit(buf, "%d\n", is_lockfancontroller); ++} ++ ++static ssize_t lockfancontroller_store(struct device *dev, ++ struct device_attribute *attr, ++ const char *buf, size_t count) ++{ ++ struct legion_private *priv = dev_get_drvdata(dev); ++ bool is_lockfancontroller; ++ int err; ++ ++ err = kstrtobool(buf, &is_lockfancontroller); ++ if (err) ++ return err; ++ ++ mutex_lock(&priv->fancurve_mutex); ++ err = ec_write_lockfancontroller(&priv->ecram, priv->conf, ++ is_lockfancontroller); ++ mutex_unlock(&priv->fancurve_mutex); ++ if (err) ++ return -EINVAL; ++ ++ return count; ++} ++ ++static DEVICE_ATTR_RW(lockfancontroller); ++ ++static ssize_t rapidcharge_show(struct device *dev, ++ struct device_attribute *attr, char *buf) ++{ ++ bool state = false; ++ int err; ++ struct legion_private *priv = dev_get_drvdata(dev); ++ ++ mutex_lock(&priv->fancurve_mutex); ++ err = acpi_read_rapidcharge(priv->adev, &state); ++ mutex_unlock(&priv->fancurve_mutex); ++ if (err) ++ return -EINVAL; ++ ++ return sysfs_emit(buf, "%d\n", state); ++} ++ ++static ssize_t rapidcharge_store(struct device *dev, ++ struct device_attribute *attr, const char *buf, ++ size_t count) ++{ ++ struct legion_private *priv = dev_get_drvdata(dev); ++ int state; ++ int err; ++ ++ err = kstrtouint(buf, 0, &state); ++ if (err) ++ return err; ++ ++ mutex_lock(&priv->fancurve_mutex); ++ err = acpi_write_rapidcharge(priv->adev, state); ++ mutex_unlock(&priv->fancurve_mutex); ++ if (err) ++ return -EINVAL; ++ ++ return count; ++} ++ ++static DEVICE_ATTR_RW(rapidcharge); ++ ++static ssize_t issupportgpuoc_show(struct device *dev, ++ struct device_attribute *attr, char *buf) ++{ ++ return show_simple_wmi_attribute(dev, attr, buf, ++ LEGION_WMI_GAMEZONE_GUID, 0, ++ WMI_METHOD_ID_ISSUPPORTGPUOC, false, ++ 1); ++} ++ ++static DEVICE_ATTR_RO(issupportgpuoc); ++ ++static ssize_t aslcodeversion_show(struct device *dev, ++ struct device_attribute *attr, char *buf) ++{ ++ return show_simple_wmi_attribute(dev, attr, buf, ++ LEGION_WMI_GAMEZONE_GUID, 0, ++ WMI_METHOD_ID_GETVERSION, false, 1); ++} ++ ++static DEVICE_ATTR_RO(aslcodeversion); ++ ++static ssize_t issupportcpuoc_show(struct device *dev, ++ struct device_attribute *attr, char *buf) ++{ ++ return show_simple_wmi_attribute(dev, attr, buf, ++ LEGION_WMI_GAMEZONE_GUID, 0, ++ WMI_METHOD_ID_ISSUPPORTCPUOC, false, ++ 1); ++} ++ ++static DEVICE_ATTR_RO(issupportcpuoc); ++ ++static ssize_t winkey_show(struct device *dev, struct device_attribute *attr, ++ char *buf) ++{ ++ return show_simple_wmi_attribute(dev, attr, buf, ++ LEGION_WMI_GAMEZONE_GUID, 0, ++ WMI_METHOD_ID_GETWINKEYSTATUS, true, ++ 1); ++} ++ ++static ssize_t winkey_store(struct device *dev, struct device_attribute *attr, ++ const char *buf, size_t count) ++{ ++ return store_simple_wmi_attribute(dev, attr, buf, count, ++ LEGION_WMI_GAMEZONE_GUID, 0, ++ WMI_METHOD_ID_SETWINKEYSTATUS, true, ++ 1); ++} ++ ++static DEVICE_ATTR_RW(winkey); ++ ++// on newer models the touchpad feature in ideapad does not work anymore, so ++// we need this ++static ssize_t touchpad_show(struct device *dev, struct device_attribute *attr, ++ char *buf) ++{ ++ return show_simple_wmi_attribute(dev, attr, buf, ++ LEGION_WMI_GAMEZONE_GUID, 0, ++ WMI_METHOD_ID_GETTPSTATUS, true, 1); ++} ++ ++static ssize_t touchpad_store(struct device *dev, struct device_attribute *attr, ++ const char *buf, size_t count) ++{ ++ return store_simple_wmi_attribute(dev, attr, buf, count, ++ LEGION_WMI_GAMEZONE_GUID, 0, ++ WMI_METHOD_ID_SETTPSTATUS, true, 1); ++} ++ ++static DEVICE_ATTR_RW(touchpad); ++ ++static ssize_t gsync_show(struct device *dev, struct device_attribute *attr, ++ char *buf) ++{ ++ return show_simple_wmi_attribute(dev, attr, buf, ++ LEGION_WMI_GAMEZONE_GUID, 0, ++ WMI_METHOD_ID_GETGSYNCSTATUS, true, 1); ++} ++ ++static ssize_t gsync_store(struct device *dev, struct device_attribute *attr, ++ const char *buf, size_t count) ++{ ++ return store_simple_wmi_attribute(dev, attr, buf, count, ++ LEGION_WMI_GAMEZONE_GUID, 0, ++ WMI_METHOD_ID_SETGSYNCSTATUS, true, ++ 1); ++} ++ ++static DEVICE_ATTR_RW(gsync); ++ ++static ssize_t powerchargemode_show(struct device *dev, ++ struct device_attribute *attr, char *buf) ++{ ++ return show_simple_wmi_attribute(dev, attr, buf, ++ LEGION_WMI_GAMEZONE_GUID, 0, ++ WMI_METHOD_ID_GETPOWERCHARGEMODE, ++ false, 1); ++} ++static DEVICE_ATTR_RO(powerchargemode); ++ ++static ssize_t overdrive_show(struct device *dev, struct device_attribute *attr, ++ char *buf) ++{ ++ return show_simple_wmi_attribute(dev, attr, buf, ++ LEGION_WMI_GAMEZONE_GUID, 0, ++ WMI_METHOD_ID_GETODSTATUS, false, 1); ++} ++ ++static ssize_t overdrive_store(struct device *dev, ++ struct device_attribute *attr, const char *buf, ++ size_t count) ++{ ++ return store_simple_wmi_attribute(dev, attr, buf, count, ++ LEGION_WMI_GAMEZONE_GUID, 0, ++ WMI_METHOD_ID_SETODSTATUS, false, 1); ++} ++ ++static DEVICE_ATTR_RW(overdrive); ++ ++static ssize_t thermalmode_show(struct device *dev, ++ struct device_attribute *attr, char *buf) ++{ ++ return show_simple_wmi_attribute(dev, attr, buf, ++ LEGION_WMI_GAMEZONE_GUID, 0, ++ WMI_METHOD_ID_GETTHERMALMODE, false, ++ 1); ++} ++static DEVICE_ATTR_RO(thermalmode); ++ ++// TOOD: probably remove again because provided by other means; only useful for overclocking ++static ssize_t cpumaxfrequency_show(struct device *dev, ++ struct device_attribute *attr, char *buf) ++{ ++ return show_simple_wmi_attribute(dev, attr, buf, ++ LEGION_WMI_GAMEZONE_GUID, 0, ++ WMI_METHOD_ID_GETCPUMAXFREQUENCY, ++ false, 1); ++} ++static DEVICE_ATTR_RO(cpumaxfrequency); ++ ++static ssize_t isacfitforoc_show(struct device *dev, ++ struct device_attribute *attr, char *buf) ++{ ++ return show_simple_wmi_attribute(dev, attr, buf, ++ LEGION_WMI_GAMEZONE_GUID, 0, ++ WMI_METHOD_ID_ISACFITFOROC, false, 1); ++} ++static DEVICE_ATTR_RO(isacfitforoc); ++ ++static ssize_t igpumode_show(struct device *dev, struct device_attribute *attr, ++ char *buf) ++{ ++ return show_simple_wmi_attribute(dev, attr, buf, ++ LEGION_WMI_GAMEZONE_GUID, 0, ++ WMI_METHOD_ID_GETIGPUMODESTATUS, false, ++ 1); ++} ++ ++static ssize_t igpumode_store(struct device *dev, struct device_attribute *attr, ++ const char *buf, size_t count) ++{ ++ return store_simple_wmi_attribute(dev, attr, buf, count, ++ LEGION_WMI_GAMEZONE_GUID, 0, ++ WMI_METHOD_ID_SETIGPUMODESTATUS, ++ false, 1); ++} ++ ++static DEVICE_ATTR_RW(igpumode); ++ ++static ssize_t cpu_oc_show(struct device *dev, struct device_attribute *attr, ++ char *buf) ++{ ++ return show_simple_wmi_attribute_from_buffer( ++ dev, attr, buf, WMI_GUID_LENOVO_CPU_METHOD, 0, ++ WMI_METHOD_ID_CPU_GET_OC_STATUS, 16, 0, 1); ++} ++ ++static ssize_t cpu_oc_store(struct device *dev, struct device_attribute *attr, ++ const char *buf, size_t count) ++{ ++ return store_simple_wmi_attribute(dev, attr, buf, count, ++ WMI_GUID_LENOVO_CPU_METHOD, 0, ++ WMI_METHOD_ID_CPU_SET_OC_STATUS, ++ false, 1); ++} ++ ++static DEVICE_ATTR_RW(cpu_oc); ++ ++static ssize_t cpu_shortterm_powerlimit_show(struct device *dev, ++ struct device_attribute *attr, ++ char *buf) ++{ ++ return show_simple_wmi_attribute_from_buffer( ++ dev, attr, buf, WMI_GUID_LENOVO_CPU_METHOD, 0, ++ WMI_METHOD_ID_CPU_GET_SHORTTERM_POWERLIMIT, 16, 0, 1); ++} ++ ++static ssize_t cpu_shortterm_powerlimit_store(struct device *dev, ++ struct device_attribute *attr, ++ const char *buf, size_t count) ++{ ++ return store_simple_wmi_attribute( ++ dev, attr, buf, count, WMI_GUID_LENOVO_CPU_METHOD, 0, ++ WMI_METHOD_ID_CPU_SET_SHORTTERM_POWERLIMIT, false, 1); ++} ++ ++static DEVICE_ATTR_RW(cpu_shortterm_powerlimit); ++ ++static ssize_t cpu_longterm_powerlimit_show(struct device *dev, ++ struct device_attribute *attr, ++ char *buf) ++{ ++ return show_simple_wmi_attribute_from_buffer( ++ dev, attr, buf, WMI_GUID_LENOVO_CPU_METHOD, 0, ++ WMI_METHOD_ID_CPU_GET_LONGTERM_POWERLIMIT, 16, 0, 1); ++} ++ ++static ssize_t cpu_longterm_powerlimit_store(struct device *dev, ++ struct device_attribute *attr, ++ const char *buf, size_t count) ++{ ++ return store_simple_wmi_attribute( ++ dev, attr, buf, count, WMI_GUID_LENOVO_CPU_METHOD, 0, ++ WMI_METHOD_ID_CPU_SET_LONGTERM_POWERLIMIT, false, 1); ++} ++ ++static DEVICE_ATTR_RW(cpu_longterm_powerlimit); ++ ++static ssize_t cpu_default_powerlimit_show(struct device *dev, ++ struct device_attribute *attr, ++ char *buf) ++{ ++ return show_simple_wmi_attribute( ++ dev, attr, buf, WMI_GUID_LENOVO_CPU_METHOD, 0, ++ WMI_METHOD_ID_CPU_GET_DEFAULT_POWERLIMIT, false, 1); ++} ++ ++static DEVICE_ATTR_RO(cpu_default_powerlimit); ++ ++static ssize_t cpu_peak_powerlimit_show(struct device *dev, ++ struct device_attribute *attr, ++ char *buf) ++{ ++ return show_simple_wmi_attribute(dev, attr, buf, ++ WMI_GUID_LENOVO_GPU_METHOD, 0, ++ WMI_METHOD_ID_CPU_GET_PEAK_POWERLIMIT, ++ false, 1); ++} ++ ++static ssize_t cpu_peak_powerlimit_store(struct device *dev, ++ struct device_attribute *attr, ++ const char *buf, size_t count) ++{ ++ return store_simple_wmi_attribute(dev, attr, buf, count, ++ WMI_GUID_LENOVO_GPU_METHOD, 0, ++ WMI_METHOD_ID_CPU_SET_PEAK_POWERLIMIT, ++ false, 1); ++} ++ ++static DEVICE_ATTR_RW(cpu_peak_powerlimit); ++ ++static ssize_t cpu_apu_sppt_powerlimit_show(struct device *dev, ++ struct device_attribute *attr, ++ char *buf) ++{ ++ return show_simple_wmi_attribute( ++ dev, attr, buf, WMI_GUID_LENOVO_GPU_METHOD, 0, ++ WMI_METHOD_ID_CPU_GET_APU_SPPT_POWERLIMIT, false, 1); ++} ++ ++static ssize_t cpu_apu_sppt_powerlimit_store(struct device *dev, ++ struct device_attribute *attr, ++ const char *buf, size_t count) ++{ ++ return store_simple_wmi_attribute( ++ dev, attr, buf, count, WMI_GUID_LENOVO_GPU_METHOD, 0, ++ WMI_METHOD_ID_CPU_SET_APU_SPPT_POWERLIMIT, false, 1); ++} ++ ++static DEVICE_ATTR_RW(cpu_apu_sppt_powerlimit); ++ ++static ssize_t cpu_cross_loading_powerlimit_show(struct device *dev, ++ struct device_attribute *attr, ++ char *buf) ++{ ++ return show_simple_wmi_attribute( ++ dev, attr, buf, WMI_GUID_LENOVO_GPU_METHOD, 0, ++ WMI_METHOD_ID_CPU_GET_CROSS_LOADING_POWERLIMIT, false, 1); ++} ++ ++static ssize_t cpu_cross_loading_powerlimit_store(struct device *dev, ++ struct device_attribute *attr, ++ const char *buf, size_t count) ++{ ++ return store_simple_wmi_attribute( ++ dev, attr, buf, count, WMI_GUID_LENOVO_GPU_METHOD, 0, ++ WMI_METHOD_ID_CPU_SET_CROSS_LOADING_POWERLIMIT, false, 1); ++} ++ ++static DEVICE_ATTR_RW(cpu_cross_loading_powerlimit); ++ ++static ssize_t gpu_oc_show(struct device *dev, struct device_attribute *attr, ++ char *buf) ++{ ++ return show_simple_wmi_attribute(dev, attr, buf, ++ WMI_GUID_LENOVO_GPU_METHOD, 0, ++ WMI_METHOD_ID_GPU_GET_OC_STATUS, false, ++ 1); ++} ++ ++static ssize_t gpu_oc_store(struct device *dev, struct device_attribute *attr, ++ const char *buf, size_t count) ++{ ++ return store_simple_wmi_attribute(dev, attr, buf, count, ++ WMI_GUID_LENOVO_GPU_METHOD, 0, ++ WMI_METHOD_ID_GPU_SET_OC_STATUS, ++ false, 1); ++} ++ ++static DEVICE_ATTR_RW(gpu_oc); ++ ++static ssize_t gpu_ppab_powerlimit_show(struct device *dev, ++ struct device_attribute *attr, ++ char *buf) ++{ ++ return show_simple_wmi_attribute_from_buffer( ++ dev, attr, buf, WMI_GUID_LENOVO_GPU_METHOD, 0, ++ WMI_METHOD_ID_GPU_GET_PPAB_POWERLIMIT, 16, 0, 1); ++} ++ ++static ssize_t gpu_ppab_powerlimit_store(struct device *dev, ++ struct device_attribute *attr, ++ const char *buf, size_t count) ++{ ++ return store_simple_wmi_attribute(dev, attr, buf, count, ++ WMI_GUID_LENOVO_GPU_METHOD, 0, ++ WMI_METHOD_ID_GPU_SET_PPAB_POWERLIMIT, ++ false, 1); ++} ++ ++static DEVICE_ATTR_RW(gpu_ppab_powerlimit); ++ ++static ssize_t gpu_ctgp_powerlimit_show(struct device *dev, ++ struct device_attribute *attr, ++ char *buf) ++{ ++ return show_simple_wmi_attribute_from_buffer( ++ dev, attr, buf, WMI_GUID_LENOVO_GPU_METHOD, 0, ++ WMI_METHOD_ID_GPU_GET_CTGP_POWERLIMIT, 16, 0, 1); ++} ++ ++static ssize_t gpu_ctgp_powerlimit_store(struct device *dev, ++ struct device_attribute *attr, ++ const char *buf, size_t count) ++{ ++ return store_simple_wmi_attribute(dev, attr, buf, count, ++ WMI_GUID_LENOVO_GPU_METHOD, 0, ++ WMI_METHOD_ID_GPU_SET_CTGP_POWERLIMIT, ++ false, 1); ++} ++ ++static DEVICE_ATTR_RW(gpu_ctgp_powerlimit); ++ ++static ssize_t gpu_ctgp2_powerlimit_show(struct device *dev, ++ struct device_attribute *attr, ++ char *buf) ++{ ++ return show_simple_wmi_attribute_from_buffer( ++ dev, attr, buf, WMI_GUID_LENOVO_GPU_METHOD, 0, ++ WMI_METHOD_ID_GPU_GET_CTGP_POWERLIMIT, 16, 0x0C, 1); ++} ++ ++static DEVICE_ATTR_RO(gpu_ctgp2_powerlimit); ++ ++// TOOD: probably remove again because provided by other means; only useful for overclocking ++static ssize_t ++gpu_default_ppab_ctrgp_powerlimit_show(struct device *dev, ++ struct device_attribute *attr, char *buf) ++{ ++ return show_simple_wmi_attribute( ++ dev, attr, buf, WMI_GUID_LENOVO_GPU_METHOD, 0, ++ WMI_METHOD_ID_GPU_GET_DEFAULT_PPAB_CTGP_POWERLIMIT, false, 1); ++} ++static DEVICE_ATTR_RO(gpu_default_ppab_ctrgp_powerlimit); ++ ++static ssize_t gpu_temperature_limit_show(struct device *dev, ++ struct device_attribute *attr, ++ char *buf) ++{ ++ return show_simple_wmi_attribute( ++ dev, attr, buf, WMI_GUID_LENOVO_GPU_METHOD, 0, ++ WMI_METHOD_ID_GPU_GET_TEMPERATURE_LIMIT, false, 1); ++} ++ ++static ssize_t gpu_temperature_limit_store(struct device *dev, ++ struct device_attribute *attr, ++ const char *buf, size_t count) ++{ ++ return store_simple_wmi_attribute( ++ dev, attr, buf, count, WMI_GUID_LENOVO_GPU_METHOD, 0, ++ WMI_METHOD_ID_GPU_SET_TEMPERATURE_LIMIT, false, 1); ++} ++ ++static DEVICE_ATTR_RW(gpu_temperature_limit); ++ ++// TOOD: probably remove again because provided by other means; only useful for overclocking ++static ssize_t gpu_boost_clock_show(struct device *dev, ++ struct device_attribute *attr, char *buf) ++{ ++ return show_simple_wmi_attribute(dev, attr, buf, ++ WMI_GUID_LENOVO_GPU_METHOD, 0, ++ WMI_METHOD_ID_GPU_GET_BOOST_CLOCK, ++ false, 1); ++} ++static DEVICE_ATTR_RO(gpu_boost_clock); ++ ++static ssize_t fan_fullspeed_show(struct device *dev, ++ struct device_attribute *attr, char *buf) ++{ ++ bool state = false; ++ int err; ++ struct legion_private *priv = dev_get_drvdata(dev); ++ ++ mutex_lock(&priv->fancurve_mutex); ++ err = read_fanfullspeed(priv, &state); ++ mutex_unlock(&priv->fancurve_mutex); ++ if (err) ++ return -EINVAL; ++ ++ return sysfs_emit(buf, "%d\n", state); ++} ++ ++static ssize_t fan_fullspeed_store(struct device *dev, ++ struct device_attribute *attr, ++ const char *buf, size_t count) ++{ ++ struct legion_private *priv = dev_get_drvdata(dev); ++ int state; ++ int err; ++ ++ err = kstrtouint(buf, 0, &state); ++ if (err) ++ return err; ++ ++ mutex_lock(&priv->fancurve_mutex); ++ err = write_fanfullspeed(priv, state); ++ mutex_unlock(&priv->fancurve_mutex); ++ if (err) ++ return -EINVAL; ++ ++ return count; ++} ++ ++static DEVICE_ATTR_RW(fan_fullspeed); ++ ++static ssize_t fan_maxspeed_show(struct device *dev, ++ struct device_attribute *attr, char *buf) ++{ ++ return show_simple_wmi_attribute(dev, attr, buf, ++ WMI_GUID_LENOVO_FAN_METHOD, 0, ++ WMI_METHOD_ID_FAN_GET_MAXSPEED, false, ++ 1); ++} ++ ++static ssize_t fan_maxspeed_store(struct device *dev, ++ struct device_attribute *attr, ++ const char *buf, size_t count) ++{ ++ return store_simple_wmi_attribute(dev, attr, buf, count, ++ WMI_GUID_LENOVO_FAN_METHOD, 0, ++ WMI_METHOD_ID_FAN_SET_MAXSPEED, false, ++ 1); ++} ++ ++static DEVICE_ATTR_RW(fan_maxspeed); ++ ++static ssize_t powermode_show(struct device *dev, struct device_attribute *attr, ++ char *buf) ++{ ++ struct legion_private *priv = dev_get_drvdata(dev); ++ int power_mode; ++ ++ mutex_lock(&priv->fancurve_mutex); ++ read_powermode(priv, &power_mode); ++ mutex_unlock(&priv->fancurve_mutex); ++ return sysfs_emit(buf, "%d\n", power_mode); ++} ++ ++static void legion_platform_profile_notify(void); ++ ++static ssize_t powermode_store(struct device *dev, ++ struct device_attribute *attr, const char *buf, ++ size_t count) ++{ ++ struct legion_private *priv = dev_get_drvdata(dev); ++ int powermode; ++ int err; ++ ++ err = kstrtouint(buf, 0, &powermode); ++ if (err) ++ return err; ++ ++ mutex_lock(&priv->fancurve_mutex); ++ err = write_powermode(priv, powermode); ++ mutex_unlock(&priv->fancurve_mutex); ++ if (err) ++ return -EINVAL; ++ ++ // TODO: better? ++ // we have to wait a bit before change is done in hardware and ++ // readback done after notifying returns correct value, otherwise ++ // the notified reader will read old value ++ msleep(500); ++ legion_platform_profile_notify(); ++ ++ return count; ++} ++ ++static DEVICE_ATTR_RW(powermode); ++ ++static struct attribute *legion_sysfs_attributes[] = { ++ &dev_attr_powermode.attr, ++ &dev_attr_lockfancontroller.attr, ++ &dev_attr_rapidcharge.attr, ++ &dev_attr_winkey.attr, ++ &dev_attr_touchpad.attr, ++ &dev_attr_gsync.attr, ++ &dev_attr_powerchargemode.attr, ++ &dev_attr_overdrive.attr, ++ &dev_attr_cpumaxfrequency.attr, ++ &dev_attr_isacfitforoc.attr, ++ &dev_attr_cpu_oc.attr, ++ &dev_attr_cpu_shortterm_powerlimit.attr, ++ &dev_attr_cpu_longterm_powerlimit.attr, ++ &dev_attr_cpu_apu_sppt_powerlimit.attr, ++ &dev_attr_cpu_default_powerlimit.attr, ++ &dev_attr_cpu_peak_powerlimit.attr, ++ &dev_attr_cpu_cross_loading_powerlimit.attr, ++ &dev_attr_gpu_oc.attr, ++ &dev_attr_gpu_ppab_powerlimit.attr, ++ &dev_attr_gpu_ctgp_powerlimit.attr, ++ &dev_attr_gpu_ctgp2_powerlimit.attr, ++ &dev_attr_gpu_default_ppab_ctrgp_powerlimit.attr, ++ &dev_attr_gpu_temperature_limit.attr, ++ &dev_attr_gpu_boost_clock.attr, ++ &dev_attr_fan_fullspeed.attr, ++ &dev_attr_fan_maxspeed.attr, ++ &dev_attr_thermalmode.attr, ++ &dev_attr_issupportcpuoc.attr, ++ &dev_attr_issupportgpuoc.attr, ++ &dev_attr_aslcodeversion.attr, ++ &dev_attr_igpumode.attr, ++ NULL ++}; ++ ++static const struct attribute_group legion_attribute_group = { ++ .attrs = legion_sysfs_attributes ++}; ++ ++static int legion_sysfs_init(struct legion_private *priv) ++{ ++ return device_add_group(&priv->platform_device->dev, ++ &legion_attribute_group); ++} ++ ++static void legion_sysfs_exit(struct legion_private *priv) ++{ ++ pr_info("Unloading legion sysfs\n"); ++ device_remove_group(&priv->platform_device->dev, ++ &legion_attribute_group); ++ pr_info("Unloading legion sysfs done\n"); ++} ++ ++/* ============================= */ ++/* WMI + ACPI */ ++/* ============================ */ ++// heavily based on ideapad_laptop.c ++ ++// TODO: proper names if meaning of all events is clear ++enum LEGION_WMI_EVENT { ++ LEGION_WMI_EVENT_GAMEZONE = 1, ++ LEGION_EVENT_A, ++ LEGION_EVENT_B, ++ LEGION_EVENT_C, ++ LEGION_EVENT_D, ++ LEGION_EVENT_E, ++ LEGION_EVENT_F, ++ LEGION_EVENT_G ++}; ++ ++struct legion_wmi_private { ++ enum LEGION_WMI_EVENT event; ++}; ++ ++//static void legion_wmi_notify2(u32 value, void *context) ++// { ++// pr_info("WMI notify\n" ); ++// } ++ ++static void legion_wmi_notify(struct wmi_device *wdev, union acpi_object *data) ++{ ++ struct legion_wmi_private *wpriv; ++ struct legion_private *priv; ++ ++ mutex_lock(&legion_shared_mutex); ++ priv = legion_shared; ++ if ((!priv) && (priv->loaded)) { ++ pr_info("Received WMI event while not initialized!\n"); ++ goto unlock; ++ } ++ ++ wpriv = dev_get_drvdata(&wdev->dev); ++ switch (wpriv->event) { ++ case LEGION_EVENT_A: ++ pr_info("Fan event: legion type: %d; acpi type: %d (%d=integer)", ++ wpriv->event, data->type, ACPI_TYPE_INTEGER); ++ // TODO: here it is too early (first unlock mutext, then wait a bit) ++ //legion_platform_profile_notify(); ++ break; ++ default: ++ pr_info("Event: legion type: %d; acpi type: %d (%d=integer)", ++ wpriv->event, data->type, ACPI_TYPE_INTEGER); ++ break; ++ } ++ ++unlock: ++ mutex_unlock(&legion_shared_mutex); ++ // todo; fix that! ++ // problem: we get a event just before the powermode change (from the key?), ++ // so if we notify to early, it will read the old power mode/platform profile ++ msleep(500); ++ legion_platform_profile_notify(); ++} ++ ++static int legion_wmi_probe(struct wmi_device *wdev, const void *context) ++{ ++ struct legion_wmi_private *wpriv; ++ ++ wpriv = devm_kzalloc(&wdev->dev, sizeof(*wpriv), GFP_KERNEL); ++ if (!wpriv) ++ return -ENOMEM; ++ ++ *wpriv = *(const struct legion_wmi_private *)context; ++ ++ dev_set_drvdata(&wdev->dev, wpriv); ++ dev_info(&wdev->dev, "Register after probing for WMI.\n"); ++ return 0; ++} ++ ++static const struct legion_wmi_private legion_wmi_context_gamezone = { ++ .event = LEGION_WMI_EVENT_GAMEZONE ++}; ++static const struct legion_wmi_private legion_wmi_context_a = { ++ .event = LEGION_EVENT_A ++}; ++static const struct legion_wmi_private legion_wmi_context_b = { ++ .event = LEGION_EVENT_B ++}; ++static const struct legion_wmi_private legion_wmi_context_c = { ++ .event = LEGION_EVENT_C ++}; ++static const struct legion_wmi_private legion_wmi_context_d = { ++ .event = LEGION_EVENT_D ++}; ++static const struct legion_wmi_private legion_wmi_context_e = { ++ .event = LEGION_EVENT_E ++}; ++static const struct legion_wmi_private legion_wmi_context_f = { ++ .event = LEGION_EVENT_F ++}; ++ ++#define LEGION_WMI_GUID_FAN_EVENT "D320289E-8FEA-41E0-86F9-611D83151B5F" ++#define LEGION_WMI_GUID_FAN2_EVENT "bc72a435-e8c1-4275-b3e2-d8b8074aba59" ++#define LEGION_WMI_GUID_GAMEZONE_KEY_EVENT \ ++ "10afc6d9-ea8b-4590-a2e7-1cd3c84bb4b1" ++#define LEGION_WMI_GUID_GAMEZONE_GPU_EVENT \ ++ "bfd42481-aee3-4502-a107-afb68425c5f8" ++#define LEGION_WMI_GUID_GAMEZONE_OC_EVENT "d062906b-12d4-4510-999d-4831ee80e985" ++#define LEGION_WMI_GUID_GAMEZONE_TEMP_EVENT \ ++ "bfd42481-aee3-4501-a107-afb68425c5f8" ++//#define LEGION_WMI_GUID_GAMEZONE_DATA_EVENT "887b54e3-dddc-4b2c-8b88-68a26a8835d0" ++ ++static const struct wmi_device_id legion_wmi_ids[] = { ++ { LEGION_WMI_GAMEZONE_GUID, &legion_wmi_context_gamezone }, ++ { LEGION_WMI_GUID_FAN_EVENT, &legion_wmi_context_a }, ++ { LEGION_WMI_GUID_FAN2_EVENT, &legion_wmi_context_b }, ++ { LEGION_WMI_GUID_GAMEZONE_KEY_EVENT, &legion_wmi_context_c }, ++ { LEGION_WMI_GUID_GAMEZONE_GPU_EVENT, &legion_wmi_context_d }, ++ { LEGION_WMI_GUID_GAMEZONE_OC_EVENT, &legion_wmi_context_e }, ++ { LEGION_WMI_GUID_GAMEZONE_TEMP_EVENT, &legion_wmi_context_f }, ++ { "8FC0DE0C-B4E4-43FD-B0F3-8871711C1294", ++ &legion_wmi_context_gamezone }, /* Legion 5 */ ++ {}, ++}; ++MODULE_DEVICE_TABLE(wmi, legion_wmi_ids); ++ ++static struct wmi_driver legion_wmi_driver = { ++ .driver = { ++ .name = "legion_wmi", ++ }, ++ .id_table = legion_wmi_ids, ++ .probe = legion_wmi_probe, ++ .notify = legion_wmi_notify, ++}; ++ ++//acpi_status status = wmi_install_notify_handler(LEGION_WMI_GAMEZONE_GUID, ++// legion_wmi_notify2, NULL); ++//if (ACPI_FAILURE(status)) { ++// return -ENODEV; ++//} ++//return 0; ++ ++static int legion_wmi_init(void) ++{ ++ return wmi_driver_register(&legion_wmi_driver); ++} ++ ++static void legion_wmi_exit(void) ++{ ++ // TODO: remove this ++ pr_info("Unloading legion WMI\n"); ++ ++ //wmi_remove_notify_handler(LEGION_WMI_GAMEZONE_GUID); ++ wmi_driver_unregister(&legion_wmi_driver); ++ pr_info("Unloading legion WMI done\n"); ++} ++ ++/* ============================= */ ++/* Platform profile */ ++/* ============================ */ ++ ++static void legion_platform_profile_notify(void) ++{ ++ if (!enable_platformprofile) ++ pr_info("Skipping platform_profile_notify because enable_platformprofile is false\n"); ++ ++ platform_profile_notify(); ++} ++ ++static int legion_platform_profile_get(struct platform_profile_handler *pprof, ++ enum platform_profile_option *profile) ++{ ++ int powermode; ++ struct legion_private *priv; ++ ++ priv = container_of(pprof, struct legion_private, ++ platform_profile_handler); ++ read_powermode(priv, &powermode); ++ ++ switch (powermode) { ++ case LEGION_WMI_POWERMODE_BALANCED: ++ *profile = PLATFORM_PROFILE_BALANCED; ++ break; ++ case LEGION_WMI_POWERMODE_PERFORMANCE: ++ *profile = PLATFORM_PROFILE_PERFORMANCE; ++ break; ++ case LEGION_WMI_POWERMODE_QUIET: ++ *profile = PLATFORM_PROFILE_QUIET; ++ break; ++ case LEGION_WMI_POWERMODE_CUSTOM: ++ *profile = PLATFORM_PROFILE_BALANCED_PERFORMANCE; ++ break; ++ default: ++ return -EINVAL; ++ } ++ return 0; ++} ++ ++static int legion_platform_profile_set(struct platform_profile_handler *pprof, ++ enum platform_profile_option profile) ++{ ++ int powermode; ++ struct legion_private *priv; ++ ++ priv = container_of(pprof, struct legion_private, ++ platform_profile_handler); ++ ++ switch (profile) { ++ case PLATFORM_PROFILE_BALANCED: ++ powermode = LEGION_WMI_POWERMODE_BALANCED; ++ break; ++ case PLATFORM_PROFILE_PERFORMANCE: ++ powermode = LEGION_WMI_POWERMODE_PERFORMANCE; ++ break; ++ case PLATFORM_PROFILE_QUIET: ++ powermode = LEGION_WMI_POWERMODE_QUIET; ++ break; ++ case PLATFORM_PROFILE_BALANCED_PERFORMANCE: ++ powermode = LEGION_WMI_POWERMODE_CUSTOM; ++ break; ++ default: ++ return -EOPNOTSUPP; ++ } ++ ++ return write_powermode(priv, powermode); ++} ++ ++static int legion_platform_profile_init(struct legion_private *priv) ++{ ++ int err; ++ ++ if (!enable_platformprofile) { ++ pr_info("Skipping creating platform profile support because enable_platformprofile is false\n"); ++ return 0; ++ } ++ ++ priv->platform_profile_handler.profile_get = ++ legion_platform_profile_get; ++ priv->platform_profile_handler.profile_set = ++ legion_platform_profile_set; ++ ++ set_bit(PLATFORM_PROFILE_QUIET, priv->platform_profile_handler.choices); ++ set_bit(PLATFORM_PROFILE_BALANCED, ++ priv->platform_profile_handler.choices); ++ set_bit(PLATFORM_PROFILE_PERFORMANCE, ++ priv->platform_profile_handler.choices); ++ if (priv->conf->has_custom_powermode && ++ priv->conf->access_method_powermode == ACCESS_METHOD_WMI) { ++ set_bit(PLATFORM_PROFILE_BALANCED_PERFORMANCE, ++ priv->platform_profile_handler.choices); ++ } ++ ++ err = platform_profile_register(&priv->platform_profile_handler); ++ if (err) ++ return err; ++ ++ return 0; ++} ++ ++static void legion_platform_profile_exit(struct legion_private *priv) ++{ ++ if (!enable_platformprofile) { ++ pr_info("Skipping unloading platform profile support because enable_platformprofile is false\n"); ++ return; ++ } ++ pr_info("Unloading legion platform profile\n"); ++ platform_profile_remove(); ++ pr_info("Unloading legion platform profile done\n"); ++} ++ ++/* ============================= */ ++/* hwom interface */ ++/* ============================ */ ++ ++// hw-mon interface ++ ++// todo: register_group or register_info? ++ ++// TODO: use one common function (like here) or one function per attribute? ++static ssize_t sensor_label_show(struct device *dev, ++ struct device_attribute *attr, char *buf) ++{ ++ int sensor_id = (to_sensor_dev_attr(attr))->index; ++ const char *label; ++ ++ switch (sensor_id) { ++ case SENSOR_CPU_TEMP_ID: ++ label = "CPU Temperature\n"; ++ break; ++ case SENSOR_GPU_TEMP_ID: ++ label = "GPU Temperature\n"; ++ break; ++ case SENSOR_IC_TEMP_ID: ++ label = "IC Temperature\n"; ++ break; ++ case SENSOR_FAN1_RPM_ID: ++ label = "Fan 1\n"; ++ break; ++ case SENSOR_FAN2_RPM_ID: ++ label = "Fan 2\n"; ++ break; ++ case SENSOR_FAN1_TARGET_RPM_ID: ++ label = "Fan 1 Target\n"; ++ break; ++ case SENSOR_FAN2_TARGET_RPM_ID: ++ label = "Fan 2 Target\n"; ++ break; ++ default: ++ return -EOPNOTSUPP; ++ } ++ ++ return sprintf(buf, label); ++} ++ ++// TODO: use one common function (like here) or one function per attribute? ++static ssize_t sensor_show(struct device *dev, struct device_attribute *devattr, ++ char *buf) ++{ ++ struct legion_private *priv = dev_get_drvdata(dev); ++ int sensor_id = (to_sensor_dev_attr(devattr))->index; ++ struct sensor_values values; ++ int outval; ++ int err = -EIO; ++ ++ switch (sensor_id) { ++ case SENSOR_CPU_TEMP_ID: ++ err = read_temperature(priv, 0, &outval); ++ outval *= 1000; ++ break; ++ case SENSOR_GPU_TEMP_ID: ++ err = read_temperature(priv, 1, &outval); ++ outval *= 1000; ++ break; ++ case SENSOR_IC_TEMP_ID: ++ ec_read_sensor_values(&priv->ecram, priv->conf, &values); ++ outval = 1000 * values.ic_temp_celsius; ++ err = 0; ++ break; ++ case SENSOR_FAN1_RPM_ID: ++ err = read_fanspeed(priv, 0, &outval); ++ break; ++ case SENSOR_FAN2_RPM_ID: ++ err = read_fanspeed(priv, 1, &outval); ++ break; ++ case SENSOR_FAN1_TARGET_RPM_ID: ++ ec_read_sensor_values(&priv->ecram, priv->conf, &values); ++ outval = values.fan1_target_rpm; ++ err = 0; ++ break; ++ case SENSOR_FAN2_TARGET_RPM_ID: ++ ec_read_sensor_values(&priv->ecram, priv->conf, &values); ++ outval = values.fan2_target_rpm; ++ err = 0; ++ break; ++ default: ++ pr_info("Error reading sensor value with id %d\n", sensor_id); ++ return -EOPNOTSUPP; ++ } ++ if (err) ++ return err; ++ ++ return sprintf(buf, "%d\n", outval); ++} ++ ++static SENSOR_DEVICE_ATTR_RO(temp1_input, sensor, SENSOR_CPU_TEMP_ID); ++static SENSOR_DEVICE_ATTR_RO(temp1_label, sensor_label, SENSOR_CPU_TEMP_ID); ++static SENSOR_DEVICE_ATTR_RO(temp2_input, sensor, SENSOR_GPU_TEMP_ID); ++static SENSOR_DEVICE_ATTR_RO(temp2_label, sensor_label, SENSOR_GPU_TEMP_ID); ++static SENSOR_DEVICE_ATTR_RO(temp3_input, sensor, SENSOR_IC_TEMP_ID); ++static SENSOR_DEVICE_ATTR_RO(temp3_label, sensor_label, SENSOR_IC_TEMP_ID); ++static SENSOR_DEVICE_ATTR_RO(fan1_input, sensor, SENSOR_FAN1_RPM_ID); ++static SENSOR_DEVICE_ATTR_RO(fan1_label, sensor_label, SENSOR_FAN1_RPM_ID); ++static SENSOR_DEVICE_ATTR_RO(fan2_input, sensor, SENSOR_FAN2_RPM_ID); ++static SENSOR_DEVICE_ATTR_RO(fan2_label, sensor_label, SENSOR_FAN2_RPM_ID); ++static SENSOR_DEVICE_ATTR_RO(fan1_target, sensor, SENSOR_FAN1_TARGET_RPM_ID); ++static SENSOR_DEVICE_ATTR_RO(fan2_target, sensor, SENSOR_FAN2_TARGET_RPM_ID); ++ ++static struct attribute *sensor_hwmon_attributes[] = { ++ &sensor_dev_attr_temp1_input.dev_attr.attr, ++ &sensor_dev_attr_temp1_label.dev_attr.attr, ++ &sensor_dev_attr_temp2_input.dev_attr.attr, ++ &sensor_dev_attr_temp2_label.dev_attr.attr, ++ &sensor_dev_attr_temp3_input.dev_attr.attr, ++ &sensor_dev_attr_temp3_label.dev_attr.attr, ++ &sensor_dev_attr_fan1_input.dev_attr.attr, ++ &sensor_dev_attr_fan1_label.dev_attr.attr, ++ &sensor_dev_attr_fan2_input.dev_attr.attr, ++ &sensor_dev_attr_fan2_label.dev_attr.attr, ++ &sensor_dev_attr_fan1_target.dev_attr.attr, ++ &sensor_dev_attr_fan2_target.dev_attr.attr, ++ NULL ++}; ++ ++static ssize_t autopoint_show(struct device *dev, ++ struct device_attribute *devattr, char *buf) ++{ ++ struct fancurve fancurve; ++ int err; ++ int value; ++ struct legion_private *priv = dev_get_drvdata(dev); ++ int fancurve_attr_id = to_sensor_dev_attr_2(devattr)->nr; ++ int point_id = to_sensor_dev_attr_2(devattr)->index; ++ ++ mutex_lock(&priv->fancurve_mutex); ++ err = read_fancurve(priv, &fancurve); ++ mutex_unlock(&priv->fancurve_mutex); ++ ++ if (err) { ++ pr_info("Reading fancurve failed\n"); ++ return -EOPNOTSUPP; ++ } ++ if (!(point_id >= 0 && point_id < MAXFANCURVESIZE)) { ++ pr_info("Reading fancurve failed due to wrong point id: %d\n", ++ point_id); ++ return -EOPNOTSUPP; ++ } ++ ++ switch (fancurve_attr_id) { ++ case FANCURVE_ATTR_PWM1: ++ value = fancurve.points[point_id].rpm1_raw * 100; ++ break; ++ case FANCURVE_ATTR_PWM2: ++ value = fancurve.points[point_id].rpm2_raw * 100; ++ break; ++ case FANCURVE_ATTR_CPU_TEMP: ++ value = fancurve.points[point_id].cpu_max_temp_celsius; ++ break; ++ case FANCURVE_ATTR_CPU_HYST: ++ value = fancurve.points[point_id].cpu_min_temp_celsius; ++ break; ++ case FANCURVE_ATTR_GPU_TEMP: ++ value = fancurve.points[point_id].gpu_max_temp_celsius; ++ break; ++ case FANCURVE_ATTR_GPU_HYST: ++ value = fancurve.points[point_id].gpu_min_temp_celsius; ++ break; ++ case FANCURVE_ATTR_IC_TEMP: ++ value = fancurve.points[point_id].ic_max_temp_celsius; ++ break; ++ case FANCURVE_ATTR_IC_HYST: ++ value = fancurve.points[point_id].ic_min_temp_celsius; ++ break; ++ case FANCURVE_ATTR_ACCEL: ++ value = fancurve.points[point_id].accel; ++ break; ++ case FANCURVE_ATTR_DECEL: ++ value = fancurve.points[point_id].decel; ++ break; ++ case FANCURVE_SIZE: ++ value = fancurve.size; ++ break; ++ default: ++ pr_info("Reading fancurve failed due to wrong attribute id: %d\n", ++ fancurve_attr_id); ++ return -EOPNOTSUPP; ++ } ++ ++ return sprintf(buf, "%d\n", value); ++} ++ ++static ssize_t autopoint_store(struct device *dev, ++ struct device_attribute *devattr, ++ const char *buf, size_t count) ++{ ++ struct fancurve fancurve; ++ int err; ++ int value; ++ bool valid; ++ struct legion_private *priv = dev_get_drvdata(dev); ++ int fancurve_attr_id = to_sensor_dev_attr_2(devattr)->nr; ++ int point_id = to_sensor_dev_attr_2(devattr)->index; ++ bool write_fancurve_size = false; ++ ++ if (!(point_id >= 0 && point_id < MAXFANCURVESIZE)) { ++ pr_info("Reading fancurve failed due to wrong point id: %d\n", ++ point_id); ++ err = -EOPNOTSUPP; ++ goto error; ++ } ++ ++ err = kstrtoint(buf, 0, &value); ++ if (err) { ++ pr_info("Parse for hwmon store is not succesful: error:%d; point_id: %d; fancurve_attr_id: %d\\n", ++ err, point_id, fancurve_attr_id); ++ goto error; ++ } ++ ++ mutex_lock(&priv->fancurve_mutex); ++ err = read_fancurve(priv, &fancurve); ++ ++ if (err) { ++ pr_info("Reading fancurve failed\n"); ++ err = -EOPNOTSUPP; ++ goto error_mutex; ++ } ++ ++ switch (fancurve_attr_id) { ++ case FANCURVE_ATTR_PWM1: ++ valid = fancurve_set_rpm1(&fancurve, point_id, value); ++ break; ++ case FANCURVE_ATTR_PWM2: ++ valid = fancurve_set_rpm2(&fancurve, point_id, value); ++ break; ++ case FANCURVE_ATTR_CPU_TEMP: ++ valid = fancurve_set_cpu_temp_max(&fancurve, point_id, value); ++ break; ++ case FANCURVE_ATTR_CPU_HYST: ++ valid = fancurve_set_cpu_temp_min(&fancurve, point_id, value); ++ break; ++ case FANCURVE_ATTR_GPU_TEMP: ++ valid = fancurve_set_gpu_temp_max(&fancurve, point_id, value); ++ break; ++ case FANCURVE_ATTR_GPU_HYST: ++ valid = fancurve_set_gpu_temp_min(&fancurve, point_id, value); ++ break; ++ case FANCURVE_ATTR_IC_TEMP: ++ valid = fancurve_set_ic_temp_max(&fancurve, point_id, value); ++ break; ++ case FANCURVE_ATTR_IC_HYST: ++ valid = fancurve_set_ic_temp_min(&fancurve, point_id, value); ++ break; ++ case FANCURVE_ATTR_ACCEL: ++ valid = fancurve_set_accel(&fancurve, point_id, value); ++ break; ++ case FANCURVE_ATTR_DECEL: ++ valid = fancurve_set_decel(&fancurve, point_id, value); ++ break; ++ case FANCURVE_SIZE: ++ valid = fancurve_set_size(&fancurve, value, true); ++ write_fancurve_size = true; ++ break; ++ default: ++ pr_info("Writing fancurve failed due to wrong attribute id: %d\n", ++ fancurve_attr_id); ++ err = -EOPNOTSUPP; ++ goto error_mutex; ++ } ++ ++ if (!valid) { ++ pr_info("Ignoring invalid fancurve value %d for attribute %d at point %d\n", ++ value, fancurve_attr_id, point_id); ++ err = -EOPNOTSUPP; ++ goto error_mutex; ++ } ++ ++ err = write_fancurve(priv, &fancurve, write_fancurve_size); ++ if (err) { ++ pr_info("Writing fancurve failed for accessing hwmon at point_id: %d\n", ++ point_id); ++ err = -EOPNOTSUPP; ++ goto error_mutex; ++ } ++ ++ mutex_unlock(&priv->fancurve_mutex); ++ return count; ++ ++error_mutex: ++ mutex_unlock(&priv->fancurve_mutex); ++error: ++ return count; ++} ++ ++// rpm1 ++static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point1_pwm, autopoint, ++ FANCURVE_ATTR_PWM1, 0); ++static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point2_pwm, autopoint, ++ FANCURVE_ATTR_PWM1, 1); ++static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point3_pwm, autopoint, ++ FANCURVE_ATTR_PWM1, 2); ++static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point4_pwm, autopoint, ++ FANCURVE_ATTR_PWM1, 3); ++static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point5_pwm, autopoint, ++ FANCURVE_ATTR_PWM1, 4); ++static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point6_pwm, autopoint, ++ FANCURVE_ATTR_PWM1, 5); ++static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point7_pwm, autopoint, ++ FANCURVE_ATTR_PWM1, 6); ++static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point8_pwm, autopoint, ++ FANCURVE_ATTR_PWM1, 7); ++static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point9_pwm, autopoint, ++ FANCURVE_ATTR_PWM1, 8); ++static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point10_pwm, autopoint, ++ FANCURVE_ATTR_PWM1, 9); ++// rpm2 ++static SENSOR_DEVICE_ATTR_2_RW(pwm2_auto_point1_pwm, autopoint, ++ FANCURVE_ATTR_PWM2, 0); ++static SENSOR_DEVICE_ATTR_2_RW(pwm2_auto_point2_pwm, autopoint, ++ FANCURVE_ATTR_PWM2, 1); ++static SENSOR_DEVICE_ATTR_2_RW(pwm2_auto_point3_pwm, autopoint, ++ FANCURVE_ATTR_PWM2, 2); ++static SENSOR_DEVICE_ATTR_2_RW(pwm2_auto_point4_pwm, autopoint, ++ FANCURVE_ATTR_PWM2, 3); ++static SENSOR_DEVICE_ATTR_2_RW(pwm2_auto_point5_pwm, autopoint, ++ FANCURVE_ATTR_PWM2, 4); ++static SENSOR_DEVICE_ATTR_2_RW(pwm2_auto_point6_pwm, autopoint, ++ FANCURVE_ATTR_PWM2, 5); ++static SENSOR_DEVICE_ATTR_2_RW(pwm2_auto_point7_pwm, autopoint, ++ FANCURVE_ATTR_PWM2, 6); ++static SENSOR_DEVICE_ATTR_2_RW(pwm2_auto_point8_pwm, autopoint, ++ FANCURVE_ATTR_PWM2, 7); ++static SENSOR_DEVICE_ATTR_2_RW(pwm2_auto_point9_pwm, autopoint, ++ FANCURVE_ATTR_PWM2, 8); ++static SENSOR_DEVICE_ATTR_2_RW(pwm2_auto_point10_pwm, autopoint, ++ FANCURVE_ATTR_PWM2, 9); ++// CPU temp ++static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point1_temp, autopoint, ++ FANCURVE_ATTR_CPU_TEMP, 0); ++static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point2_temp, autopoint, ++ FANCURVE_ATTR_CPU_TEMP, 1); ++static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point3_temp, autopoint, ++ FANCURVE_ATTR_CPU_TEMP, 2); ++static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point4_temp, autopoint, ++ FANCURVE_ATTR_CPU_TEMP, 3); ++static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point5_temp, autopoint, ++ FANCURVE_ATTR_CPU_TEMP, 4); ++static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point6_temp, autopoint, ++ FANCURVE_ATTR_CPU_TEMP, 5); ++static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point7_temp, autopoint, ++ FANCURVE_ATTR_CPU_TEMP, 6); ++static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point8_temp, autopoint, ++ FANCURVE_ATTR_CPU_TEMP, 7); ++static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point9_temp, autopoint, ++ FANCURVE_ATTR_CPU_TEMP, 8); ++static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point10_temp, autopoint, ++ FANCURVE_ATTR_CPU_TEMP, 9); ++// CPU temp hyst ++static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point1_temp_hyst, autopoint, ++ FANCURVE_ATTR_CPU_HYST, 0); ++static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point2_temp_hyst, autopoint, ++ FANCURVE_ATTR_CPU_HYST, 1); ++static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point3_temp_hyst, autopoint, ++ FANCURVE_ATTR_CPU_HYST, 2); ++static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point4_temp_hyst, autopoint, ++ FANCURVE_ATTR_CPU_HYST, 3); ++static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point5_temp_hyst, autopoint, ++ FANCURVE_ATTR_CPU_HYST, 4); ++static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point6_temp_hyst, autopoint, ++ FANCURVE_ATTR_CPU_HYST, 5); ++static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point7_temp_hyst, autopoint, ++ FANCURVE_ATTR_CPU_HYST, 6); ++static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point8_temp_hyst, autopoint, ++ FANCURVE_ATTR_CPU_HYST, 7); ++static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point9_temp_hyst, autopoint, ++ FANCURVE_ATTR_CPU_HYST, 8); ++static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point10_temp_hyst, autopoint, ++ FANCURVE_ATTR_CPU_HYST, 9); ++// GPU temp ++static SENSOR_DEVICE_ATTR_2_RW(pwm2_auto_point1_temp, autopoint, ++ FANCURVE_ATTR_GPU_TEMP, 0); ++static SENSOR_DEVICE_ATTR_2_RW(pwm2_auto_point2_temp, autopoint, ++ FANCURVE_ATTR_GPU_TEMP, 1); ++static SENSOR_DEVICE_ATTR_2_RW(pwm2_auto_point3_temp, autopoint, ++ FANCURVE_ATTR_GPU_TEMP, 2); ++static SENSOR_DEVICE_ATTR_2_RW(pwm2_auto_point4_temp, autopoint, ++ FANCURVE_ATTR_GPU_TEMP, 3); ++static SENSOR_DEVICE_ATTR_2_RW(pwm2_auto_point5_temp, autopoint, ++ FANCURVE_ATTR_GPU_TEMP, 4); ++static SENSOR_DEVICE_ATTR_2_RW(pwm2_auto_point6_temp, autopoint, ++ FANCURVE_ATTR_GPU_TEMP, 5); ++static SENSOR_DEVICE_ATTR_2_RW(pwm2_auto_point7_temp, autopoint, ++ FANCURVE_ATTR_GPU_TEMP, 6); ++static SENSOR_DEVICE_ATTR_2_RW(pwm2_auto_point8_temp, autopoint, ++ FANCURVE_ATTR_GPU_TEMP, 7); ++static SENSOR_DEVICE_ATTR_2_RW(pwm2_auto_point9_temp, autopoint, ++ FANCURVE_ATTR_GPU_TEMP, 8); ++static SENSOR_DEVICE_ATTR_2_RW(pwm2_auto_point10_temp, autopoint, ++ FANCURVE_ATTR_GPU_TEMP, 9); ++// GPU temp hyst ++static SENSOR_DEVICE_ATTR_2_RW(pwm2_auto_point1_temp_hyst, autopoint, ++ FANCURVE_ATTR_GPU_HYST, 0); ++static SENSOR_DEVICE_ATTR_2_RW(pwm2_auto_point2_temp_hyst, autopoint, ++ FANCURVE_ATTR_GPU_HYST, 1); ++static SENSOR_DEVICE_ATTR_2_RW(pwm2_auto_point3_temp_hyst, autopoint, ++ FANCURVE_ATTR_GPU_HYST, 2); ++static SENSOR_DEVICE_ATTR_2_RW(pwm2_auto_point4_temp_hyst, autopoint, ++ FANCURVE_ATTR_GPU_HYST, 3); ++static SENSOR_DEVICE_ATTR_2_RW(pwm2_auto_point5_temp_hyst, autopoint, ++ FANCURVE_ATTR_GPU_HYST, 4); ++static SENSOR_DEVICE_ATTR_2_RW(pwm2_auto_point6_temp_hyst, autopoint, ++ FANCURVE_ATTR_GPU_HYST, 5); ++static SENSOR_DEVICE_ATTR_2_RW(pwm2_auto_point7_temp_hyst, autopoint, ++ FANCURVE_ATTR_GPU_HYST, 6); ++static SENSOR_DEVICE_ATTR_2_RW(pwm2_auto_point8_temp_hyst, autopoint, ++ FANCURVE_ATTR_GPU_HYST, 7); ++static SENSOR_DEVICE_ATTR_2_RW(pwm2_auto_point9_temp_hyst, autopoint, ++ FANCURVE_ATTR_GPU_HYST, 8); ++static SENSOR_DEVICE_ATTR_2_RW(pwm2_auto_point10_temp_hyst, autopoint, ++ FANCURVE_ATTR_GPU_HYST, 9); ++// IC temp ++static SENSOR_DEVICE_ATTR_2_RW(pwm3_auto_point1_temp, autopoint, ++ FANCURVE_ATTR_IC_TEMP, 0); ++static SENSOR_DEVICE_ATTR_2_RW(pwm3_auto_point2_temp, autopoint, ++ FANCURVE_ATTR_IC_TEMP, 1); ++static SENSOR_DEVICE_ATTR_2_RW(pwm3_auto_point3_temp, autopoint, ++ FANCURVE_ATTR_IC_TEMP, 2); ++static SENSOR_DEVICE_ATTR_2_RW(pwm3_auto_point4_temp, autopoint, ++ FANCURVE_ATTR_IC_TEMP, 3); ++static SENSOR_DEVICE_ATTR_2_RW(pwm3_auto_point5_temp, autopoint, ++ FANCURVE_ATTR_IC_TEMP, 4); ++static SENSOR_DEVICE_ATTR_2_RW(pwm3_auto_point6_temp, autopoint, ++ FANCURVE_ATTR_IC_TEMP, 5); ++static SENSOR_DEVICE_ATTR_2_RW(pwm3_auto_point7_temp, autopoint, ++ FANCURVE_ATTR_IC_TEMP, 6); ++static SENSOR_DEVICE_ATTR_2_RW(pwm3_auto_point8_temp, autopoint, ++ FANCURVE_ATTR_IC_TEMP, 7); ++static SENSOR_DEVICE_ATTR_2_RW(pwm3_auto_point9_temp, autopoint, ++ FANCURVE_ATTR_IC_TEMP, 8); ++static SENSOR_DEVICE_ATTR_2_RW(pwm3_auto_point10_temp, autopoint, ++ FANCURVE_ATTR_IC_TEMP, 9); ++// IC temp hyst ++static SENSOR_DEVICE_ATTR_2_RW(pwm3_auto_point1_temp_hyst, autopoint, ++ FANCURVE_ATTR_IC_HYST, 0); ++static SENSOR_DEVICE_ATTR_2_RW(pwm3_auto_point2_temp_hyst, autopoint, ++ FANCURVE_ATTR_IC_HYST, 1); ++static SENSOR_DEVICE_ATTR_2_RW(pwm3_auto_point3_temp_hyst, autopoint, ++ FANCURVE_ATTR_IC_HYST, 2); ++static SENSOR_DEVICE_ATTR_2_RW(pwm3_auto_point4_temp_hyst, autopoint, ++ FANCURVE_ATTR_IC_HYST, 3); ++static SENSOR_DEVICE_ATTR_2_RW(pwm3_auto_point5_temp_hyst, autopoint, ++ FANCURVE_ATTR_IC_HYST, 4); ++static SENSOR_DEVICE_ATTR_2_RW(pwm3_auto_point6_temp_hyst, autopoint, ++ FANCURVE_ATTR_IC_HYST, 5); ++static SENSOR_DEVICE_ATTR_2_RW(pwm3_auto_point7_temp_hyst, autopoint, ++ FANCURVE_ATTR_IC_HYST, 6); ++static SENSOR_DEVICE_ATTR_2_RW(pwm3_auto_point8_temp_hyst, autopoint, ++ FANCURVE_ATTR_IC_HYST, 7); ++static SENSOR_DEVICE_ATTR_2_RW(pwm3_auto_point9_temp_hyst, autopoint, ++ FANCURVE_ATTR_IC_HYST, 8); ++static SENSOR_DEVICE_ATTR_2_RW(pwm3_auto_point10_temp_hyst, autopoint, ++ FANCURVE_ATTR_IC_HYST, 9); ++// accel ++static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point1_accel, autopoint, ++ FANCURVE_ATTR_ACCEL, 0); ++static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point2_accel, autopoint, ++ FANCURVE_ATTR_ACCEL, 1); ++static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point3_accel, autopoint, ++ FANCURVE_ATTR_ACCEL, 2); ++static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point4_accel, autopoint, ++ FANCURVE_ATTR_ACCEL, 3); ++static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point5_accel, autopoint, ++ FANCURVE_ATTR_ACCEL, 4); ++static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point6_accel, autopoint, ++ FANCURVE_ATTR_ACCEL, 5); ++static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point7_accel, autopoint, ++ FANCURVE_ATTR_ACCEL, 6); ++static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point8_accel, autopoint, ++ FANCURVE_ATTR_ACCEL, 7); ++static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point9_accel, autopoint, ++ FANCURVE_ATTR_ACCEL, 8); ++static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point10_accel, autopoint, ++ FANCURVE_ATTR_ACCEL, 9); ++// decel ++static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point1_decel, autopoint, ++ FANCURVE_ATTR_DECEL, 0); ++static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point2_decel, autopoint, ++ FANCURVE_ATTR_DECEL, 1); ++static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point3_decel, autopoint, ++ FANCURVE_ATTR_DECEL, 2); ++static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point4_decel, autopoint, ++ FANCURVE_ATTR_DECEL, 3); ++static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point5_decel, autopoint, ++ FANCURVE_ATTR_DECEL, 4); ++static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point6_decel, autopoint, ++ FANCURVE_ATTR_DECEL, 5); ++static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point7_decel, autopoint, ++ FANCURVE_ATTR_DECEL, 6); ++static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point8_decel, autopoint, ++ FANCURVE_ATTR_DECEL, 7); ++static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point9_decel, autopoint, ++ FANCURVE_ATTR_DECEL, 8); ++static SENSOR_DEVICE_ATTR_2_RW(pwm1_auto_point10_decel, autopoint, ++ FANCURVE_ATTR_DECEL, 9); ++//size ++static SENSOR_DEVICE_ATTR_2_RW(auto_points_size, autopoint, FANCURVE_SIZE, 0); ++ ++static ssize_t minifancurve_show(struct device *dev, ++ struct device_attribute *devattr, char *buf) ++{ ++ bool value; ++ int err; ++ struct legion_private *priv = dev_get_drvdata(dev); ++ ++ mutex_lock(&priv->fancurve_mutex); ++ err = ec_read_minifancurve(&priv->ecram, priv->conf, &value); ++ if (err) { ++ err = -1; ++ pr_info("Reading minifancurve not succesful\n"); ++ goto error_unlock; ++ } ++ mutex_unlock(&priv->fancurve_mutex); ++ return sprintf(buf, "%d\n", value); ++ ++error_unlock: ++ mutex_unlock(&priv->fancurve_mutex); ++ return -1; ++} ++ ++static ssize_t minifancurve_store(struct device *dev, ++ struct device_attribute *devattr, ++ const char *buf, size_t count) ++{ ++ int value; ++ int err; ++ struct legion_private *priv = dev_get_drvdata(dev); ++ ++ err = kstrtoint(buf, 0, &value); ++ if (err) { ++ err = -1; ++ pr_info("Parse for hwmon store is not succesful: error:%d\n", ++ err); ++ goto error; ++ } ++ ++ mutex_lock(&priv->fancurve_mutex); ++ err = ec_write_minifancurve(&priv->ecram, priv->conf, value); ++ if (err) { ++ err = -1; ++ pr_info("Writing minifancurve not succesful\n"); ++ goto error_unlock; ++ } ++ mutex_unlock(&priv->fancurve_mutex); ++ return count; ++ ++error_unlock: ++ mutex_unlock(&priv->fancurve_mutex); ++error: ++ return err; ++} ++ ++static SENSOR_DEVICE_ATTR_RW(minifancurve, minifancurve, 0); ++ ++static ssize_t pwm1_mode_show(struct device *dev, ++ struct device_attribute *devattr, char *buf) ++{ ++ bool value; ++ int err; ++ struct legion_private *priv = dev_get_drvdata(dev); ++ ++ mutex_lock(&priv->fancurve_mutex); ++ err = ec_read_fanfullspeed(&priv->ecram, priv->conf, &value); ++ if (err) { ++ err = -1; ++ pr_info("Reading pwm1_mode/maximumfanspeed not succesful\n"); ++ goto error_unlock; ++ } ++ mutex_unlock(&priv->fancurve_mutex); ++ return sprintf(buf, "%d\n", value ? 0 : 2); ++ ++error_unlock: ++ mutex_unlock(&priv->fancurve_mutex); ++ return -1; ++} ++ ++// TODO: remove? or use WMI method? ++static ssize_t pwm1_mode_store(struct device *dev, ++ struct device_attribute *devattr, ++ const char *buf, size_t count) ++{ ++ int value; ++ int is_maximumfanspeed; ++ int err; ++ struct legion_private *priv = dev_get_drvdata(dev); ++ ++ err = kstrtoint(buf, 0, &value); ++ if (err) { ++ err = -1; ++ pr_info("Parse for hwmon store is not succesful: error:%d\n", ++ err); ++ goto error; ++ } ++ is_maximumfanspeed = value == 0; ++ ++ mutex_lock(&priv->fancurve_mutex); ++ err = ec_write_fanfullspeed(&priv->ecram, priv->conf, ++ is_maximumfanspeed); ++ if (err) { ++ err = -1; ++ pr_info("Writing pwm1_mode/maximumfanspeed not succesful\n"); ++ goto error_unlock; ++ } ++ mutex_unlock(&priv->fancurve_mutex); ++ return count; ++ ++error_unlock: ++ mutex_unlock(&priv->fancurve_mutex); ++error: ++ return err; ++} ++ ++static SENSOR_DEVICE_ATTR_RW(pwm1_mode, pwm1_mode, 0); ++ ++static struct attribute *fancurve_hwmon_attributes[] = { ++ &sensor_dev_attr_pwm1_auto_point1_pwm.dev_attr.attr, ++ &sensor_dev_attr_pwm1_auto_point2_pwm.dev_attr.attr, ++ &sensor_dev_attr_pwm1_auto_point3_pwm.dev_attr.attr, ++ &sensor_dev_attr_pwm1_auto_point4_pwm.dev_attr.attr, ++ &sensor_dev_attr_pwm1_auto_point5_pwm.dev_attr.attr, ++ &sensor_dev_attr_pwm1_auto_point6_pwm.dev_attr.attr, ++ &sensor_dev_attr_pwm1_auto_point7_pwm.dev_attr.attr, ++ &sensor_dev_attr_pwm1_auto_point8_pwm.dev_attr.attr, ++ &sensor_dev_attr_pwm1_auto_point9_pwm.dev_attr.attr, ++ &sensor_dev_attr_pwm1_auto_point10_pwm.dev_attr.attr, ++ &sensor_dev_attr_pwm2_auto_point1_pwm.dev_attr.attr, ++ &sensor_dev_attr_pwm2_auto_point2_pwm.dev_attr.attr, ++ &sensor_dev_attr_pwm2_auto_point3_pwm.dev_attr.attr, ++ &sensor_dev_attr_pwm2_auto_point4_pwm.dev_attr.attr, ++ &sensor_dev_attr_pwm2_auto_point5_pwm.dev_attr.attr, ++ &sensor_dev_attr_pwm2_auto_point6_pwm.dev_attr.attr, ++ &sensor_dev_attr_pwm2_auto_point7_pwm.dev_attr.attr, ++ &sensor_dev_attr_pwm2_auto_point8_pwm.dev_attr.attr, ++ &sensor_dev_attr_pwm2_auto_point9_pwm.dev_attr.attr, ++ &sensor_dev_attr_pwm2_auto_point10_pwm.dev_attr.attr, ++ &sensor_dev_attr_pwm1_auto_point1_temp.dev_attr.attr, ++ &sensor_dev_attr_pwm1_auto_point2_temp.dev_attr.attr, ++ &sensor_dev_attr_pwm1_auto_point3_temp.dev_attr.attr, ++ &sensor_dev_attr_pwm1_auto_point4_temp.dev_attr.attr, ++ &sensor_dev_attr_pwm1_auto_point5_temp.dev_attr.attr, ++ &sensor_dev_attr_pwm1_auto_point6_temp.dev_attr.attr, ++ &sensor_dev_attr_pwm1_auto_point7_temp.dev_attr.attr, ++ &sensor_dev_attr_pwm1_auto_point8_temp.dev_attr.attr, ++ &sensor_dev_attr_pwm1_auto_point9_temp.dev_attr.attr, ++ &sensor_dev_attr_pwm1_auto_point10_temp.dev_attr.attr, ++ &sensor_dev_attr_pwm1_auto_point1_temp_hyst.dev_attr.attr, ++ &sensor_dev_attr_pwm1_auto_point2_temp_hyst.dev_attr.attr, ++ &sensor_dev_attr_pwm1_auto_point3_temp_hyst.dev_attr.attr, ++ &sensor_dev_attr_pwm1_auto_point4_temp_hyst.dev_attr.attr, ++ &sensor_dev_attr_pwm1_auto_point5_temp_hyst.dev_attr.attr, ++ &sensor_dev_attr_pwm1_auto_point6_temp_hyst.dev_attr.attr, ++ &sensor_dev_attr_pwm1_auto_point7_temp_hyst.dev_attr.attr, ++ &sensor_dev_attr_pwm1_auto_point8_temp_hyst.dev_attr.attr, ++ &sensor_dev_attr_pwm1_auto_point9_temp_hyst.dev_attr.attr, ++ &sensor_dev_attr_pwm1_auto_point10_temp_hyst.dev_attr.attr, ++ &sensor_dev_attr_pwm2_auto_point1_temp.dev_attr.attr, ++ &sensor_dev_attr_pwm2_auto_point2_temp.dev_attr.attr, ++ &sensor_dev_attr_pwm2_auto_point3_temp.dev_attr.attr, ++ &sensor_dev_attr_pwm2_auto_point4_temp.dev_attr.attr, ++ &sensor_dev_attr_pwm2_auto_point5_temp.dev_attr.attr, ++ &sensor_dev_attr_pwm2_auto_point6_temp.dev_attr.attr, ++ &sensor_dev_attr_pwm2_auto_point7_temp.dev_attr.attr, ++ &sensor_dev_attr_pwm2_auto_point8_temp.dev_attr.attr, ++ &sensor_dev_attr_pwm2_auto_point9_temp.dev_attr.attr, ++ &sensor_dev_attr_pwm2_auto_point10_temp.dev_attr.attr, ++ &sensor_dev_attr_pwm2_auto_point1_temp_hyst.dev_attr.attr, ++ &sensor_dev_attr_pwm2_auto_point2_temp_hyst.dev_attr.attr, ++ &sensor_dev_attr_pwm2_auto_point3_temp_hyst.dev_attr.attr, ++ &sensor_dev_attr_pwm2_auto_point4_temp_hyst.dev_attr.attr, ++ &sensor_dev_attr_pwm2_auto_point5_temp_hyst.dev_attr.attr, ++ &sensor_dev_attr_pwm2_auto_point6_temp_hyst.dev_attr.attr, ++ &sensor_dev_attr_pwm2_auto_point7_temp_hyst.dev_attr.attr, ++ &sensor_dev_attr_pwm2_auto_point8_temp_hyst.dev_attr.attr, ++ &sensor_dev_attr_pwm2_auto_point9_temp_hyst.dev_attr.attr, ++ &sensor_dev_attr_pwm2_auto_point10_temp_hyst.dev_attr.attr, ++ &sensor_dev_attr_pwm3_auto_point1_temp.dev_attr.attr, ++ &sensor_dev_attr_pwm3_auto_point2_temp.dev_attr.attr, ++ &sensor_dev_attr_pwm3_auto_point3_temp.dev_attr.attr, ++ &sensor_dev_attr_pwm3_auto_point4_temp.dev_attr.attr, ++ &sensor_dev_attr_pwm3_auto_point5_temp.dev_attr.attr, ++ &sensor_dev_attr_pwm3_auto_point6_temp.dev_attr.attr, ++ &sensor_dev_attr_pwm3_auto_point7_temp.dev_attr.attr, ++ &sensor_dev_attr_pwm3_auto_point8_temp.dev_attr.attr, ++ &sensor_dev_attr_pwm3_auto_point9_temp.dev_attr.attr, ++ &sensor_dev_attr_pwm3_auto_point10_temp.dev_attr.attr, ++ &sensor_dev_attr_pwm3_auto_point1_temp_hyst.dev_attr.attr, ++ &sensor_dev_attr_pwm3_auto_point2_temp_hyst.dev_attr.attr, ++ &sensor_dev_attr_pwm3_auto_point3_temp_hyst.dev_attr.attr, ++ &sensor_dev_attr_pwm3_auto_point4_temp_hyst.dev_attr.attr, ++ &sensor_dev_attr_pwm3_auto_point5_temp_hyst.dev_attr.attr, ++ &sensor_dev_attr_pwm3_auto_point6_temp_hyst.dev_attr.attr, ++ &sensor_dev_attr_pwm3_auto_point7_temp_hyst.dev_attr.attr, ++ &sensor_dev_attr_pwm3_auto_point8_temp_hyst.dev_attr.attr, ++ &sensor_dev_attr_pwm3_auto_point9_temp_hyst.dev_attr.attr, ++ &sensor_dev_attr_pwm3_auto_point10_temp_hyst.dev_attr.attr, ++ &sensor_dev_attr_pwm1_auto_point1_accel.dev_attr.attr, ++ &sensor_dev_attr_pwm1_auto_point2_accel.dev_attr.attr, ++ &sensor_dev_attr_pwm1_auto_point3_accel.dev_attr.attr, ++ &sensor_dev_attr_pwm1_auto_point4_accel.dev_attr.attr, ++ &sensor_dev_attr_pwm1_auto_point5_accel.dev_attr.attr, ++ &sensor_dev_attr_pwm1_auto_point6_accel.dev_attr.attr, ++ &sensor_dev_attr_pwm1_auto_point7_accel.dev_attr.attr, ++ &sensor_dev_attr_pwm1_auto_point8_accel.dev_attr.attr, ++ &sensor_dev_attr_pwm1_auto_point9_accel.dev_attr.attr, ++ &sensor_dev_attr_pwm1_auto_point10_accel.dev_attr.attr, ++ &sensor_dev_attr_pwm1_auto_point1_decel.dev_attr.attr, ++ &sensor_dev_attr_pwm1_auto_point2_decel.dev_attr.attr, ++ &sensor_dev_attr_pwm1_auto_point3_decel.dev_attr.attr, ++ &sensor_dev_attr_pwm1_auto_point4_decel.dev_attr.attr, ++ &sensor_dev_attr_pwm1_auto_point5_decel.dev_attr.attr, ++ &sensor_dev_attr_pwm1_auto_point6_decel.dev_attr.attr, ++ &sensor_dev_attr_pwm1_auto_point7_decel.dev_attr.attr, ++ &sensor_dev_attr_pwm1_auto_point8_decel.dev_attr.attr, ++ &sensor_dev_attr_pwm1_auto_point9_decel.dev_attr.attr, ++ &sensor_dev_attr_pwm1_auto_point10_decel.dev_attr.attr, ++ // ++ &sensor_dev_attr_auto_points_size.dev_attr.attr, ++ &sensor_dev_attr_minifancurve.dev_attr.attr, ++ &sensor_dev_attr_pwm1_mode.dev_attr.attr, NULL ++}; ++ ++static umode_t legion_hwmon_is_visible(struct kobject *kobj, ++ struct attribute *attr, int idx) ++{ ++ bool supported = true; ++ struct device *dev = kobj_to_dev(kobj); ++ struct legion_private *priv = dev_get_drvdata(dev); ++ ++ if (attr == &sensor_dev_attr_minifancurve.dev_attr.attr) ++ supported = priv->conf->has_minifancurve; ++ ++ supported = supported && (priv->conf->access_method_fancurve != ++ ACCESS_METHOD_NO_ACCESS); ++ ++ return supported ? attr->mode : 0; ++} ++ ++static const struct attribute_group legion_hwmon_sensor_group = { ++ .attrs = sensor_hwmon_attributes, ++ .is_visible = NULL ++}; ++ ++static const struct attribute_group legion_hwmon_fancurve_group = { ++ .attrs = fancurve_hwmon_attributes, ++ .is_visible = legion_hwmon_is_visible, ++}; ++ ++static const struct attribute_group *legion_hwmon_groups[] = { ++ &legion_hwmon_sensor_group, &legion_hwmon_fancurve_group, NULL ++}; ++ ++static ssize_t legion_hwmon_init(struct legion_private *priv) ++{ ++ //TODO: use hwmon_device_register_with_groups or ++ // hwmon_device_register_with_info (latter means all hwmon functions have to be ++ // changed) ++ // some laptop driver do it in one way, some in the other ++ // TODO: Use devm_hwmon_device_register_with_groups ? ++ // some laptop drivers use this, some ++ struct device *hwmon_dev = hwmon_device_register_with_groups( ++ &priv->platform_device->dev, "legion_hwmon", priv, ++ legion_hwmon_groups); ++ if (IS_ERR_OR_NULL(hwmon_dev)) { ++ pr_err("hwmon_device_register failed!\n"); ++ return PTR_ERR(hwmon_dev); ++ } ++ dev_set_drvdata(hwmon_dev, priv); ++ priv->hwmon_dev = hwmon_dev; ++ return 0; ++} ++ ++static void legion_hwmon_exit(struct legion_private *priv) ++{ ++ pr_info("Unloading legion hwon\n"); ++ if (priv->hwmon_dev) { ++ hwmon_device_unregister(priv->hwmon_dev); ++ priv->hwmon_dev = NULL; ++ } ++ pr_info("Unloading legion hwon done\n"); ++} ++ ++/* ACPI*/ ++ ++static int acpi_init(struct legion_private *priv, struct acpi_device *adev) ++{ ++ int err; ++ unsigned long cfg; ++ bool skip_acpi_sta_check; ++ struct device *dev = &priv->platform_device->dev; ++ ++ priv->adev = adev; ++ if (!priv->adev) { ++ dev_info(dev, "Could not get ACPI handle\n"); ++ goto err_acpi_init; ++ } ++ ++ skip_acpi_sta_check = force || (!priv->conf->acpi_check_dev); ++ if (!skip_acpi_sta_check) { ++ err = eval_int(priv->adev->handle, "_STA", &cfg); ++ if (err) { ++ dev_info(dev, "Could not evaluate ACPI _STA\n"); ++ goto err_acpi_init; ++ } ++ ++ err = eval_int(priv->adev->handle, "VPC0._CFG", &cfg); ++ if (err) { ++ dev_info(dev, "Could not evaluate ACPI _CFG\n"); ++ goto err_acpi_init; ++ } ++ dev_info(dev, "ACPI CFG: %lu\n", cfg); ++ } else { ++ dev_info(dev, "Skipping ACPI _STA check"); ++ } ++ ++ return 0; ++ ++err_acpi_init: ++ return err; ++} ++ ++/* ============================= */ ++/* White Keyboard Backlight */ ++/* ============================ */ ++// In style of ideapad-driver and with code modified from ideapad-driver. ++ ++static enum led_brightness ++legion_kbd_bl_led_cdev_brightness_get(struct led_classdev *led_cdev) ++{ ++ struct legion_private *priv = ++ container_of(led_cdev, struct legion_private, kbd_bl.led); ++ ++ return legion_kbd_bl_brightness_get(priv); ++} ++ ++static int legion_kbd_bl_led_cdev_brightness_set(struct led_classdev *led_cdev, ++ enum led_brightness brightness) ++{ ++ struct legion_private *priv = ++ container_of(led_cdev, struct legion_private, kbd_bl.led); ++ ++ return legion_kbd_bl_brightness_set(priv, brightness); ++} ++ ++static int legion_kbd_bl_init(struct legion_private *priv) ++{ ++ int brightness, err; ++ ++ if (WARN_ON(priv->kbd_bl.initialized)) { ++ pr_info("Keyboard backlight already initialized\n"); ++ return -EEXIST; ++ } ++ ++ if (priv->conf->access_method_keyboard == ACCESS_METHOD_NO_ACCESS) { ++ pr_info("Keyboard backlight handling disabled by this driver\n"); ++ return -ENODEV; ++ } ++ ++ brightness = legion_kbd_bl_brightness_get(priv); ++ if (brightness < 0) { ++ pr_info("Error reading keyboard brighntess\n"); ++ return brightness; ++ } ++ ++ priv->kbd_bl.last_brightness = brightness; ++ ++ // will be renamed to "platform::kbd_backlight_1" if it exists already ++ priv->kbd_bl.led.name = "platform::" LED_FUNCTION_KBD_BACKLIGHT; ++ priv->kbd_bl.led.max_brightness = 2; ++ priv->kbd_bl.led.brightness_get = legion_kbd_bl_led_cdev_brightness_get; ++ priv->kbd_bl.led.brightness_set_blocking = ++ legion_kbd_bl_led_cdev_brightness_set; ++ priv->kbd_bl.led.flags = LED_BRIGHT_HW_CHANGED; ++ ++ err = led_classdev_register(&priv->platform_device->dev, ++ &priv->kbd_bl.led); ++ if (err) ++ return err; ++ ++ priv->kbd_bl.initialized = true; ++ ++ return 0; ++} ++ ++/** ++ * Deinit keyboard backlight. ++ * ++ * Can also be called if init was not successful. ++ * ++ */ ++static void legion_kbd_bl_exit(struct legion_private *priv) ++{ ++ if (!priv->kbd_bl.initialized) ++ return; ++ ++ priv->kbd_bl.initialized = false; ++ ++ led_classdev_unregister(&priv->kbd_bl.led); ++} ++ ++/* ============================= */ ++/* Additional light driver */ ++/* ============================ */ ++ ++static enum led_brightness ++legion_wmi_cdev_brightness_get(struct led_classdev *led_cdev) ++{ ++ struct legion_private *priv = ++ container_of(led_cdev, struct legion_private, kbd_bl.led); ++ struct light *light_ins = container_of(led_cdev, struct light, led); ++ ++ return legion_wmi_light_get(priv, light_ins->light_id, ++ light_ins->lower_limit, ++ light_ins->upper_limit); ++} ++ ++static int legion_wmi_cdev_brightness_set(struct led_classdev *led_cdev, ++ enum led_brightness brightness) ++{ ++ struct legion_private *priv = ++ container_of(led_cdev, struct legion_private, kbd_bl.led); ++ struct light *light_ins = container_of(led_cdev, struct light, led); ++ ++ return legion_wmi_light_set(priv, light_ins->light_id, ++ light_ins->lower_limit, ++ light_ins->upper_limit, brightness); ++} ++ ++static int legion_light_init(struct legion_private *priv, ++ struct light *light_ins, u8 light_id, ++ u8 lower_limit, u8 upper_limit, const char *name) ++{ ++ int brightness, err; ++ ++ if (WARN_ON(light_ins->initialized)) { ++ pr_info("Light already initialized for light: %u\n", ++ light_ins->light_id); ++ return -EEXIST; ++ } ++ ++ light_ins->light_id = light_id; ++ light_ins->lower_limit = lower_limit; ++ light_ins->upper_limit = upper_limit; ++ ++ brightness = legion_wmi_light_get(priv, light_ins->light_id, ++ light_ins->lower_limit, ++ light_ins->upper_limit); ++ if (brightness < 0) { ++ pr_info("Error reading brighntess for light: %u\n", ++ light_ins->light_id); ++ return brightness; ++ } ++ ++ light_ins->led.name = name; ++ light_ins->led.max_brightness = ++ light_ins->upper_limit - light_ins->lower_limit; ++ light_ins->led.brightness_get = legion_wmi_cdev_brightness_get; ++ light_ins->led.brightness_set_blocking = legion_wmi_cdev_brightness_set; ++ light_ins->led.flags = LED_BRIGHT_HW_CHANGED; ++ ++ err = led_classdev_register(&priv->platform_device->dev, ++ &light_ins->led); ++ if (err) ++ return err; ++ ++ light_ins->initialized = true; ++ ++ return 0; ++} ++ ++/** ++ * Deinit light. ++ * ++ * Can also be called if init was not successful. ++ * ++ */ ++static void legion_light_exit(struct legion_private *priv, ++ struct light *light_ins) ++{ ++ if (!light_ins->initialized) ++ return; ++ ++ light_ins->initialized = false; ++ ++ led_classdev_unregister(&light_ins->led); ++} ++ ++/* ============================= */ ++/* Platform driver */ ++/* ============================ */ ++ ++static int legion_add(struct platform_device *pdev) ++{ ++ struct legion_private *priv; ++ const struct dmi_system_id *dmi_sys; ++ int err; ++ u16 ec_read_id; ++ bool skip_ec_id_check; ++ bool is_ec_id_valid; ++ bool is_denied = true; ++ bool is_allowed = false; ++ bool do_load_by_list = false; ++ bool do_load = false; ++ //struct legion_private *priv = dev_get_drvdata(&pdev->dev); ++ dev_info(&pdev->dev, "legion_laptop platform driver probing\n"); ++ ++ dev_info( ++ &pdev->dev, ++ "Read identifying information: DMI_SYS_VENDOR: %s; DMI_PRODUCT_NAME: %s; DMI_BIOS_VERSION:%s\n", ++ dmi_get_system_info(DMI_SYS_VENDOR), ++ dmi_get_system_info(DMI_PRODUCT_NAME), ++ dmi_get_system_info(DMI_BIOS_VERSION)); ++ ++ // TODO: allocate? ++ priv = &_priv; ++ priv->platform_device = pdev; ++ err = legion_shared_init(priv); ++ if (err) { ++ dev_info(&pdev->dev, "legion_laptop is forced to load.\n"); ++ goto err_legion_shared_init; ++ } ++ dev_set_drvdata(&pdev->dev, priv); ++ ++ // TODO: remove ++ pr_info("Read identifying information: DMI_SYS_VENDOR: %s; DMI_PRODUCT_NAME: %s; DMI_BIOS_VERSION:%s\n", ++ dmi_get_system_info(DMI_SYS_VENDOR), ++ dmi_get_system_info(DMI_PRODUCT_NAME), ++ dmi_get_system_info(DMI_BIOS_VERSION)); ++ ++ dmi_sys = dmi_first_match(optimistic_allowlist); ++ is_allowed = dmi_sys != NULL; ++ is_denied = dmi_check_system(denylist); ++ do_load_by_list = is_allowed && !is_denied; ++ do_load = do_load_by_list || force; ++ ++ dev_info( ++ &pdev->dev, ++ "is_denied: %d; is_allowed: %d; do_load_by_list: %d; do_load: %d\n", ++ is_denied, is_allowed, do_load_by_list, do_load); ++ ++ if (!(do_load)) { ++ dev_info( ++ &pdev->dev, ++ "Module not useable for this laptop because it is not in allowlist. Notify maintainer if you want to add your device or force load with param force.\n"); ++ err = -ENOMEM; ++ goto err_model_mismtach; ++ } ++ ++ if (force) ++ dev_info(&pdev->dev, "legion_laptop is forced to load.\n"); ++ ++ if (!do_load_by_list && do_load) { ++ dev_info( ++ &pdev->dev, ++ "legion_laptop is forced to load and would otherwise be not loaded\n"); ++ } ++ ++ // if forced and no module found, use config for first model ++ if (dmi_sys == NULL) ++ dmi_sys = &optimistic_allowlist[0]; ++ dev_info(&pdev->dev, "Using configuration for system: %s\n", ++ dmi_sys->ident); ++ ++ priv->conf = dmi_sys->driver_data; ++ ++ err = acpi_init(priv, ACPI_COMPANION(&pdev->dev)); ++ if (err) { ++ dev_info(&pdev->dev, "Could not init ACPI access: %d\n", err); ++ goto err_acpi_init; ++ } ++ ++ // TODO: remove; only used for reverse engineering ++ pr_info("Creating RAM access to embedded controller\n"); ++ err = ecram_memoryio_init(&priv->ec_memoryio, ++ priv->conf->ramio_physical_start, 0, ++ priv->conf->ramio_size); ++ if (err) { ++ dev_info( ++ &pdev->dev, ++ "Could not init RAM access to embedded controller: %d\n", ++ err); ++ goto err_ecram_memoryio_init; ++ } ++ ++ err = ecram_init(&priv->ecram, priv->conf->memoryio_physical_ec_start, ++ priv->conf->memoryio_size); ++ if (err) { ++ dev_info(&pdev->dev, ++ "Could not init access to embedded controller: %d\n", ++ err); ++ goto err_ecram_init; ++ } ++ ++ ec_read_id = read_ec_id(&priv->ecram, priv->conf); ++ dev_info(&pdev->dev, "Read embedded controller ID 0x%x\n", ec_read_id); ++ skip_ec_id_check = force || (!priv->conf->check_embedded_controller_id); ++ is_ec_id_valid = skip_ec_id_check || ++ (ec_read_id == priv->conf->embedded_controller_id); ++ if (!is_ec_id_valid) { ++ err = -ENOMEM; ++ dev_info(&pdev->dev, "Expected EC chip id 0x%x but read 0x%x\n", ++ priv->conf->embedded_controller_id, ec_read_id); ++ goto err_ecram_id; ++ } ++ if (skip_ec_id_check) { ++ dev_info(&pdev->dev, ++ "Skipped checking embedded controller id\n"); ++ } ++ ++ dev_info(&pdev->dev, "Creating debugfs inteface\n"); ++ legion_debugfs_init(priv); ++ ++ pr_info("Creating sysfs inteface\n"); ++ err = legion_sysfs_init(priv); ++ if (err) { ++ dev_info(&pdev->dev, "Creating sysfs interface failed: %d\n", ++ err); ++ goto err_sysfs_init; ++ } ++ ++ pr_info("Creating hwmon interface"); ++ err = legion_hwmon_init(priv); ++ if (err) { ++ dev_info(&pdev->dev, "Creating hwmon interface failed: %d\n", ++ err); ++ goto err_hwmon_init; ++ } ++ ++ pr_info("Creating platform profile support\n"); ++ err = legion_platform_profile_init(priv); ++ if (err) { ++ dev_info(&pdev->dev, "Creating platform profile failed: %d\n", ++ err); ++ goto err_platform_profile; ++ } ++ ++ pr_info("Init WMI driver support\n"); ++ err = legion_wmi_init(); ++ if (err) { ++ dev_info(&pdev->dev, "Init WMI driver failed: %d\n", err); ++ goto err_wmi; ++ } ++ ++ pr_info("Init keyboard backlight LED driver\n"); ++ err = legion_kbd_bl_init(priv); ++ if (err) { ++ dev_info( ++ &pdev->dev, ++ "Init keyboard backlight LED driver failed. Skipping ...\n"); ++ } ++ ++ pr_info("Init Y-Logo LED driver\n"); ++ err = legion_light_init(priv, &priv->ylogo_light, LIGHT_ID_YLOGO, 0, 1, ++ "platform::ylogo"); ++ if (err) { ++ dev_info(&pdev->dev, ++ "Init Y-Logo LED driver failed. Skipping ...\n"); ++ } ++ ++ pr_info("Init IO-Port LED driver\n"); ++ err = legion_light_init(priv, &priv->iport_light, LIGHT_ID_IOPORT, 1, 2, ++ "platform::ioport"); ++ if (err) { ++ dev_info(&pdev->dev, ++ "Init IO-Port LED driver failed. Skipping ...\n"); ++ } ++ ++ dev_info(&pdev->dev, "legion_laptop loaded for this device\n"); ++ return 0; ++ ++ // TODO: remove eventually ++ legion_light_exit(priv, &priv->iport_light); ++ legion_light_exit(priv, &priv->ylogo_light); ++ legion_kbd_bl_exit(priv); ++ legion_wmi_exit(); ++err_wmi: ++ legion_platform_profile_exit(priv); ++err_platform_profile: ++ legion_hwmon_exit(priv); ++err_hwmon_init: ++ legion_sysfs_exit(priv); ++err_sysfs_init: ++ legion_debugfs_exit(priv); ++err_ecram_id: ++ ecram_exit(&priv->ecram); ++err_ecram_init: ++ ecram_memoryio_exit(&priv->ec_memoryio); ++err_ecram_memoryio_init: ++err_acpi_init: ++ legion_shared_exit(priv); ++err_legion_shared_init: ++err_model_mismtach: ++ dev_info(&pdev->dev, "legion_laptop not loaded for this device\n"); ++ return err; ++} ++ ++static int legion_remove(struct platform_device *pdev) ++{ ++ struct legion_private *priv = dev_get_drvdata(&pdev->dev); ++ ++ mutex_lock(&legion_shared_mutex); ++ priv->loaded = false; ++ mutex_unlock(&legion_shared_mutex); ++ ++ legion_light_exit(priv, &priv->iport_light); ++ legion_light_exit(priv, &priv->ylogo_light); ++ legion_kbd_bl_exit(priv); ++ // first unregister wmi, so toggling powermode does not ++ // generate events anymore that even might be delayed ++ legion_wmi_exit(); ++ legion_platform_profile_exit(priv); ++ ++ // toggle power mode to load default setting from embedded controller ++ // again ++ toggle_powermode(priv); ++ ++ legion_hwmon_exit(priv); ++ legion_sysfs_exit(priv); ++ legion_debugfs_exit(priv); ++ ecram_exit(&priv->ecram); ++ ecram_memoryio_exit(&priv->ec_memoryio); ++ legion_shared_exit(priv); ++ ++ pr_info("Legion platform unloaded\n"); ++ return 0; ++} ++ ++static int legion_resume(struct platform_device *pdev) ++{ ++ //struct legion_private *priv = dev_get_drvdata(&pdev->dev); ++ dev_info(&pdev->dev, "Resumed in legion-laptop\n"); ++ ++ return 0; ++} ++ ++#ifdef CONFIG_PM_SLEEP ++static int legion_pm_resume(struct device *dev) ++{ ++ //struct legion_private *priv = dev_get_drvdata(dev); ++ dev_info(dev, "Resumed PM in legion-laptop\n"); ++ ++ return 0; ++} ++#endif ++static SIMPLE_DEV_PM_OPS(legion_pm, NULL, legion_pm_resume); ++ ++// same as ideapad ++static const struct acpi_device_id legion_device_ids[] = { ++ // todo: change to "VPC2004", and also ACPI paths ++ { "PNP0C09", 0 }, ++ { "", 0 }, ++}; ++MODULE_DEVICE_TABLE(acpi, legion_device_ids); ++ ++static struct platform_driver legion_driver = { ++ .probe = legion_add, ++ .remove = legion_remove, ++ .resume = legion_resume, ++ .driver = { ++ .name = "legion", ++ .pm = &legion_pm, ++ .acpi_match_table = ACPI_PTR(legion_device_ids), ++ }, ++}; ++ ++static int __init legion_init(void) ++{ ++ int err; ++ ++ pr_info("legion_laptop starts loading\n"); ++ err = platform_driver_register(&legion_driver); ++ if (err) { ++ pr_info("legion_laptop: platform_driver_register failed\n"); ++ return err; ++ } ++ ++ return 0; ++} ++ ++module_init(legion_init); ++ ++static void __exit legion_exit(void) ++{ ++ platform_driver_unregister(&legion_driver); ++ pr_info("legion_laptop exit\n"); ++} ++ ++module_exit(legion_exit); +-- +2.41.0 + diff --git a/patches/nobara-rebased/linux-surface.patch b/patches/nobara/linux-surface.patch similarity index 56% rename from patches/nobara-rebased/linux-surface.patch rename to patches/nobara/linux-surface.patch index bd9574a..3378feb 100644 --- a/patches/nobara-rebased/linux-surface.patch +++ b/patches/nobara/linux-surface.patch @@ -1,169 +1,466 @@ -diff '--color=auto' -uraN cachyos/MAINTAINERS cachyos-surface/MAINTAINERS ---- cachyos/MAINTAINERS 2023-10-30 05:31:08.000000000 +0300 -+++ cachyos-surface/MAINTAINERS 2023-11-04 18:32:41.050988043 +0300 -@@ -6301,6 +6301,13 @@ - T: git git://linuxtv.org/media_tree.git - F: drivers/media/i2c/dw9719.c +From da55b6ffe4a98a4af6ced4074317ba9d026f84dd Mon Sep 17 00:00:00 2001 +From: Tsuchiya Yuto +Date: Sun, 18 Oct 2020 16:42:44 +0900 +Subject: [PATCH] (surface3-oemb) add DMI matches for Surface 3 with broken DMI + table + +On some Surface 3, the DMI table gets corrupted for unknown reasons +and breaks existing DMI matching used for device-specific quirks. + +This commit adds the (broken) DMI data into dmi_system_id tables used +for quirks so that each driver can enable quirks even on the affected +systems. + +On affected systems, DMI data will look like this: + $ grep . /sys/devices/virtual/dmi/id/{bios_vendor,board_name,board_vendor,\ + chassis_vendor,product_name,sys_vendor} + /sys/devices/virtual/dmi/id/bios_vendor:American Megatrends Inc. + /sys/devices/virtual/dmi/id/board_name:OEMB + /sys/devices/virtual/dmi/id/board_vendor:OEMB + /sys/devices/virtual/dmi/id/chassis_vendor:OEMB + /sys/devices/virtual/dmi/id/product_name:OEMB + /sys/devices/virtual/dmi/id/sys_vendor:OEMB + +Expected: + $ grep . /sys/devices/virtual/dmi/id/{bios_vendor,board_name,board_vendor,\ + chassis_vendor,product_name,sys_vendor} + /sys/devices/virtual/dmi/id/bios_vendor:American Megatrends Inc. + /sys/devices/virtual/dmi/id/board_name:Surface 3 + /sys/devices/virtual/dmi/id/board_vendor:Microsoft Corporation + /sys/devices/virtual/dmi/id/chassis_vendor:Microsoft Corporation + /sys/devices/virtual/dmi/id/product_name:Surface 3 + /sys/devices/virtual/dmi/id/sys_vendor:Microsoft Corporation + +Signed-off-by: Tsuchiya Yuto +Patchset: surface3-oemb +--- + drivers/platform/surface/surface3-wmi.c | 7 +++++++ + sound/soc/codecs/rt5645.c | 9 +++++++++ + sound/soc/intel/common/soc-acpi-intel-cht-match.c | 8 ++++++++ + 3 files changed, 24 insertions(+) + +diff --git a/drivers/platform/surface/surface3-wmi.c b/drivers/platform/surface/surface3-wmi.c +index ca4602bcc7dea..490b9731068ae 100644 +--- a/drivers/platform/surface/surface3-wmi.c ++++ b/drivers/platform/surface/surface3-wmi.c +@@ -37,6 +37,13 @@ static const struct dmi_system_id surface3_dmi_table[] = { + DMI_MATCH(DMI_PRODUCT_NAME, "Surface 3"), + }, + }, ++ { ++ .matches = { ++ DMI_MATCH(DMI_BIOS_VENDOR, "American Megatrends Inc."), ++ DMI_MATCH(DMI_SYS_VENDOR, "OEMB"), ++ DMI_MATCH(DMI_PRODUCT_NAME, "OEMB"), ++ }, ++ }, + #endif + { } + }; +diff --git a/sound/soc/codecs/rt5645.c b/sound/soc/codecs/rt5645.c +index 7938b52d741d8..2d5f83b0cdb0b 100644 +--- a/sound/soc/codecs/rt5645.c ++++ b/sound/soc/codecs/rt5645.c +@@ -3746,6 +3746,15 @@ static const struct dmi_system_id dmi_platform_data[] = { + }, + .driver_data = (void *)&intel_braswell_platform_data, + }, ++ { ++ .ident = "Microsoft Surface 3", ++ .matches = { ++ DMI_MATCH(DMI_BIOS_VENDOR, "American Megatrends Inc."), ++ DMI_MATCH(DMI_SYS_VENDOR, "OEMB"), ++ DMI_MATCH(DMI_PRODUCT_NAME, "OEMB"), ++ }, ++ .driver_data = (void *)&intel_braswell_platform_data, ++ }, + { + /* + * Match for the GPDwin which unfortunately uses somewhat +diff --git a/sound/soc/intel/common/soc-acpi-intel-cht-match.c b/sound/soc/intel/common/soc-acpi-intel-cht-match.c +index cdcbf04b8832f..958305779b125 100644 +--- a/sound/soc/intel/common/soc-acpi-intel-cht-match.c ++++ b/sound/soc/intel/common/soc-acpi-intel-cht-match.c +@@ -27,6 +27,14 @@ static const struct dmi_system_id cht_table[] = { + DMI_MATCH(DMI_PRODUCT_NAME, "Surface 3"), + }, + }, ++ { ++ .callback = cht_surface_quirk_cb, ++ .matches = { ++ DMI_MATCH(DMI_BIOS_VENDOR, "American Megatrends Inc."), ++ DMI_MATCH(DMI_SYS_VENDOR, "OEMB"), ++ DMI_MATCH(DMI_PRODUCT_NAME, "OEMB"), ++ }, ++ }, + { } + }; -+DONGWOON DW9719 LENS VOICE COIL DRIVER -+M: Daniel Scally -+L: linux-media@vger.kernel.org -+S: Maintained -+T: git git://linuxtv.org/media_tree.git -+F: drivers/media/i2c/dw9719.c +-- +2.42.0 + +From 35b3c5195c9fc191de6b5a6e4361762aa37edad2 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= +Date: Tue, 3 Nov 2020 13:28:04 +0100 +Subject: [PATCH] mwifiex: Add quirk resetting the PCI bridge on MS Surface + devices + +The most recent firmware of the 88W8897 card reports a hardcoded LTR +value to the system during initialization, probably as an (unsuccessful) +attempt of the developers to fix firmware crashes. This LTR value +prevents most of the Microsoft Surface devices from entering deep +powersaving states (either platform C-State 10 or S0ix state), because +the exit latency of that state would be higher than what the card can +tolerate. + +Turns out the card works just the same (including the firmware crashes) +no matter if that hardcoded LTR value is reported or not, so it's kind +of useless and only prevents us from saving power. + +To get rid of those hardcoded LTR reports, it's possible to reset the +PCI bridge device after initializing the cards firmware. I'm not exactly +sure why that works, maybe the power management subsystem of the PCH +resets its stored LTR values when doing a function level reset of the +bridge device. Doing the reset once after starting the wifi firmware +works very well, probably because the firmware only reports that LTR +value a single time during firmware startup. + +Patchset: mwifiex +--- + drivers/net/wireless/marvell/mwifiex/pcie.c | 12 +++++++++ + .../wireless/marvell/mwifiex/pcie_quirks.c | 26 +++++++++++++------ + .../wireless/marvell/mwifiex/pcie_quirks.h | 1 + + 3 files changed, 31 insertions(+), 8 deletions(-) + +diff --git a/drivers/net/wireless/marvell/mwifiex/pcie.c b/drivers/net/wireless/marvell/mwifiex/pcie.c +index 6697132ecc977..f06b4ebc5bd8e 100644 +--- a/drivers/net/wireless/marvell/mwifiex/pcie.c ++++ b/drivers/net/wireless/marvell/mwifiex/pcie.c +@@ -1771,9 +1771,21 @@ mwifiex_pcie_send_boot_cmd(struct mwifiex_adapter *adapter, struct sk_buff *skb) + static int mwifiex_pcie_init_fw_port(struct mwifiex_adapter *adapter) + { + struct pcie_service_card *card = adapter->card; ++ struct pci_dev *pdev = card->dev; ++ struct pci_dev *parent_pdev = pci_upstream_bridge(pdev); + const struct mwifiex_pcie_card_reg *reg = card->pcie.reg; + int tx_wrap = card->txbd_wrptr & reg->tx_wrap_mask; + ++ /* Trigger a function level reset of the PCI bridge device, this makes ++ * the firmware of PCIe 88W8897 cards stop reporting a fixed LTR value ++ * that prevents the system from entering package C10 and S0ix powersaving ++ * states. ++ * We need to do it here because it must happen after firmware ++ * initialization and this function is called after that is done. ++ */ ++ if (card->quirks & QUIRK_DO_FLR_ON_BRIDGE) ++ pci_reset_function(parent_pdev); + - DONGWOON DW9768 LENS VOICE COIL DRIVER - L: linux-media@vger.kernel.org - S: Orphan -diff '--color=auto' -uraN cachyos/arch/x86/kernel/acpi/boot.c cachyos-surface/arch/x86/kernel/acpi/boot.c ---- cachyos/arch/x86/kernel/acpi/boot.c 2023-10-30 05:31:08.000000000 +0300 -+++ cachyos-surface/arch/x86/kernel/acpi/boot.c 2023-11-04 18:32:41.057654806 +0300 -@@ -22,6 +22,7 @@ - #include - #include - #include -+#include - - #include - #include -@@ -1255,6 +1256,24 @@ - } + /* Write the RX ring read pointer in to reg->rx_rdptr */ + if (mwifiex_write_reg(adapter, reg->rx_rdptr, card->rxbd_rdptr | + tx_wrap)) { +diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c +index dd6d21f1dbfd7..f46b06f8d6435 100644 +--- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c ++++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c +@@ -13,7 +13,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 4"), + }, +- .driver_data = (void *)QUIRK_FW_RST_D3COLD, ++ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | ++ QUIRK_DO_FLR_ON_BRIDGE), + }, + { + .ident = "Surface Pro 5", +@@ -22,7 +23,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1796"), + }, +- .driver_data = (void *)QUIRK_FW_RST_D3COLD, ++ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | ++ QUIRK_DO_FLR_ON_BRIDGE), + }, + { + .ident = "Surface Pro 5 (LTE)", +@@ -31,7 +33,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1807"), + }, +- .driver_data = (void *)QUIRK_FW_RST_D3COLD, ++ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | ++ QUIRK_DO_FLR_ON_BRIDGE), + }, + { + .ident = "Surface Pro 6", +@@ -39,7 +42,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 6"), + }, +- .driver_data = (void *)QUIRK_FW_RST_D3COLD, ++ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | ++ QUIRK_DO_FLR_ON_BRIDGE), + }, + { + .ident = "Surface Book 1", +@@ -47,7 +51,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book"), + }, +- .driver_data = (void *)QUIRK_FW_RST_D3COLD, ++ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | ++ QUIRK_DO_FLR_ON_BRIDGE), + }, + { + .ident = "Surface Book 2", +@@ -55,7 +60,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book 2"), + }, +- .driver_data = (void *)QUIRK_FW_RST_D3COLD, ++ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | ++ QUIRK_DO_FLR_ON_BRIDGE), + }, + { + .ident = "Surface Laptop 1", +@@ -63,7 +69,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop"), + }, +- .driver_data = (void *)QUIRK_FW_RST_D3COLD, ++ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | ++ QUIRK_DO_FLR_ON_BRIDGE), + }, + { + .ident = "Surface Laptop 2", +@@ -71,7 +78,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop 2"), + }, +- .driver_data = (void *)QUIRK_FW_RST_D3COLD, ++ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | ++ QUIRK_DO_FLR_ON_BRIDGE), + }, + {} + }; +@@ -89,6 +97,8 @@ void mwifiex_initialize_quirks(struct pcie_service_card *card) + dev_info(&pdev->dev, "no quirks enabled\n"); + if (card->quirks & QUIRK_FW_RST_D3COLD) + dev_info(&pdev->dev, "quirk reset_d3cold enabled\n"); ++ if (card->quirks & QUIRK_DO_FLR_ON_BRIDGE) ++ dev_info(&pdev->dev, "quirk do_flr_on_bridge enabled\n"); } -+static const struct dmi_system_id surface_quirk[] __initconst = { -+ { -+ .ident = "Microsoft Surface Laptop 4 (AMD 15\")", -+ .matches = { -+ DMI_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_MATCH(DMI_PRODUCT_SKU, "Surface_Laptop_4_1952:1953") -+ }, -+ }, -+ { -+ .ident = "Microsoft Surface Laptop 4 (AMD 13\")", -+ .matches = { -+ DMI_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_MATCH(DMI_PRODUCT_SKU, "Surface_Laptop_4_1958:1959") -+ }, -+ }, -+ {} -+}; -+ - /* - * Parse IOAPIC related entries in MADT - * returns 0 on success, < 0 on error -@@ -1310,6 +1329,11 @@ - acpi_sci_ioapic_setup(acpi_gbl_FADT.sci_interrupt, 0, 0, - acpi_gbl_FADT.sci_interrupt); + static void mwifiex_pcie_set_power_d3cold(struct pci_dev *pdev) +diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h +index d6ff964aec5bf..5d30ae39d65ec 100644 +--- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h ++++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h +@@ -4,6 +4,7 @@ + #include "pcie.h" -+ if (dmi_check_system(surface_quirk)) { -+ pr_warn("Surface hack: Override irq 7\n"); -+ mp_override_legacy_irq(7, 3, 3, 7); -+ } -+ - /* Fill in identity legacy mappings where no override */ - mp_config_acpi_legacy_irqs(); + #define QUIRK_FW_RST_D3COLD BIT(0) ++#define QUIRK_DO_FLR_ON_BRIDGE BIT(1) -diff '--color=auto' -uraN cachyos/drivers/acpi/acpi_tad.c cachyos-surface/drivers/acpi/acpi_tad.c ---- cachyos/drivers/acpi/acpi_tad.c 2023-10-30 05:31:08.000000000 +0300 -+++ cachyos-surface/drivers/acpi/acpi_tad.c 2023-11-04 18:37:07.619676820 +0300 -@@ -432,6 +432,14 @@ - - static DEVICE_ATTR_RO(caps); - -+static struct attribute *acpi_tad_attrs[] = { -+ &dev_attr_caps.attr, -+ NULL, -+}; -+static const struct attribute_group acpi_tad_attr_group = { -+ .attrs = acpi_tad_attrs, -+}; -+ - static ssize_t ac_alarm_store(struct device *dev, struct device_attribute *attr, - const char *buf, size_t count) + void mwifiex_initialize_quirks(struct pcie_service_card *card); + int mwifiex_pcie_reset_d3cold_quirk(struct pci_dev *pdev); +-- +2.42.0 + +From 241da24644ea2f5b8119019448b638aa8df6ab26 Mon Sep 17 00:00:00 2001 +From: Tsuchiya Yuto +Date: Sun, 4 Oct 2020 00:11:49 +0900 +Subject: [PATCH] mwifiex: pcie: disable bridge_d3 for Surface gen4+ + +Currently, mwifiex fw will crash after suspend on recent kernel series. +On Windows, it seems that the root port of wifi will never enter D3 state +(stay on D0 state). And on Linux, disabling the D3 state for the +bridge fixes fw crashing after suspend. + +This commit disables the D3 state of root port on driver initialization +and fixes fw crashing after suspend. + +Signed-off-by: Tsuchiya Yuto +Patchset: mwifiex +--- + drivers/net/wireless/marvell/mwifiex/pcie.c | 7 +++++ + .../wireless/marvell/mwifiex/pcie_quirks.c | 27 +++++++++++++------ + .../wireless/marvell/mwifiex/pcie_quirks.h | 1 + + 3 files changed, 27 insertions(+), 8 deletions(-) + +diff --git a/drivers/net/wireless/marvell/mwifiex/pcie.c b/drivers/net/wireless/marvell/mwifiex/pcie.c +index f06b4ebc5bd8e..07f13b52ddb92 100644 +--- a/drivers/net/wireless/marvell/mwifiex/pcie.c ++++ b/drivers/net/wireless/marvell/mwifiex/pcie.c +@@ -370,6 +370,7 @@ static int mwifiex_pcie_probe(struct pci_dev *pdev, + const struct pci_device_id *ent) { -@@ -480,15 +488,14 @@ + struct pcie_service_card *card; ++ struct pci_dev *parent_pdev = pci_upstream_bridge(pdev); + int ret; - static DEVICE_ATTR_RW(ac_status); - --static struct attribute *acpi_tad_attrs[] = { -- &dev_attr_caps.attr, -+static struct attribute *acpi_tad_ac_attrs[] = { - &dev_attr_ac_alarm.attr, - &dev_attr_ac_policy.attr, - &dev_attr_ac_status.attr, - NULL, - }; --static const struct attribute_group acpi_tad_attr_group = { -- .attrs = acpi_tad_attrs, -+static const struct attribute_group acpi_tad_ac_attr_group = { -+ .attrs = acpi_tad_ac_attrs, - }; - - static ssize_t dc_alarm_store(struct device *dev, struct device_attribute *attr, -@@ -564,13 +571,18 @@ - - pm_runtime_get_sync(dev); - -+ if (dd->capabilities & ACPI_TAD_AC_WAKE) -+ sysfs_remove_group(&dev->kobj, &acpi_tad_ac_attr_group); -+ - if (dd->capabilities & ACPI_TAD_DC_WAKE) - sysfs_remove_group(&dev->kobj, &acpi_tad_dc_attr_group); - - sysfs_remove_group(&dev->kobj, &acpi_tad_attr_group); - -- acpi_tad_disable_timer(dev, ACPI_TAD_AC_TIMER); -- acpi_tad_clear_status(dev, ACPI_TAD_AC_TIMER); -+ if (dd->capabilities & ACPI_TAD_AC_WAKE) { -+ acpi_tad_disable_timer(dev, ACPI_TAD_AC_TIMER); -+ acpi_tad_clear_status(dev, ACPI_TAD_AC_TIMER); -+ } - if (dd->capabilities & ACPI_TAD_DC_WAKE) { - acpi_tad_disable_timer(dev, ACPI_TAD_DC_TIMER); - acpi_tad_clear_status(dev, ACPI_TAD_DC_TIMER); -@@ -613,12 +625,6 @@ - goto remove_handler; + pr_debug("info: vendor=0x%4.04X device=0x%4.04X rev=%d\n", +@@ -411,6 +412,12 @@ static int mwifiex_pcie_probe(struct pci_dev *pdev, + return -1; } -- if (!acpi_has_method(handle, "_PRW")) { -- dev_info(dev, "Missing _PRW\n"); -- ret = -ENODEV; -- goto remove_handler; -- } -- - dd = devm_kzalloc(dev, sizeof(*dd), GFP_KERNEL); - if (!dd) { - ret = -ENOMEM; -@@ -649,6 +655,12 @@ - if (ret) - goto fail; - -+ if (caps & ACPI_TAD_AC_WAKE) { -+ ret = sysfs_create_group(&dev->kobj, &acpi_tad_ac_attr_group); -+ if (ret) -+ goto fail; -+ } ++ /* disable bridge_d3 for Surface gen4+ devices to fix fw crashing ++ * after suspend ++ */ ++ if (card->quirks & QUIRK_NO_BRIDGE_D3) ++ parent_pdev->bridge_d3 = false; + - if (caps & ACPI_TAD_DC_WAKE) { - ret = sysfs_create_group(&dev->kobj, &acpi_tad_dc_attr_group); - if (ret) -diff '--color=auto' -uraN cachyos/drivers/acpi/scan.c cachyos-surface/drivers/acpi/scan.c ---- cachyos/drivers/acpi/scan.c 2023-10-30 05:31:08.000000000 +0300 -+++ cachyos-surface/drivers/acpi/scan.c 2023-11-04 18:32:41.047654661 +0300 -@@ -2108,6 +2108,9 @@ + return 0; + } - static void acpi_default_enumeration(struct acpi_device *device) - { -+ if (!acpi_dev_ready_for_enumeration(device)) -+ return; -+ - /* - * Do not enumerate devices with enumeration_by_parent flag set as - * they will be enumerated by their respective parents. -diff '--color=auto' -uraN cachyos/drivers/bluetooth/btusb.c cachyos-surface/drivers/bluetooth/btusb.c ---- cachyos/drivers/bluetooth/btusb.c 2023-11-04 17:51:36.247336224 +0300 -+++ cachyos-surface/drivers/bluetooth/btusb.c 2023-11-04 18:32:41.037654516 +0300 -@@ -65,6 +65,7 @@ +diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c +index f46b06f8d6435..99b024ecbadea 100644 +--- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c ++++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c +@@ -14,7 +14,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 4"), + }, + .driver_data = (void *)(QUIRK_FW_RST_D3COLD | +- QUIRK_DO_FLR_ON_BRIDGE), ++ QUIRK_DO_FLR_ON_BRIDGE | ++ QUIRK_NO_BRIDGE_D3), + }, + { + .ident = "Surface Pro 5", +@@ -24,7 +25,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1796"), + }, + .driver_data = (void *)(QUIRK_FW_RST_D3COLD | +- QUIRK_DO_FLR_ON_BRIDGE), ++ QUIRK_DO_FLR_ON_BRIDGE | ++ QUIRK_NO_BRIDGE_D3), + }, + { + .ident = "Surface Pro 5 (LTE)", +@@ -34,7 +36,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1807"), + }, + .driver_data = (void *)(QUIRK_FW_RST_D3COLD | +- QUIRK_DO_FLR_ON_BRIDGE), ++ QUIRK_DO_FLR_ON_BRIDGE | ++ QUIRK_NO_BRIDGE_D3), + }, + { + .ident = "Surface Pro 6", +@@ -43,7 +46,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 6"), + }, + .driver_data = (void *)(QUIRK_FW_RST_D3COLD | +- QUIRK_DO_FLR_ON_BRIDGE), ++ QUIRK_DO_FLR_ON_BRIDGE | ++ QUIRK_NO_BRIDGE_D3), + }, + { + .ident = "Surface Book 1", +@@ -52,7 +56,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book"), + }, + .driver_data = (void *)(QUIRK_FW_RST_D3COLD | +- QUIRK_DO_FLR_ON_BRIDGE), ++ QUIRK_DO_FLR_ON_BRIDGE | ++ QUIRK_NO_BRIDGE_D3), + }, + { + .ident = "Surface Book 2", +@@ -61,7 +66,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book 2"), + }, + .driver_data = (void *)(QUIRK_FW_RST_D3COLD | +- QUIRK_DO_FLR_ON_BRIDGE), ++ QUIRK_DO_FLR_ON_BRIDGE | ++ QUIRK_NO_BRIDGE_D3), + }, + { + .ident = "Surface Laptop 1", +@@ -70,7 +76,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop"), + }, + .driver_data = (void *)(QUIRK_FW_RST_D3COLD | +- QUIRK_DO_FLR_ON_BRIDGE), ++ QUIRK_DO_FLR_ON_BRIDGE | ++ QUIRK_NO_BRIDGE_D3), + }, + { + .ident = "Surface Laptop 2", +@@ -79,7 +86,8 @@ static const struct dmi_system_id mwifiex_quirk_table[] = { + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop 2"), + }, + .driver_data = (void *)(QUIRK_FW_RST_D3COLD | +- QUIRK_DO_FLR_ON_BRIDGE), ++ QUIRK_DO_FLR_ON_BRIDGE | ++ QUIRK_NO_BRIDGE_D3), + }, + {} + }; +@@ -99,6 +107,9 @@ void mwifiex_initialize_quirks(struct pcie_service_card *card) + dev_info(&pdev->dev, "quirk reset_d3cold enabled\n"); + if (card->quirks & QUIRK_DO_FLR_ON_BRIDGE) + dev_info(&pdev->dev, "quirk do_flr_on_bridge enabled\n"); ++ if (card->quirks & QUIRK_NO_BRIDGE_D3) ++ dev_info(&pdev->dev, ++ "quirk no_brigde_d3 enabled\n"); + } + + static void mwifiex_pcie_set_power_d3cold(struct pci_dev *pdev) +diff --git a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h +index 5d30ae39d65ec..c14eb56eb9118 100644 +--- a/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h ++++ b/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h +@@ -5,6 +5,7 @@ + + #define QUIRK_FW_RST_D3COLD BIT(0) + #define QUIRK_DO_FLR_ON_BRIDGE BIT(1) ++#define QUIRK_NO_BRIDGE_D3 BIT(2) + + void mwifiex_initialize_quirks(struct pcie_service_card *card); + int mwifiex_pcie_reset_d3cold_quirk(struct pci_dev *pdev); +-- +2.42.0 + +From d20b58f9e2ccec57c66864e79c291c2618ab2dbe Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= +Date: Thu, 25 Mar 2021 11:33:02 +0100 +Subject: [PATCH] Bluetooth: btusb: Lower passive lescan interval on Marvell + 88W8897 + +The Marvell 88W8897 combined wifi and bluetooth card (pcie+usb version) +is used in a lot of Microsoft Surface devices, and all those devices +suffer from very low 2.4GHz wifi connection speeds while bluetooth is +enabled. The reason for that is that the default passive scanning +interval for Bluetooth Low Energy devices is quite high in Linux +(interval of 60 msec and scan window of 30 msec, see hci_core.c), and +the Marvell chip is known for its bad bt+wifi coexisting performance. + +So decrease that passive scan interval and make the scan window shorter +on this particular device to allow for spending more time transmitting +wifi signals: The new scan interval is 250 msec (0x190 * 0.625 msec) and +the new scan window is 6.25 msec (0xa * 0,625 msec). + +This change has a very large impact on the 2.4GHz wifi speeds and gets +it up to performance comparable with the Windows driver, which seems to +apply a similar quirk. + +The interval and window length were tested and found to work very well +with a lot of Bluetooth Low Energy devices, including the Surface Pen, a +Bluetooth Speaker and two modern Bluetooth headphones. All devices were +discovered immediately after turning them on. Even lower values were +also tested, but they introduced longer delays until devices get +discovered. + +Patchset: mwifiex +--- + drivers/bluetooth/btusb.c | 15 +++++++++++++++ + 1 file changed, 15 insertions(+) + +diff --git a/drivers/bluetooth/btusb.c b/drivers/bluetooth/btusb.c +index 499f4809fcdf3..2d442e080ca28 100644 +--- a/drivers/bluetooth/btusb.c ++++ b/drivers/bluetooth/btusb.c +@@ -65,6 +65,7 @@ static struct usb_driver btusb_driver; #define BTUSB_INTEL_BROKEN_INITIAL_NCMD BIT(25) #define BTUSB_INTEL_NO_WBS_SUPPORT BIT(26) #define BTUSB_ACTIONS_SEMI BIT(27) @@ -171,7 +468,7 @@ diff '--color=auto' -uraN cachyos/drivers/bluetooth/btusb.c cachyos-surface/driv static const struct usb_device_id btusb_table[] = { /* Generic Bluetooth USB device */ -@@ -468,6 +469,7 @@ +@@ -468,6 +469,7 @@ static const struct usb_device_id quirks_table[] = { { USB_DEVICE(0x1286, 0x2044), .driver_info = BTUSB_MARVELL }, { USB_DEVICE(0x1286, 0x2046), .driver_info = BTUSB_MARVELL }, { USB_DEVICE(0x1286, 0x204e), .driver_info = BTUSB_MARVELL }, @@ -179,7 +476,7 @@ diff '--color=auto' -uraN cachyos/drivers/bluetooth/btusb.c cachyos-surface/driv /* Intel Bluetooth devices */ { USB_DEVICE(0x8087, 0x0025), .driver_info = BTUSB_INTEL_COMBINED }, -@@ -4388,6 +4390,19 @@ +@@ -4388,6 +4390,19 @@ static int btusb_probe(struct usb_interface *intf, if (id->driver_info & BTUSB_MARVELL) hdev->set_bdaddr = btusb_set_bdaddr_marvell; @@ -199,397 +496,360 @@ diff '--color=auto' -uraN cachyos/drivers/bluetooth/btusb.c cachyos-surface/driv if (IS_ENABLED(CONFIG_BT_HCIBTUSB_MTK) && (id->driver_info & BTUSB_MEDIATEK)) { hdev->setup = btusb_mtk_setup; -diff '--color=auto' -uraN cachyos/drivers/hid/Kconfig cachyos-surface/drivers/hid/Kconfig ---- cachyos/drivers/hid/Kconfig 2023-10-30 05:31:08.000000000 +0300 -+++ cachyos-surface/drivers/hid/Kconfig 2023-11-04 18:32:41.040987898 +0300 -@@ -1345,4 +1345,8 @@ +-- +2.42.0 + +From c6f0985fae241ed43ea1245c9e5861e2c728e21e Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Sat, 27 Feb 2021 00:45:52 +0100 +Subject: [PATCH] ath10k: Add module parameters to override board files + +Some Surface devices, specifically the Surface Go and AMD version of the +Surface Laptop 3 (wich both come with QCA6174 WiFi chips), work better +with a different board file, as it seems that the firmeware included +upstream is buggy. + +As it is generally not a good idea to randomly overwrite files, let +alone doing so via packages, we add module parameters to override those +file names in the driver. This allows us to package/deploy the override +via a modprobe.d config. + +Signed-off-by: Maximilian Luz +Patchset: ath10k +--- + drivers/net/wireless/ath/ath10k/core.c | 58 ++++++++++++++++++++++++++ + 1 file changed, 58 insertions(+) + +diff --git a/drivers/net/wireless/ath/ath10k/core.c b/drivers/net/wireless/ath/ath10k/core.c +index 6cdb225b7eacc..19c036751fb16 100644 +--- a/drivers/net/wireless/ath/ath10k/core.c ++++ b/drivers/net/wireless/ath/ath10k/core.c +@@ -38,6 +38,9 @@ static bool fw_diag_log; + /* frame mode values are mapped as per enum ath10k_hw_txrx_mode */ + unsigned int ath10k_frame_mode = ATH10K_HW_TXRX_NATIVE_WIFI; + ++static char *override_board = ""; ++static char *override_board2 = ""; ++ + unsigned long ath10k_coredump_mask = BIT(ATH10K_FW_CRASH_DUMP_REGISTERS) | + BIT(ATH10K_FW_CRASH_DUMP_CE_DATA); + +@@ -50,6 +53,9 @@ module_param(fw_diag_log, bool, 0644); + module_param_named(frame_mode, ath10k_frame_mode, uint, 0644); + module_param_named(coredump_mask, ath10k_coredump_mask, ulong, 0444); + ++module_param(override_board, charp, 0644); ++module_param(override_board2, charp, 0644); ++ + MODULE_PARM_DESC(debug_mask, "Debugging mask"); + MODULE_PARM_DESC(uart_print, "Uart target debugging"); + MODULE_PARM_DESC(skip_otp, "Skip otp failure for calibration in testmode"); +@@ -59,6 +65,9 @@ MODULE_PARM_DESC(frame_mode, + MODULE_PARM_DESC(coredump_mask, "Bitfield of what to include in firmware crash file"); + MODULE_PARM_DESC(fw_diag_log, "Diag based fw log debugging"); + ++MODULE_PARM_DESC(override_board, "Override for board.bin file"); ++MODULE_PARM_DESC(override_board2, "Override for board-2.bin file"); ++ + static const struct ath10k_hw_params ath10k_hw_params_list[] = { + { + .id = QCA988X_HW_2_0_VERSION, +@@ -911,6 +920,42 @@ static int ath10k_init_configure_target(struct ath10k *ar) + return 0; + } + ++static const char *ath10k_override_board_fw_file(struct ath10k *ar, ++ const char *file) ++{ ++ if (strcmp(file, "board.bin") == 0) { ++ if (strcmp(override_board, "") == 0) ++ return file; ++ ++ if (strcmp(override_board, "none") == 0) { ++ dev_info(ar->dev, "firmware override: pretending 'board.bin' does not exist\n"); ++ return NULL; ++ } ++ ++ dev_info(ar->dev, "firmware override: replacing 'board.bin' with '%s'\n", ++ override_board); ++ ++ return override_board; ++ } ++ ++ if (strcmp(file, "board-2.bin") == 0) { ++ if (strcmp(override_board2, "") == 0) ++ return file; ++ ++ if (strcmp(override_board2, "none") == 0) { ++ dev_info(ar->dev, "firmware override: pretending 'board-2.bin' does not exist\n"); ++ return NULL; ++ } ++ ++ dev_info(ar->dev, "firmware override: replacing 'board-2.bin' with '%s'\n", ++ override_board2); ++ ++ return override_board2; ++ } ++ ++ return file; ++} ++ + static const struct firmware *ath10k_fetch_fw_file(struct ath10k *ar, + const char *dir, + const char *file) +@@ -925,6 +970,19 @@ static const struct firmware *ath10k_fetch_fw_file(struct ath10k *ar, + if (dir == NULL) + dir = "."; + ++ /* HACK: Override board.bin and board-2.bin files if specified. ++ * ++ * Some Surface devices perform better with a different board ++ * configuration. To this end, one would need to replace the board.bin ++ * file with the modified config and remove the board-2.bin file. ++ * Unfortunately, that's not a solution that we can easily package. So ++ * we add module options to perform these overrides here. ++ */ ++ ++ file = ath10k_override_board_fw_file(ar, file); ++ if (!file) ++ return ERR_PTR(-ENOENT); ++ + snprintf(filename, sizeof(filename), "%s/%s", dir, file); + ret = firmware_request_nowarn(&fw, filename, ar->dev); + ath10k_dbg(ar, ATH10K_DBG_BOOT, "boot fw request '%s': %d\n", +-- +2.42.0 + +From 986fe56f682f93925b2964f59fe78c7043758e47 Mon Sep 17 00:00:00 2001 +From: Dorian Stoll +Date: Thu, 30 Jul 2020 13:21:53 +0200 +Subject: [PATCH] misc: mei: Add missing IPTS device IDs + +Patchset: ipts +--- + drivers/misc/mei/hw-me-regs.h | 1 + + drivers/misc/mei/pci-me.c | 1 + + 2 files changed, 2 insertions(+) + +diff --git a/drivers/misc/mei/hw-me-regs.h b/drivers/misc/mei/hw-me-regs.h +index bdc65d50b945f..08723c01d7275 100644 +--- a/drivers/misc/mei/hw-me-regs.h ++++ b/drivers/misc/mei/hw-me-regs.h +@@ -92,6 +92,7 @@ + #define MEI_DEV_ID_CDF 0x18D3 /* Cedar Fork */ + + #define MEI_DEV_ID_ICP_LP 0x34E0 /* Ice Lake Point LP */ ++#define MEI_DEV_ID_ICP_LP_3 0x34E4 /* Ice Lake Point LP 3 (iTouch) */ + #define MEI_DEV_ID_ICP_N 0x38E0 /* Ice Lake Point N */ + + #define MEI_DEV_ID_JSP_N 0x4DE0 /* Jasper Lake Point N */ +diff --git a/drivers/misc/mei/pci-me.c b/drivers/misc/mei/pci-me.c +index 676d566f38ddf..6b37dd1f8b2a3 100644 +--- a/drivers/misc/mei/pci-me.c ++++ b/drivers/misc/mei/pci-me.c +@@ -97,6 +97,7 @@ static const struct pci_device_id mei_me_pci_tbl[] = { + {MEI_PCI_DEVICE(MEI_DEV_ID_CMP_H_3, MEI_ME_PCH8_ITOUCH_CFG)}, + + {MEI_PCI_DEVICE(MEI_DEV_ID_ICP_LP, MEI_ME_PCH12_CFG)}, ++ {MEI_PCI_DEVICE(MEI_DEV_ID_ICP_LP_3, MEI_ME_PCH12_CFG)}, + {MEI_PCI_DEVICE(MEI_DEV_ID_ICP_N, MEI_ME_PCH12_CFG)}, + + {MEI_PCI_DEVICE(MEI_DEV_ID_TGP_LP, MEI_ME_PCH15_CFG)}, +-- +2.42.0 + +From 72ee1cbf26ccc575dbfbaee5e7305ab13e1aeb1e Mon Sep 17 00:00:00 2001 +From: Liban Hannan +Date: Tue, 12 Apr 2022 23:31:12 +0100 +Subject: [PATCH] iommu: ipts: use IOMMU passthrough mode for IPTS + +Adds a quirk so that IOMMU uses passthrough mode for the IPTS device. +Otherwise, when IOMMU is enabled, IPTS produces DMAR errors like: + +DMAR: [DMA Read NO_PASID] Request device [00:16.4] fault addr +0x104ea3000 [fault reason 0x06] PTE Read access is not set + +This is very similar to the bug described at: +https://bugs.launchpad.net/bugs/1958004 + +Fixed with the following patch which this patch basically copies: +https://launchpadlibrarian.net/586396847/43255ca.diff +Patchset: ipts +--- + drivers/iommu/intel/iommu.c | 24 ++++++++++++++++++++++++ + 1 file changed, 24 insertions(+) + +diff --git a/drivers/iommu/intel/iommu.c b/drivers/iommu/intel/iommu.c +index 3685ba90ec88e..5a627e081797c 100644 +--- a/drivers/iommu/intel/iommu.c ++++ b/drivers/iommu/intel/iommu.c +@@ -38,6 +38,8 @@ + #define IS_GFX_DEVICE(pdev) ((pdev->class >> 16) == PCI_BASE_CLASS_DISPLAY) + #define IS_USB_DEVICE(pdev) ((pdev->class >> 8) == PCI_CLASS_SERIAL_USB) + #define IS_ISA_DEVICE(pdev) ((pdev->class >> 8) == PCI_CLASS_BRIDGE_ISA) ++#define IS_IPTS(pdev) ((pdev)->vendor == PCI_VENDOR_ID_INTEL && \ ++ ((pdev)->device == 0x9d3e)) + #define IS_AZALIA(pdev) ((pdev)->vendor == 0x8086 && (pdev)->device == 0x3a3e) + + #define IOAPIC_RANGE_START (0xfee00000) +@@ -292,12 +294,14 @@ int intel_iommu_enabled = 0; + EXPORT_SYMBOL_GPL(intel_iommu_enabled); + + static int dmar_map_gfx = 1; ++static int dmar_map_ipts = 1; + static int intel_iommu_superpage = 1; + static int iommu_identity_mapping; + static int iommu_skip_te_disable; + + #define IDENTMAP_GFX 2 + #define IDENTMAP_AZALIA 4 ++#define IDENTMAP_IPTS 16 + + const struct iommu_ops intel_iommu_ops; + +@@ -2542,6 +2546,9 @@ static int device_def_domain_type(struct device *dev) + + if ((iommu_identity_mapping & IDENTMAP_GFX) && IS_GFX_DEVICE(pdev)) + return IOMMU_DOMAIN_IDENTITY; ++ ++ if ((iommu_identity_mapping & IDENTMAP_IPTS) && IS_IPTS(pdev)) ++ return IOMMU_DOMAIN_IDENTITY; + } + + return 0; +@@ -2849,6 +2856,9 @@ static int __init init_dmars(void) + if (!dmar_map_gfx) + iommu_identity_mapping |= IDENTMAP_GFX; + ++ if (!dmar_map_ipts) ++ iommu_identity_mapping |= IDENTMAP_IPTS; ++ + check_tylersburg_isoch(); + + ret = si_domain_init(hw_pass_through); +@@ -4828,6 +4838,17 @@ static void quirk_iommu_igfx(struct pci_dev *dev) + dmar_map_gfx = 0; + } + ++static void quirk_iommu_ipts(struct pci_dev *dev) ++{ ++ if (!IS_IPTS(dev)) ++ return; ++ ++ if (risky_device(dev)) ++ return; ++ ++ pci_info(dev, "Passthrough IOMMU for IPTS\n"); ++ dmar_map_ipts = 0; ++} + /* G4x/GM45 integrated gfx dmar support is totally busted. */ + DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x2a40, quirk_iommu_igfx); + DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x2e00, quirk_iommu_igfx); +@@ -4863,6 +4884,9 @@ DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x1632, quirk_iommu_igfx); + DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x163A, quirk_iommu_igfx); + DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x163D, quirk_iommu_igfx); + ++/* disable IPTS dmar support */ ++DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x9D3E, quirk_iommu_ipts); ++ + static void quirk_iommu_rwbf(struct pci_dev *dev) + { + if (risky_device(dev)) +-- +2.42.0 + +From 8330f9f39ce8c9796259a8aeffe919fa950e18f5 Mon Sep 17 00:00:00 2001 +From: Dorian Stoll +Date: Sun, 11 Dec 2022 12:00:59 +0100 +Subject: [PATCH] hid: Add support for Intel Precise Touch and Stylus + +Based on linux-surface/intel-precise-touch@8abe268 + +Signed-off-by: Dorian Stoll +Patchset: ipts +--- + drivers/hid/Kconfig | 2 + + drivers/hid/Makefile | 2 + + drivers/hid/ipts/Kconfig | 14 + + drivers/hid/ipts/Makefile | 16 ++ + drivers/hid/ipts/cmd.c | 61 +++++ + drivers/hid/ipts/cmd.h | 60 ++++ + drivers/hid/ipts/context.h | 52 ++++ + drivers/hid/ipts/control.c | 486 +++++++++++++++++++++++++++++++++ + drivers/hid/ipts/control.h | 126 +++++++++ + drivers/hid/ipts/desc.h | 80 ++++++ + drivers/hid/ipts/eds1.c | 103 +++++++ + drivers/hid/ipts/eds1.h | 35 +++ + drivers/hid/ipts/eds2.c | 144 ++++++++++ + drivers/hid/ipts/eds2.h | 35 +++ + drivers/hid/ipts/hid.c | 225 +++++++++++++++ + drivers/hid/ipts/hid.h | 24 ++ + drivers/hid/ipts/main.c | 126 +++++++++ + drivers/hid/ipts/mei.c | 188 +++++++++++++ + drivers/hid/ipts/mei.h | 66 +++++ + drivers/hid/ipts/receiver.c | 250 +++++++++++++++++ + drivers/hid/ipts/receiver.h | 16 ++ + drivers/hid/ipts/resources.c | 131 +++++++++ + drivers/hid/ipts/resources.h | 41 +++ + drivers/hid/ipts/spec-data.h | 100 +++++++ + drivers/hid/ipts/spec-device.h | 290 ++++++++++++++++++++ + drivers/hid/ipts/spec-hid.h | 34 +++ + drivers/hid/ipts/thread.c | 84 ++++++ + drivers/hid/ipts/thread.h | 59 ++++ + 28 files changed, 2850 insertions(+) + create mode 100644 drivers/hid/ipts/Kconfig + create mode 100644 drivers/hid/ipts/Makefile + create mode 100644 drivers/hid/ipts/cmd.c + create mode 100644 drivers/hid/ipts/cmd.h + create mode 100644 drivers/hid/ipts/context.h + create mode 100644 drivers/hid/ipts/control.c + create mode 100644 drivers/hid/ipts/control.h + create mode 100644 drivers/hid/ipts/desc.h + create mode 100644 drivers/hid/ipts/eds1.c + create mode 100644 drivers/hid/ipts/eds1.h + create mode 100644 drivers/hid/ipts/eds2.c + create mode 100644 drivers/hid/ipts/eds2.h + create mode 100644 drivers/hid/ipts/hid.c + create mode 100644 drivers/hid/ipts/hid.h + create mode 100644 drivers/hid/ipts/main.c + create mode 100644 drivers/hid/ipts/mei.c + create mode 100644 drivers/hid/ipts/mei.h + create mode 100644 drivers/hid/ipts/receiver.c + create mode 100644 drivers/hid/ipts/receiver.h + create mode 100644 drivers/hid/ipts/resources.c + create mode 100644 drivers/hid/ipts/resources.h + create mode 100644 drivers/hid/ipts/spec-data.h + create mode 100644 drivers/hid/ipts/spec-device.h + create mode 100644 drivers/hid/ipts/spec-hid.h + create mode 100644 drivers/hid/ipts/thread.c + create mode 100644 drivers/hid/ipts/thread.h + +diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig +index 790aa908e2a78..0b9d245d10e54 100644 +--- a/drivers/hid/Kconfig ++++ b/drivers/hid/Kconfig +@@ -1345,4 +1345,6 @@ source "drivers/hid/amd-sfh-hid/Kconfig" source "drivers/hid/surface-hid/Kconfig" +source "drivers/hid/ipts/Kconfig" -+ -+source "drivers/hid/ithc/Kconfig" + endif # HID_SUPPORT -diff '--color=auto' -uraN cachyos/drivers/hid/Makefile cachyos-surface/drivers/hid/Makefile ---- cachyos/drivers/hid/Makefile 2023-10-30 05:31:08.000000000 +0300 -+++ cachyos-surface/drivers/hid/Makefile 2023-11-04 18:32:41.040987898 +0300 -@@ -169,3 +169,6 @@ +diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile +index 8a06d0f840bcb..2ef21b257d0b5 100644 +--- a/drivers/hid/Makefile ++++ b/drivers/hid/Makefile +@@ -169,3 +169,5 @@ obj-$(INTEL_ISH_FIRMWARE_DOWNLOADER) += intel-ish-hid/ obj-$(CONFIG_AMD_SFH_HID) += amd-sfh-hid/ obj-$(CONFIG_SURFACE_HID_CORE) += surface-hid/ + +obj-$(CONFIG_HID_IPTS) += ipts/ -+obj-$(CONFIG_HID_ITHC) += ithc/ -diff '--color=auto' -uraN cachyos/drivers/hid/hid-multitouch.c cachyos-surface/drivers/hid/hid-multitouch.c ---- cachyos/drivers/hid/hid-multitouch.c 2023-10-30 05:31:08.000000000 +0300 -+++ cachyos-surface/drivers/hid/hid-multitouch.c 2023-11-04 18:32:41.044321279 +0300 -@@ -34,7 +34,10 @@ - #include - #include - #include -+#include - #include -+#include -+#include - #include - #include - #include -@@ -47,6 +50,7 @@ - MODULE_LICENSE("GPL"); - - #include "hid-ids.h" -+#include "usbhid/usbhid.h" - - /* quirks to control the device */ - #define MT_QUIRK_NOT_SEEN_MEANS_UP BIT(0) -@@ -72,12 +76,18 @@ - #define MT_QUIRK_FORCE_MULTI_INPUT BIT(20) - #define MT_QUIRK_DISABLE_WAKEUP BIT(21) - #define MT_QUIRK_ORIENTATION_INVERT BIT(22) -+#define MT_QUIRK_HAS_TYPE_COVER_BACKLIGHT BIT(23) -+#define MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH BIT(24) - - #define MT_INPUTMODE_TOUCHSCREEN 0x02 - #define MT_INPUTMODE_TOUCHPAD 0x03 - - #define MT_BUTTONTYPE_CLICKPAD 0 - -+#define MS_TYPE_COVER_FEATURE_REPORT_USAGE 0xff050086 -+#define MS_TYPE_COVER_TABLET_MODE_SWITCH_USAGE 0xff050072 -+#define MS_TYPE_COVER_APPLICATION 0xff050050 -+ - enum latency_mode { - HID_LATENCY_NORMAL = 0, - HID_LATENCY_HIGH = 1, -@@ -169,6 +179,8 @@ - - struct list_head applications; - struct list_head reports; -+ -+ struct notifier_block pm_notifier; - }; - - static void mt_post_parse_default_settings(struct mt_device *td, -@@ -213,6 +225,7 @@ - #define MT_CLS_GOOGLE 0x0111 - #define MT_CLS_RAZER_BLADE_STEALTH 0x0112 - #define MT_CLS_SMART_TECH 0x0113 -+#define MT_CLS_WIN_8_MS_SURFACE_TYPE_COVER 0x0114 - - #define MT_DEFAULT_MAXCONTACT 10 - #define MT_MAX_MAXCONTACT 250 -@@ -397,6 +410,17 @@ - MT_QUIRK_CONTACT_CNT_ACCURATE | - MT_QUIRK_SEPARATE_APP_REPORT, - }, -+ { .name = MT_CLS_WIN_8_MS_SURFACE_TYPE_COVER, -+ .quirks = MT_QUIRK_HAS_TYPE_COVER_BACKLIGHT | -+ MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH | -+ MT_QUIRK_ALWAYS_VALID | -+ MT_QUIRK_IGNORE_DUPLICATES | -+ MT_QUIRK_HOVERING | -+ MT_QUIRK_CONTACT_CNT_ACCURATE | -+ MT_QUIRK_STICKY_FINGERS | -+ MT_QUIRK_WIN8_PTP_BUTTONS, -+ .export_all_inputs = true -+ }, - { } - }; - -@@ -1370,6 +1394,9 @@ - field->application != HID_CP_CONSUMER_CONTROL && - field->application != HID_GD_WIRELESS_RADIO_CTLS && - field->application != HID_GD_SYSTEM_MULTIAXIS && -+ !(field->application == MS_TYPE_COVER_APPLICATION && -+ application->quirks & MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH && -+ usage->hid == MS_TYPE_COVER_TABLET_MODE_SWITCH_USAGE) && - !(field->application == HID_VD_ASUS_CUSTOM_MEDIA_KEYS && - application->quirks & MT_QUIRK_ASUS_CUSTOM_UP)) - return -1; -@@ -1397,6 +1424,21 @@ - return 1; - } - -+ /* -+ * The Microsoft Surface Pro Typecover has a non-standard HID -+ * tablet mode switch on a vendor specific usage page with vendor -+ * specific usage. -+ */ -+ if (field->application == MS_TYPE_COVER_APPLICATION && -+ application->quirks & MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH && -+ usage->hid == MS_TYPE_COVER_TABLET_MODE_SWITCH_USAGE) { -+ usage->type = EV_SW; -+ usage->code = SW_TABLET_MODE; -+ *max = SW_MAX; -+ *bit = hi->input->swbit; -+ return 1; -+ } -+ - if (rdata->is_mt_collection) - return mt_touch_input_mapping(hdev, hi, field, usage, bit, max, - application); -@@ -1418,6 +1460,7 @@ - { - struct mt_device *td = hid_get_drvdata(hdev); - struct mt_report_data *rdata; -+ struct input_dev *input; - - rdata = mt_find_report_data(td, field->report); - if (rdata && rdata->is_mt_collection) { -@@ -1425,6 +1468,19 @@ - return -1; - } - -+ /* -+ * We own an input device which acts as a tablet mode switch for -+ * the Surface Pro Typecover. -+ */ -+ if (field->application == MS_TYPE_COVER_APPLICATION && -+ rdata->application->quirks & MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH && -+ usage->hid == MS_TYPE_COVER_TABLET_MODE_SWITCH_USAGE) { -+ input = hi->input; -+ input_set_capability(input, EV_SW, SW_TABLET_MODE); -+ input_report_switch(input, SW_TABLET_MODE, 0); -+ return -1; -+ } -+ - /* let hid-core decide for the others */ - return 0; - } -@@ -1434,11 +1490,21 @@ - { - struct mt_device *td = hid_get_drvdata(hid); - struct mt_report_data *rdata; -+ struct input_dev *input; - - rdata = mt_find_report_data(td, field->report); - if (rdata && rdata->is_mt_collection) - return mt_touch_event(hid, field, usage, value); - -+ if (field->application == MS_TYPE_COVER_APPLICATION && -+ rdata->application->quirks & MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH && -+ usage->hid == MS_TYPE_COVER_TABLET_MODE_SWITCH_USAGE) { -+ input = field->hidinput->input; -+ input_report_switch(input, SW_TABLET_MODE, (value & 0xFF) != 0x22); -+ input_sync(input); -+ return 1; -+ } -+ - return 0; - } - -@@ -1591,6 +1657,42 @@ - app->quirks &= ~MT_QUIRK_CONTACT_CNT_ACCURATE; - } - -+static int get_type_cover_field(struct hid_report_enum *rep_enum, -+ struct hid_field **field, int usage) -+{ -+ struct hid_report *rep; -+ struct hid_field *cur_field; -+ int i, j; -+ -+ list_for_each_entry(rep, &rep_enum->report_list, list) { -+ for (i = 0; i < rep->maxfield; i++) { -+ cur_field = rep->field[i]; -+ if (cur_field->application != MS_TYPE_COVER_APPLICATION) -+ continue; -+ for (j = 0; j < cur_field->maxusage; j++) { -+ if (cur_field->usage[j].hid == usage) { -+ *field = cur_field; -+ return true; -+ } -+ } -+ } -+ } -+ return false; -+} -+ -+static void request_type_cover_tablet_mode_switch(struct hid_device *hdev) -+{ -+ struct hid_field *field; -+ -+ if (get_type_cover_field(&hdev->report_enum[HID_INPUT_REPORT], -+ &field, -+ MS_TYPE_COVER_TABLET_MODE_SWITCH_USAGE)) { -+ hid_hw_request(hdev, field->report, HID_REQ_GET_REPORT); -+ } else { -+ hid_err(hdev, "couldn't find tablet mode field\n"); -+ } -+} -+ - static int mt_input_configured(struct hid_device *hdev, struct hid_input *hi) - { - struct mt_device *td = hid_get_drvdata(hdev); -@@ -1639,6 +1741,13 @@ - /* force BTN_STYLUS to allow tablet matching in udev */ - __set_bit(BTN_STYLUS, hi->input->keybit); - break; -+ case MS_TYPE_COVER_APPLICATION: -+ if (td->mtclass.quirks & MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH) { -+ suffix = "Tablet Mode Switch"; -+ request_type_cover_tablet_mode_switch(hdev); -+ break; -+ } -+ fallthrough; - default: - suffix = "UNKNOWN"; - break; -@@ -1721,6 +1830,46 @@ - clear_bit_unlock(MT_IO_FLAGS_RUNNING, &td->mt_io_flags); - } - -+static void update_keyboard_backlight(struct hid_device *hdev, bool enabled) -+{ -+ struct usb_device *udev = hid_to_usb_dev(hdev); -+ struct hid_field *field = NULL; -+ -+ /* Wake up the device in case it's already suspended */ -+ pm_runtime_get_sync(&udev->dev); -+ -+ if (!get_type_cover_field(&hdev->report_enum[HID_FEATURE_REPORT], -+ &field, -+ MS_TYPE_COVER_FEATURE_REPORT_USAGE)) { -+ hid_err(hdev, "couldn't find backlight field\n"); -+ goto out; -+ } -+ -+ field->value[field->index] = enabled ? 0x01ff00ff : 0x00ff00ff; -+ hid_hw_request(hdev, field->report, HID_REQ_SET_REPORT); -+ -+out: -+ pm_runtime_put_sync(&udev->dev); -+} -+ -+static int mt_pm_notifier(struct notifier_block *notifier, -+ unsigned long pm_event, -+ void *unused) -+{ -+ struct mt_device *td = -+ container_of(notifier, struct mt_device, pm_notifier); -+ struct hid_device *hdev = td->hdev; -+ -+ if (td->mtclass.quirks & MT_QUIRK_HAS_TYPE_COVER_BACKLIGHT) { -+ if (pm_event == PM_SUSPEND_PREPARE) -+ update_keyboard_backlight(hdev, 0); -+ else if (pm_event == PM_POST_SUSPEND) -+ update_keyboard_backlight(hdev, 1); -+ } -+ -+ return NOTIFY_DONE; -+} -+ - static int mt_probe(struct hid_device *hdev, const struct hid_device_id *id) - { - int ret, i; -@@ -1744,6 +1893,9 @@ - td->inputmode_value = MT_INPUTMODE_TOUCHSCREEN; - hid_set_drvdata(hdev, td); - -+ td->pm_notifier.notifier_call = mt_pm_notifier; -+ register_pm_notifier(&td->pm_notifier); -+ - INIT_LIST_HEAD(&td->applications); - INIT_LIST_HEAD(&td->reports); - -@@ -1782,15 +1934,19 @@ - timer_setup(&td->release_timer, mt_expired_timeout, 0); - - ret = hid_parse(hdev); -- if (ret != 0) -+ if (ret != 0) { -+ unregister_pm_notifier(&td->pm_notifier); - return ret; -+ } - - if (mtclass->quirks & MT_QUIRK_FIX_CONST_CONTACT_ID) - mt_fix_const_fields(hdev, HID_DG_CONTACTID); - - ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT); -- if (ret) -+ if (ret) { -+ unregister_pm_notifier(&td->pm_notifier); - return ret; -+ } - - ret = sysfs_create_group(&hdev->dev.kobj, &mt_attribute_group); - if (ret) -@@ -1819,13 +1975,24 @@ - - static int mt_reset_resume(struct hid_device *hdev) - { -+ struct mt_device *td = hid_get_drvdata(hdev); -+ - mt_release_contacts(hdev); - mt_set_modes(hdev, HID_LATENCY_NORMAL, true, true); -+ -+ /* Request an update on the typecover folding state on resume -+ * after reset. -+ */ -+ if (td->mtclass.quirks & MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH) -+ request_type_cover_tablet_mode_switch(hdev); -+ - return 0; - } - - static int mt_resume(struct hid_device *hdev) - { -+ struct mt_device *td = hid_get_drvdata(hdev); -+ - /* Some Elan legacy devices require SET_IDLE to be set on resume. - * It should be safe to send it to other devices too. - * Tested on 3M, Stantum, Cypress, Zytronic, eGalax, and Elan panels. */ -@@ -1834,6 +2001,10 @@ - - mt_set_modes(hdev, HID_LATENCY_NORMAL, true, true); - -+ /* Request an update on the typecover folding state on resume. */ -+ if (td->mtclass.quirks & MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH) -+ request_type_cover_tablet_mode_switch(hdev); -+ - return 0; - } - #endif -@@ -1841,7 +2012,23 @@ - static void mt_remove(struct hid_device *hdev) - { - struct mt_device *td = hid_get_drvdata(hdev); -+ struct hid_field *field; -+ struct input_dev *input; - -+ /* Reset tablet mode switch on disconnect. */ -+ if (td->mtclass.quirks & MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH) { -+ if (get_type_cover_field(&hdev->report_enum[HID_INPUT_REPORT], -+ &field, -+ MS_TYPE_COVER_TABLET_MODE_SWITCH_USAGE)) { -+ input = field->hidinput->input; -+ input_report_switch(input, SW_TABLET_MODE, 0); -+ input_sync(input); -+ } else { -+ hid_err(hdev, "couldn't find tablet mode field\n"); -+ } -+ } -+ -+ unregister_pm_notifier(&td->pm_notifier); - del_timer_sync(&td->release_timer); - - sysfs_remove_group(&hdev->dev.kobj, &mt_attribute_group); -@@ -2223,6 +2410,11 @@ - MT_USB_DEVICE(USB_VENDOR_ID_XIROKU, - USB_DEVICE_ID_XIROKU_CSR2) }, - -+ /* Microsoft Surface type cover */ -+ { .driver_data = MT_CLS_WIN_8_MS_SURFACE_TYPE_COVER, -+ HID_DEVICE(HID_BUS_ANY, HID_GROUP_ANY, -+ USB_VENDOR_ID_MICROSOFT, 0x09c0) }, -+ - /* Google MT devices */ - { .driver_data = MT_CLS_GOOGLE, - HID_DEVICE(HID_BUS_ANY, HID_GROUP_ANY, USB_VENDOR_ID_GOOGLE, -diff '--color=auto' -uraN cachyos/drivers/hid/ipts/Kconfig cachyos-surface/drivers/hid/ipts/Kconfig ---- cachyos/drivers/hid/ipts/Kconfig 1970-01-01 03:00:00.000000000 +0300 -+++ cachyos-surface/drivers/hid/ipts/Kconfig 2023-11-04 18:32:41.037654516 +0300 +diff --git a/drivers/hid/ipts/Kconfig b/drivers/hid/ipts/Kconfig +new file mode 100644 +index 0000000000000..297401bd388dd +--- /dev/null ++++ b/drivers/hid/ipts/Kconfig @@ -0,0 +1,14 @@ +# SPDX-License-Identifier: GPL-2.0-or-later + @@ -605,9 +865,11 @@ diff '--color=auto' -uraN cachyos/drivers/hid/ipts/Kconfig cachyos-surface/drive + + To compile this driver as a module, choose M here: the + module will be called ipts. -diff '--color=auto' -uraN cachyos/drivers/hid/ipts/Makefile cachyos-surface/drivers/hid/ipts/Makefile ---- cachyos/drivers/hid/ipts/Makefile 1970-01-01 03:00:00.000000000 +0300 -+++ cachyos-surface/drivers/hid/ipts/Makefile 2023-11-04 18:32:41.037654516 +0300 +diff --git a/drivers/hid/ipts/Makefile b/drivers/hid/ipts/Makefile +new file mode 100644 +index 0000000000000..883896f68e6ad +--- /dev/null ++++ b/drivers/hid/ipts/Makefile @@ -0,0 +1,16 @@ +# SPDX-License-Identifier: GPL-2.0-or-later +# @@ -625,9 +887,11 @@ diff '--color=auto' -uraN cachyos/drivers/hid/ipts/Makefile cachyos-surface/driv +ipts-objs += receiver.o +ipts-objs += resources.o +ipts-objs += thread.o -diff '--color=auto' -uraN cachyos/drivers/hid/ipts/cmd.c cachyos-surface/drivers/hid/ipts/cmd.c ---- cachyos/drivers/hid/ipts/cmd.c 1970-01-01 03:00:00.000000000 +0300 -+++ cachyos-surface/drivers/hid/ipts/cmd.c 2023-11-04 18:32:41.037654516 +0300 +diff --git a/drivers/hid/ipts/cmd.c b/drivers/hid/ipts/cmd.c +new file mode 100644 +index 0000000000000..63a4934bbc5fa +--- /dev/null ++++ b/drivers/hid/ipts/cmd.c @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* @@ -690,9 +954,11 @@ diff '--color=auto' -uraN cachyos/drivers/hid/ipts/cmd.c cachyos-surface/drivers + dev_dbg(ipts->dev, "Sending 0x%02X with %ld bytes payload\n", code, size); + return ipts_mei_send(&ipts->mei, &cmd, sizeof(cmd.cmd) + size); +} -diff '--color=auto' -uraN cachyos/drivers/hid/ipts/cmd.h cachyos-surface/drivers/hid/ipts/cmd.h ---- cachyos/drivers/hid/ipts/cmd.h 1970-01-01 03:00:00.000000000 +0300 -+++ cachyos-surface/drivers/hid/ipts/cmd.h 2023-11-04 18:32:41.037654516 +0300 +diff --git a/drivers/hid/ipts/cmd.h b/drivers/hid/ipts/cmd.h +new file mode 100644 +index 0000000000000..2b4079075b642 +--- /dev/null ++++ b/drivers/hid/ipts/cmd.h @@ -0,0 +1,60 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* @@ -754,9 +1020,11 @@ diff '--color=auto' -uraN cachyos/drivers/hid/ipts/cmd.h cachyos-surface/drivers +int ipts_cmd_send(struct ipts_context *ipts, enum ipts_command_code code, void *data, size_t size); + +#endif /* IPTS_CMD_H */ -diff '--color=auto' -uraN cachyos/drivers/hid/ipts/context.h cachyos-surface/drivers/hid/ipts/context.h ---- cachyos/drivers/hid/ipts/context.h 1970-01-01 03:00:00.000000000 +0300 -+++ cachyos-surface/drivers/hid/ipts/context.h 2023-11-04 18:32:41.037654516 +0300 +diff --git a/drivers/hid/ipts/context.h b/drivers/hid/ipts/context.h +new file mode 100644 +index 0000000000000..ba33259f1f7c5 +--- /dev/null ++++ b/drivers/hid/ipts/context.h @@ -0,0 +1,52 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* @@ -810,9 +1078,11 @@ diff '--color=auto' -uraN cachyos/drivers/hid/ipts/context.h cachyos-surface/dri +}; + +#endif /* IPTS_CONTEXT_H */ -diff '--color=auto' -uraN cachyos/drivers/hid/ipts/control.c cachyos-surface/drivers/hid/ipts/control.c ---- cachyos/drivers/hid/ipts/control.c 1970-01-01 03:00:00.000000000 +0300 -+++ cachyos-surface/drivers/hid/ipts/control.c 2023-11-04 18:32:41.037654516 +0300 +diff --git a/drivers/hid/ipts/control.c b/drivers/hid/ipts/control.c +new file mode 100644 +index 0000000000000..5360842d260ba +--- /dev/null ++++ b/drivers/hid/ipts/control.c @@ -0,0 +1,486 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* @@ -1300,9 +1570,11 @@ diff '--color=auto' -uraN cachyos/drivers/hid/ipts/control.c cachyos-surface/dri + + return 0; +} -diff '--color=auto' -uraN cachyos/drivers/hid/ipts/control.h cachyos-surface/drivers/hid/ipts/control.h ---- cachyos/drivers/hid/ipts/control.h 1970-01-01 03:00:00.000000000 +0300 -+++ cachyos-surface/drivers/hid/ipts/control.h 2023-11-04 18:32:41.037654516 +0300 +diff --git a/drivers/hid/ipts/control.h b/drivers/hid/ipts/control.h +new file mode 100644 +index 0000000000000..26629c5144edb +--- /dev/null ++++ b/drivers/hid/ipts/control.h @@ -0,0 +1,126 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* @@ -1430,9 +1702,11 @@ diff '--color=auto' -uraN cachyos/drivers/hid/ipts/control.h cachyos-surface/dri +int ipts_control_restart(struct ipts_context *ipts); + +#endif /* IPTS_CONTROL_H */ -diff '--color=auto' -uraN cachyos/drivers/hid/ipts/desc.h cachyos-surface/drivers/hid/ipts/desc.h ---- cachyos/drivers/hid/ipts/desc.h 1970-01-01 03:00:00.000000000 +0300 -+++ cachyos-surface/drivers/hid/ipts/desc.h 2023-11-04 18:32:41.037654516 +0300 +diff --git a/drivers/hid/ipts/desc.h b/drivers/hid/ipts/desc.h +new file mode 100644 +index 0000000000000..307438c7c80cd +--- /dev/null ++++ b/drivers/hid/ipts/desc.h @@ -0,0 +1,80 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* @@ -1514,9 +1788,11 @@ diff '--color=auto' -uraN cachyos/drivers/hid/ipts/desc.h cachyos-surface/driver +}; + +#endif /* IPTS_DESC_H */ -diff '--color=auto' -uraN cachyos/drivers/hid/ipts/eds1.c cachyos-surface/drivers/hid/ipts/eds1.c ---- cachyos/drivers/hid/ipts/eds1.c 1970-01-01 03:00:00.000000000 +0300 -+++ cachyos-surface/drivers/hid/ipts/eds1.c 2023-11-04 18:32:41.037654516 +0300 +diff --git a/drivers/hid/ipts/eds1.c b/drivers/hid/ipts/eds1.c +new file mode 100644 +index 0000000000000..ecbb3a8bdaf60 +--- /dev/null ++++ b/drivers/hid/ipts/eds1.c @@ -0,0 +1,103 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* @@ -1621,9 +1897,11 @@ diff '--color=auto' -uraN cachyos/drivers/hid/ipts/eds1.c cachyos-surface/driver + + return ret; +} -diff '--color=auto' -uraN cachyos/drivers/hid/ipts/eds1.h cachyos-surface/drivers/hid/ipts/eds1.h ---- cachyos/drivers/hid/ipts/eds1.h 1970-01-01 03:00:00.000000000 +0300 -+++ cachyos-surface/drivers/hid/ipts/eds1.h 2023-11-04 18:32:41.037654516 +0300 +diff --git a/drivers/hid/ipts/eds1.h b/drivers/hid/ipts/eds1.h +new file mode 100644 +index 0000000000000..eeeb6575e3e89 +--- /dev/null ++++ b/drivers/hid/ipts/eds1.h @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* @@ -1660,9 +1938,11 @@ diff '--color=auto' -uraN cachyos/drivers/hid/ipts/eds1.h cachyos-surface/driver + */ +int ipts_eds1_raw_request(struct ipts_context *ipts, u8 *buffer, size_t size, u8 report_id, + enum hid_report_type report_type, enum hid_class_request request_type); -diff '--color=auto' -uraN cachyos/drivers/hid/ipts/eds2.c cachyos-surface/drivers/hid/ipts/eds2.c ---- cachyos/drivers/hid/ipts/eds2.c 1970-01-01 03:00:00.000000000 +0300 -+++ cachyos-surface/drivers/hid/ipts/eds2.c 2023-11-04 18:32:41.037654516 +0300 +diff --git a/drivers/hid/ipts/eds2.c b/drivers/hid/ipts/eds2.c +new file mode 100644 +index 0000000000000..198dc65d78876 +--- /dev/null ++++ b/drivers/hid/ipts/eds2.c @@ -0,0 +1,144 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* @@ -1808,9 +2088,11 @@ diff '--color=auto' -uraN cachyos/drivers/hid/ipts/eds2.c cachyos-surface/driver + else + return ipts_eds2_set_feature(ipts, buffer, size, report_id, feedback_type); +} -diff '--color=auto' -uraN cachyos/drivers/hid/ipts/eds2.h cachyos-surface/drivers/hid/ipts/eds2.h ---- cachyos/drivers/hid/ipts/eds2.h 1970-01-01 03:00:00.000000000 +0300 -+++ cachyos-surface/drivers/hid/ipts/eds2.h 2023-11-04 18:32:41.037654516 +0300 +diff --git a/drivers/hid/ipts/eds2.h b/drivers/hid/ipts/eds2.h +new file mode 100644 +index 0000000000000..064e3716907ab +--- /dev/null ++++ b/drivers/hid/ipts/eds2.h @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* @@ -1847,9 +2129,11 @@ diff '--color=auto' -uraN cachyos/drivers/hid/ipts/eds2.h cachyos-surface/driver + */ +int ipts_eds2_raw_request(struct ipts_context *ipts, u8 *buffer, size_t size, u8 report_id, + enum hid_report_type report_type, enum hid_class_request request_type); -diff '--color=auto' -uraN cachyos/drivers/hid/ipts/hid.c cachyos-surface/drivers/hid/ipts/hid.c ---- cachyos/drivers/hid/ipts/hid.c 1970-01-01 03:00:00.000000000 +0300 -+++ cachyos-surface/drivers/hid/ipts/hid.c 2023-11-04 18:32:41.037654516 +0300 +diff --git a/drivers/hid/ipts/hid.c b/drivers/hid/ipts/hid.c +new file mode 100644 +index 0000000000000..e34a1a4f9fa77 +--- /dev/null ++++ b/drivers/hid/ipts/hid.c @@ -0,0 +1,225 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* @@ -2076,9 +2360,11 @@ diff '--color=auto' -uraN cachyos/drivers/hid/ipts/hid.c cachyos-surface/drivers + + return 0; +} -diff '--color=auto' -uraN cachyos/drivers/hid/ipts/hid.h cachyos-surface/drivers/hid/ipts/hid.h ---- cachyos/drivers/hid/ipts/hid.h 1970-01-01 03:00:00.000000000 +0300 -+++ cachyos-surface/drivers/hid/ipts/hid.h 2023-11-04 18:32:41.040987898 +0300 +diff --git a/drivers/hid/ipts/hid.h b/drivers/hid/ipts/hid.h +new file mode 100644 +index 0000000000000..1ebe77447903a +--- /dev/null ++++ b/drivers/hid/ipts/hid.h @@ -0,0 +1,24 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* @@ -2104,9 +2390,11 @@ diff '--color=auto' -uraN cachyos/drivers/hid/ipts/hid.h cachyos-surface/drivers +int ipts_hid_free(struct ipts_context *ipts); + +#endif /* IPTS_HID_H */ -diff '--color=auto' -uraN cachyos/drivers/hid/ipts/main.c cachyos-surface/drivers/hid/ipts/main.c ---- cachyos/drivers/hid/ipts/main.c 1970-01-01 03:00:00.000000000 +0300 -+++ cachyos-surface/drivers/hid/ipts/main.c 2023-11-04 18:32:41.040987898 +0300 +diff --git a/drivers/hid/ipts/main.c b/drivers/hid/ipts/main.c +new file mode 100644 +index 0000000000000..fb5b5c13ee3ea +--- /dev/null ++++ b/drivers/hid/ipts/main.c @@ -0,0 +1,126 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* @@ -2234,9 +2522,11 @@ diff '--color=auto' -uraN cachyos/drivers/hid/ipts/main.c cachyos-surface/driver +MODULE_DESCRIPTION("IPTS touchscreen driver"); +MODULE_AUTHOR("Dorian Stoll "); +MODULE_LICENSE("GPL"); -diff '--color=auto' -uraN cachyos/drivers/hid/ipts/mei.c cachyos-surface/drivers/hid/ipts/mei.c ---- cachyos/drivers/hid/ipts/mei.c 1970-01-01 03:00:00.000000000 +0300 -+++ cachyos-surface/drivers/hid/ipts/mei.c 2023-11-04 18:32:41.040987898 +0300 +diff --git a/drivers/hid/ipts/mei.c b/drivers/hid/ipts/mei.c +new file mode 100644 +index 0000000000000..1e0395ceae4a4 +--- /dev/null ++++ b/drivers/hid/ipts/mei.c @@ -0,0 +1,188 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* @@ -2426,9 +2716,11 @@ diff '--color=auto' -uraN cachyos/drivers/hid/ipts/mei.c cachyos-surface/drivers + + return 0; +} -diff '--color=auto' -uraN cachyos/drivers/hid/ipts/mei.h cachyos-surface/drivers/hid/ipts/mei.h ---- cachyos/drivers/hid/ipts/mei.h 1970-01-01 03:00:00.000000000 +0300 -+++ cachyos-surface/drivers/hid/ipts/mei.h 2023-11-04 18:32:41.040987898 +0300 +diff --git a/drivers/hid/ipts/mei.h b/drivers/hid/ipts/mei.h +new file mode 100644 +index 0000000000000..973bade6b0fdd +--- /dev/null ++++ b/drivers/hid/ipts/mei.h @@ -0,0 +1,66 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* @@ -2496,9 +2788,11 @@ diff '--color=auto' -uraN cachyos/drivers/hid/ipts/mei.h cachyos-surface/drivers +int ipts_mei_init(struct ipts_mei *mei, struct mei_cl_device *cldev); + +#endif /* IPTS_MEI_H */ -diff '--color=auto' -uraN cachyos/drivers/hid/ipts/receiver.c cachyos-surface/drivers/hid/ipts/receiver.c ---- cachyos/drivers/hid/ipts/receiver.c 1970-01-01 03:00:00.000000000 +0300 -+++ cachyos-surface/drivers/hid/ipts/receiver.c 2023-11-04 18:32:41.040987898 +0300 +diff --git a/drivers/hid/ipts/receiver.c b/drivers/hid/ipts/receiver.c +new file mode 100644 +index 0000000000000..ef66c3c9db807 +--- /dev/null ++++ b/drivers/hid/ipts/receiver.c @@ -0,0 +1,250 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* @@ -2750,9 +3044,11 @@ diff '--color=auto' -uraN cachyos/drivers/hid/ipts/receiver.c cachyos-surface/dr + + return 0; +} -diff '--color=auto' -uraN cachyos/drivers/hid/ipts/receiver.h cachyos-surface/drivers/hid/ipts/receiver.h ---- cachyos/drivers/hid/ipts/receiver.h 1970-01-01 03:00:00.000000000 +0300 -+++ cachyos-surface/drivers/hid/ipts/receiver.h 2023-11-04 18:32:41.040987898 +0300 +diff --git a/drivers/hid/ipts/receiver.h b/drivers/hid/ipts/receiver.h +new file mode 100644 +index 0000000000000..3de7da62d40c1 +--- /dev/null ++++ b/drivers/hid/ipts/receiver.h @@ -0,0 +1,16 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* @@ -2770,9 +3066,11 @@ diff '--color=auto' -uraN cachyos/drivers/hid/ipts/receiver.h cachyos-surface/dr +int ipts_receiver_stop(struct ipts_context *ipts); + +#endif /* IPTS_RECEIVER_H */ -diff '--color=auto' -uraN cachyos/drivers/hid/ipts/resources.c cachyos-surface/drivers/hid/ipts/resources.c ---- cachyos/drivers/hid/ipts/resources.c 1970-01-01 03:00:00.000000000 +0300 -+++ cachyos-surface/drivers/hid/ipts/resources.c 2023-11-04 18:32:41.040987898 +0300 +diff --git a/drivers/hid/ipts/resources.c b/drivers/hid/ipts/resources.c +new file mode 100644 +index 0000000000000..cc14653b2a9f5 +--- /dev/null ++++ b/drivers/hid/ipts/resources.c @@ -0,0 +1,131 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* @@ -2905,9 +3203,11 @@ diff '--color=auto' -uraN cachyos/drivers/hid/ipts/resources.c cachyos-surface/d + + return 0; +} -diff '--color=auto' -uraN cachyos/drivers/hid/ipts/resources.h cachyos-surface/drivers/hid/ipts/resources.h ---- cachyos/drivers/hid/ipts/resources.h 1970-01-01 03:00:00.000000000 +0300 -+++ cachyos-surface/drivers/hid/ipts/resources.h 2023-11-04 18:32:41.040987898 +0300 +diff --git a/drivers/hid/ipts/resources.h b/drivers/hid/ipts/resources.h +new file mode 100644 +index 0000000000000..2068e13285f0e +--- /dev/null ++++ b/drivers/hid/ipts/resources.h @@ -0,0 +1,41 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* @@ -2950,9 +3250,11 @@ diff '--color=auto' -uraN cachyos/drivers/hid/ipts/resources.h cachyos-surface/d +int ipts_resources_free(struct ipts_resources *res); + +#endif /* IPTS_RESOURCES_H */ -diff '--color=auto' -uraN cachyos/drivers/hid/ipts/spec-data.h cachyos-surface/drivers/hid/ipts/spec-data.h ---- cachyos/drivers/hid/ipts/spec-data.h 1970-01-01 03:00:00.000000000 +0300 -+++ cachyos-surface/drivers/hid/ipts/spec-data.h 2023-11-04 18:32:41.040987898 +0300 +diff --git a/drivers/hid/ipts/spec-data.h b/drivers/hid/ipts/spec-data.h +new file mode 100644 +index 0000000000000..e8dd98895a7ee +--- /dev/null ++++ b/drivers/hid/ipts/spec-data.h @@ -0,0 +1,100 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* @@ -3054,9 +3356,11 @@ diff '--color=auto' -uraN cachyos/drivers/hid/ipts/spec-data.h cachyos-surface/d +static_assert(sizeof(struct ipts_data_header) == 64); + +#endif /* IPTS_SPEC_DATA_H */ -diff '--color=auto' -uraN cachyos/drivers/hid/ipts/spec-device.h cachyos-surface/drivers/hid/ipts/spec-device.h ---- cachyos/drivers/hid/ipts/spec-device.h 1970-01-01 03:00:00.000000000 +0300 -+++ cachyos-surface/drivers/hid/ipts/spec-device.h 2023-11-04 18:32:41.040987898 +0300 +diff --git a/drivers/hid/ipts/spec-device.h b/drivers/hid/ipts/spec-device.h +new file mode 100644 +index 0000000000000..41845f9d90257 +--- /dev/null ++++ b/drivers/hid/ipts/spec-device.h @@ -0,0 +1,290 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* @@ -3348,9 +3652,11 @@ diff '--color=auto' -uraN cachyos/drivers/hid/ipts/spec-device.h cachyos-surface +static_assert(sizeof(struct ipts_device_info) == 44); + +#endif /* IPTS_SPEC_DEVICE_H */ -diff '--color=auto' -uraN cachyos/drivers/hid/ipts/spec-hid.h cachyos-surface/drivers/hid/ipts/spec-hid.h ---- cachyos/drivers/hid/ipts/spec-hid.h 1970-01-01 03:00:00.000000000 +0300 -+++ cachyos-surface/drivers/hid/ipts/spec-hid.h 2023-11-04 18:32:41.040987898 +0300 +diff --git a/drivers/hid/ipts/spec-hid.h b/drivers/hid/ipts/spec-hid.h +new file mode 100644 +index 0000000000000..5a58d4a0a610f +--- /dev/null ++++ b/drivers/hid/ipts/spec-hid.h @@ -0,0 +1,34 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* @@ -3386,9 +3692,11 @@ diff '--color=auto' -uraN cachyos/drivers/hid/ipts/spec-hid.h cachyos-surface/dr +static_assert(sizeof(struct ipts_hid_header) == 7); + +#endif /* IPTS_SPEC_HID_H */ -diff '--color=auto' -uraN cachyos/drivers/hid/ipts/thread.c cachyos-surface/drivers/hid/ipts/thread.c ---- cachyos/drivers/hid/ipts/thread.c 1970-01-01 03:00:00.000000000 +0300 -+++ cachyos-surface/drivers/hid/ipts/thread.c 2023-11-04 18:32:41.040987898 +0300 +diff --git a/drivers/hid/ipts/thread.c b/drivers/hid/ipts/thread.c +new file mode 100644 +index 0000000000000..355e92bea26f8 +--- /dev/null ++++ b/drivers/hid/ipts/thread.c @@ -0,0 +1,84 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* @@ -3474,9 +3782,11 @@ diff '--color=auto' -uraN cachyos/drivers/hid/ipts/thread.c cachyos-surface/driv + + return ret; +} -diff '--color=auto' -uraN cachyos/drivers/hid/ipts/thread.h cachyos-surface/drivers/hid/ipts/thread.h ---- cachyos/drivers/hid/ipts/thread.h 1970-01-01 03:00:00.000000000 +0300 -+++ cachyos-surface/drivers/hid/ipts/thread.h 2023-11-04 18:32:41.040987898 +0300 +diff --git a/drivers/hid/ipts/thread.h b/drivers/hid/ipts/thread.h +new file mode 100644 +index 0000000000000..1f966b8b32c45 +--- /dev/null ++++ b/drivers/hid/ipts/thread.h @@ -0,0 +1,59 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* @@ -3537,9 +3847,107 @@ diff '--color=auto' -uraN cachyos/drivers/hid/ipts/thread.h cachyos-surface/driv +int ipts_thread_stop(struct ipts_thread *thread); + +#endif /* IPTS_THREAD_H */ -diff '--color=auto' -uraN cachyos/drivers/hid/ithc/Kbuild cachyos-surface/drivers/hid/ithc/Kbuild ---- cachyos/drivers/hid/ithc/Kbuild 1970-01-01 03:00:00.000000000 +0300 -+++ cachyos-surface/drivers/hid/ithc/Kbuild 2023-11-04 18:32:41.040987898 +0300 +-- +2.42.0 + +From 033de13abc9653b2d773f06182465e03d5d6463b Mon Sep 17 00:00:00 2001 +From: Dorian Stoll +Date: Sun, 11 Dec 2022 12:03:38 +0100 +Subject: [PATCH] iommu: intel: Disable source id verification for ITHC + +Signed-off-by: Dorian Stoll +Patchset: ithc +--- + drivers/iommu/intel/irq_remapping.c | 16 ++++++++++++++++ + 1 file changed, 16 insertions(+) + +diff --git a/drivers/iommu/intel/irq_remapping.c b/drivers/iommu/intel/irq_remapping.c +index 29b9e55dcf26c..986e91c813ae1 100644 +--- a/drivers/iommu/intel/irq_remapping.c ++++ b/drivers/iommu/intel/irq_remapping.c +@@ -386,6 +386,22 @@ static int set_msi_sid(struct irte *irte, struct pci_dev *dev) + data.busmatch_count = 0; + pci_for_each_dma_alias(dev, set_msi_sid_cb, &data); + ++ /* ++ * The Intel Touch Host Controller is at 00:10.6, but for some reason ++ * the MSI interrupts have request id 01:05.0. ++ * Disable id verification to work around this. ++ * FIXME Find proper fix or turn this into a quirk. ++ */ ++ if (dev->vendor == PCI_VENDOR_ID_INTEL && (dev->class >> 8) == PCI_CLASS_INPUT_PEN) { ++ switch(dev->device) { ++ case 0x98d0: case 0x98d1: // LKF ++ case 0xa0d0: case 0xa0d1: // TGL LP ++ case 0x43d0: case 0x43d1: // TGL H ++ set_irte_sid(irte, SVT_NO_VERIFY, SQ_ALL_16, 0); ++ return 0; ++ } ++ } ++ + /* + * DMA alias provides us with a PCI device and alias. The only case + * where the it will return an alias on a different bus than the +-- +2.42.0 + +From 0dd32bcfb70f9e36cfa009d94cd6c86a4839cff3 Mon Sep 17 00:00:00 2001 +From: Dorian Stoll +Date: Sun, 11 Dec 2022 12:10:54 +0100 +Subject: [PATCH] hid: Add support for Intel Touch Host Controller + +Based on quo/ithc-linux@55803a2 + +Signed-off-by: Dorian Stoll +Patchset: ithc +--- + drivers/hid/Kconfig | 2 + + drivers/hid/Makefile | 1 + + drivers/hid/ithc/Kbuild | 6 + + drivers/hid/ithc/Kconfig | 12 + + drivers/hid/ithc/ithc-debug.c | 96 ++++++ + drivers/hid/ithc/ithc-dma.c | 258 ++++++++++++++++ + drivers/hid/ithc/ithc-dma.h | 67 +++++ + drivers/hid/ithc/ithc-main.c | 534 ++++++++++++++++++++++++++++++++++ + drivers/hid/ithc/ithc-regs.c | 64 ++++ + drivers/hid/ithc/ithc-regs.h | 186 ++++++++++++ + drivers/hid/ithc/ithc.h | 60 ++++ + 11 files changed, 1286 insertions(+) + create mode 100644 drivers/hid/ithc/Kbuild + create mode 100644 drivers/hid/ithc/Kconfig + create mode 100644 drivers/hid/ithc/ithc-debug.c + create mode 100644 drivers/hid/ithc/ithc-dma.c + create mode 100644 drivers/hid/ithc/ithc-dma.h + create mode 100644 drivers/hid/ithc/ithc-main.c + create mode 100644 drivers/hid/ithc/ithc-regs.c + create mode 100644 drivers/hid/ithc/ithc-regs.h + create mode 100644 drivers/hid/ithc/ithc.h + +diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig +index 0b9d245d10e54..8ba1c309228be 100644 +--- a/drivers/hid/Kconfig ++++ b/drivers/hid/Kconfig +@@ -1347,4 +1347,6 @@ source "drivers/hid/surface-hid/Kconfig" + + source "drivers/hid/ipts/Kconfig" + ++source "drivers/hid/ithc/Kconfig" ++ + endif # HID_SUPPORT +diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile +index 2ef21b257d0b5..e94b79727b489 100644 +--- a/drivers/hid/Makefile ++++ b/drivers/hid/Makefile +@@ -171,3 +171,4 @@ obj-$(CONFIG_AMD_SFH_HID) += amd-sfh-hid/ + obj-$(CONFIG_SURFACE_HID_CORE) += surface-hid/ + + obj-$(CONFIG_HID_IPTS) += ipts/ ++obj-$(CONFIG_HID_ITHC) += ithc/ +diff --git a/drivers/hid/ithc/Kbuild b/drivers/hid/ithc/Kbuild +new file mode 100644 +index 0000000000000..aea83f2ac07b4 +--- /dev/null ++++ b/drivers/hid/ithc/Kbuild @@ -0,0 +1,6 @@ +obj-$(CONFIG_HID_ITHC) := ithc.o + @@ -3547,9 +3955,11 @@ diff '--color=auto' -uraN cachyos/drivers/hid/ithc/Kbuild cachyos-surface/driver + +ccflags-y := -std=gnu11 -Wno-declaration-after-statement + -diff '--color=auto' -uraN cachyos/drivers/hid/ithc/Kconfig cachyos-surface/drivers/hid/ithc/Kconfig ---- cachyos/drivers/hid/ithc/Kconfig 1970-01-01 03:00:00.000000000 +0300 -+++ cachyos-surface/drivers/hid/ithc/Kconfig 2023-11-04 18:32:41.040987898 +0300 +diff --git a/drivers/hid/ithc/Kconfig b/drivers/hid/ithc/Kconfig +new file mode 100644 +index 0000000000000..ede7130236096 +--- /dev/null ++++ b/drivers/hid/ithc/Kconfig @@ -0,0 +1,12 @@ +config HID_ITHC + tristate "Intel Touch Host Controller" @@ -3563,9 +3973,11 @@ diff '--color=auto' -uraN cachyos/drivers/hid/ithc/Kconfig cachyos-surface/drive + + To compile this driver as a module, choose M here: the + module will be called ithc. -diff '--color=auto' -uraN cachyos/drivers/hid/ithc/ithc-debug.c cachyos-surface/drivers/hid/ithc/ithc-debug.c ---- cachyos/drivers/hid/ithc/ithc-debug.c 1970-01-01 03:00:00.000000000 +0300 -+++ cachyos-surface/drivers/hid/ithc/ithc-debug.c 2023-11-04 18:32:41.040987898 +0300 +diff --git a/drivers/hid/ithc/ithc-debug.c b/drivers/hid/ithc/ithc-debug.c +new file mode 100644 +index 0000000000000..57bf125c45bd5 +--- /dev/null ++++ b/drivers/hid/ithc/ithc-debug.c @@ -0,0 +1,96 @@ +#include "ithc.h" + @@ -3663,9 +4075,11 @@ diff '--color=auto' -uraN cachyos/drivers/hid/ithc/ithc-debug.c cachyos-surface/ + return 0; +} + -diff '--color=auto' -uraN cachyos/drivers/hid/ithc/ithc-dma.c cachyos-surface/drivers/hid/ithc/ithc-dma.c ---- cachyos/drivers/hid/ithc/ithc-dma.c 1970-01-01 03:00:00.000000000 +0300 -+++ cachyos-surface/drivers/hid/ithc/ithc-dma.c 2023-11-04 18:32:41.040987898 +0300 +diff --git a/drivers/hid/ithc/ithc-dma.c b/drivers/hid/ithc/ithc-dma.c +new file mode 100644 +index 0000000000000..7e89b3496918d +--- /dev/null ++++ b/drivers/hid/ithc/ithc-dma.c @@ -0,0 +1,258 @@ +#include "ithc.h" + @@ -3925,9 +4339,11 @@ diff '--color=auto' -uraN cachyos/drivers/hid/ithc/ithc-dma.c cachyos-surface/dr + return ret; +} + -diff '--color=auto' -uraN cachyos/drivers/hid/ithc/ithc-dma.h cachyos-surface/drivers/hid/ithc/ithc-dma.h ---- cachyos/drivers/hid/ithc/ithc-dma.h 1970-01-01 03:00:00.000000000 +0300 -+++ cachyos-surface/drivers/hid/ithc/ithc-dma.h 2023-11-04 18:32:41.040987898 +0300 +diff --git a/drivers/hid/ithc/ithc-dma.h b/drivers/hid/ithc/ithc-dma.h +new file mode 100644 +index 0000000000000..d9f2c19a13f3a +--- /dev/null ++++ b/drivers/hid/ithc/ithc-dma.h @@ -0,0 +1,67 @@ +#define PRD_SIZE_MASK 0xffffff +#define PRD_FLAG_END 0x1000000 @@ -3996,9 +4412,11 @@ diff '--color=auto' -uraN cachyos/drivers/hid/ithc/ithc-dma.h cachyos-surface/dr +int ithc_dma_rx(struct ithc *ithc, u8 channel); +int ithc_dma_tx(struct ithc *ithc, u32 cmdcode, u32 datasize, void *cmddata); + -diff '--color=auto' -uraN cachyos/drivers/hid/ithc/ithc-main.c cachyos-surface/drivers/hid/ithc/ithc-main.c ---- cachyos/drivers/hid/ithc/ithc-main.c 1970-01-01 03:00:00.000000000 +0300 -+++ cachyos-surface/drivers/hid/ithc/ithc-main.c 2023-11-04 18:32:41.040987898 +0300 +diff --git a/drivers/hid/ithc/ithc-main.c b/drivers/hid/ithc/ithc-main.c +new file mode 100644 +index 0000000000000..09512b9cb4d31 +--- /dev/null ++++ b/drivers/hid/ithc/ithc-main.c @@ -0,0 +1,534 @@ +#include "ithc.h" + @@ -4534,9 +4952,11 @@ diff '--color=auto' -uraN cachyos/drivers/hid/ithc/ithc-main.c cachyos-surface/d +module_init(ithc_init); +module_exit(ithc_exit); + -diff '--color=auto' -uraN cachyos/drivers/hid/ithc/ithc-regs.c cachyos-surface/drivers/hid/ithc/ithc-regs.c ---- cachyos/drivers/hid/ithc/ithc-regs.c 1970-01-01 03:00:00.000000000 +0300 -+++ cachyos-surface/drivers/hid/ithc/ithc-regs.c 2023-11-04 18:32:41.040987898 +0300 +diff --git a/drivers/hid/ithc/ithc-regs.c b/drivers/hid/ithc/ithc-regs.c +new file mode 100644 +index 0000000000000..85d567b05761f +--- /dev/null ++++ b/drivers/hid/ithc/ithc-regs.c @@ -0,0 +1,64 @@ +#include "ithc.h" + @@ -4602,9 +5022,11 @@ diff '--color=auto' -uraN cachyos/drivers/hid/ithc/ithc-regs.c cachyos-surface/d + return 0; +} + -diff '--color=auto' -uraN cachyos/drivers/hid/ithc/ithc-regs.h cachyos-surface/drivers/hid/ithc/ithc-regs.h ---- cachyos/drivers/hid/ithc/ithc-regs.h 1970-01-01 03:00:00.000000000 +0300 -+++ cachyos-surface/drivers/hid/ithc/ithc-regs.h 2023-11-04 18:32:41.040987898 +0300 +diff --git a/drivers/hid/ithc/ithc-regs.h b/drivers/hid/ithc/ithc-regs.h +new file mode 100644 +index 0000000000000..1a96092ed7eed +--- /dev/null ++++ b/drivers/hid/ithc/ithc-regs.h @@ -0,0 +1,186 @@ +#define CONTROL_QUIESCE BIT(1) +#define CONTROL_IS_QUIESCED BIT(2) @@ -4792,9 +5214,11 @@ diff '--color=auto' -uraN cachyos/drivers/hid/ithc/ithc-regs.h cachyos-surface/d +int ithc_set_spi_config(struct ithc *ithc, u8 speed, u8 mode); +int ithc_spi_command(struct ithc *ithc, u8 command, u32 offset, u32 size, void *data); + -diff '--color=auto' -uraN cachyos/drivers/hid/ithc/ithc.h cachyos-surface/drivers/hid/ithc/ithc.h ---- cachyos/drivers/hid/ithc/ithc.h 1970-01-01 03:00:00.000000000 +0300 -+++ cachyos-surface/drivers/hid/ithc/ithc.h 2023-11-04 18:32:41.040987898 +0300 +diff --git a/drivers/hid/ithc/ithc.h b/drivers/hid/ithc/ithc.h +new file mode 100644 +index 0000000000000..6a9b0d480bc15 +--- /dev/null ++++ b/drivers/hid/ithc/ithc.h @@ -0,0 +1,60 @@ +#include +#include @@ -4856,10 +5280,1764 @@ diff '--color=auto' -uraN cachyos/drivers/hid/ithc/ithc.h cachyos-surface/driver +int ithc_debug_init(struct ithc *ithc); +void ithc_log_regs(struct ithc *ithc); + -diff '--color=auto' -uraN cachyos/drivers/i2c/i2c-core-acpi.c cachyos-surface/drivers/i2c/i2c-core-acpi.c ---- cachyos/drivers/i2c/i2c-core-acpi.c 2023-10-30 05:31:08.000000000 +0300 -+++ cachyos-surface/drivers/i2c/i2c-core-acpi.c 2023-11-04 18:32:41.040987898 +0300 -@@ -628,6 +628,28 @@ +-- +2.42.0 + +From 9f8d2a0f4012644f56ed8dfd322e575b57e1c208 Mon Sep 17 00:00:00 2001 +From: quo +Date: Mon, 23 Oct 2023 10:15:29 +0200 +Subject: [PATCH] Update ITHC from module repo + +Changes: + - Added some comments and fixed a few checkpatch warnings + - Improved CPU latency QoS handling + - Retry reading the report descriptor on error / timeout + +Based on https://github.com/quo/ithc-linux/commit/0b8b45d9775e756d6bd3a699bfaf9f5bd7b9b10b + +Signed-off-by: Dorian Stoll +Patchset: ithc +--- + drivers/hid/ithc/ithc-debug.c | 94 +++++--- + drivers/hid/ithc/ithc-dma.c | 231 +++++++++++++----- + drivers/hid/ithc/ithc-dma.h | 4 +- + drivers/hid/ithc/ithc-main.c | 430 ++++++++++++++++++++++++---------- + drivers/hid/ithc/ithc-regs.c | 68 ++++-- + drivers/hid/ithc/ithc-regs.h | 19 +- + drivers/hid/ithc/ithc.h | 13 +- + 7 files changed, 623 insertions(+), 236 deletions(-) + +diff --git a/drivers/hid/ithc/ithc-debug.c b/drivers/hid/ithc/ithc-debug.c +index 57bf125c45bd5..1f1f1e33f2e5a 100644 +--- a/drivers/hid/ithc/ithc-debug.c ++++ b/drivers/hid/ithc/ithc-debug.c +@@ -1,10 +1,14 @@ ++// SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause ++ + #include "ithc.h" + +-void ithc_log_regs(struct ithc *ithc) { +- if (!ithc->prev_regs) return; +- u32 __iomem *cur = (__iomem void*)ithc->regs; +- u32 *prev = (void*)ithc->prev_regs; +- for (int i = 1024; i < sizeof *ithc->regs / 4; i++) { ++void ithc_log_regs(struct ithc *ithc) ++{ ++ if (!ithc->prev_regs) ++ return; ++ u32 __iomem *cur = (__iomem void *)ithc->regs; ++ u32 *prev = (void *)ithc->prev_regs; ++ for (int i = 1024; i < sizeof(*ithc->regs) / 4; i++) { + u32 x = readl(cur + i); + if (x != prev[i]) { + pci_info(ithc->pci, "reg %04x: %08x -> %08x\n", i * 4, prev[i], x); +@@ -13,55 +17,79 @@ void ithc_log_regs(struct ithc *ithc) { + } + } + +-static ssize_t ithc_debugfs_cmd_write(struct file *f, const char __user *buf, size_t len, loff_t *offset) { ++static ssize_t ithc_debugfs_cmd_write(struct file *f, const char __user *buf, size_t len, ++ loff_t *offset) ++{ ++ // Debug commands consist of a single letter followed by a list of numbers (decimal or ++ // hexadecimal, space-separated). + struct ithc *ithc = file_inode(f)->i_private; + char cmd[256]; +- if (!ithc || !ithc->pci) return -ENODEV; +- if (!len) return -EINVAL; +- if (len >= sizeof cmd) return -EINVAL; +- if (copy_from_user(cmd, buf, len)) return -EFAULT; ++ if (!ithc || !ithc->pci) ++ return -ENODEV; ++ if (!len) ++ return -EINVAL; ++ if (len >= sizeof(cmd)) ++ return -EINVAL; ++ if (copy_from_user(cmd, buf, len)) ++ return -EFAULT; + cmd[len] = 0; +- if (cmd[len-1] == '\n') cmd[len-1] = 0; ++ if (cmd[len-1] == '\n') ++ cmd[len-1] = 0; + pci_info(ithc->pci, "debug command: %s\n", cmd); ++ ++ // Parse the list of arguments into a u32 array. + u32 n = 0; + const char *s = cmd + 1; + u32 a[32]; + while (*s && *s != '\n') { +- if (n >= ARRAY_SIZE(a)) return -EINVAL; +- if (*s++ != ' ') return -EINVAL; ++ if (n >= ARRAY_SIZE(a)) ++ return -EINVAL; ++ if (*s++ != ' ') ++ return -EINVAL; + char *e; + a[n++] = simple_strtoul(s, &e, 0); +- if (e == s) return -EINVAL; ++ if (e == s) ++ return -EINVAL; + s = e; + } + ithc_log_regs(ithc); +- switch(cmd[0]) { ++ ++ // Execute the command. ++ switch (cmd[0]) { + case 'x': // reset + ithc_reset(ithc); + break; + case 'w': // write register: offset mask value +- if (n != 3 || (a[0] & 3)) return -EINVAL; +- pci_info(ithc->pci, "debug write 0x%04x = 0x%08x (mask 0x%08x)\n", a[0], a[2], a[1]); ++ if (n != 3 || (a[0] & 3)) ++ return -EINVAL; ++ pci_info(ithc->pci, "debug write 0x%04x = 0x%08x (mask 0x%08x)\n", ++ a[0], a[2], a[1]); + bitsl(((__iomem u32 *)ithc->regs) + a[0] / 4, a[1], a[2]); + break; + case 'r': // read register: offset +- if (n != 1 || (a[0] & 3)) return -EINVAL; +- pci_info(ithc->pci, "debug read 0x%04x = 0x%08x\n", a[0], readl(((__iomem u32 *)ithc->regs) + a[0] / 4)); ++ if (n != 1 || (a[0] & 3)) ++ return -EINVAL; ++ pci_info(ithc->pci, "debug read 0x%04x = 0x%08x\n", a[0], ++ readl(((__iomem u32 *)ithc->regs) + a[0] / 4)); + break; + case 's': // spi command: cmd offset len data... + // read config: s 4 0 64 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 + // set touch cfg: s 6 12 4 XX +- if (n < 3 || a[2] > (n - 3) * 4) return -EINVAL; ++ if (n < 3 || a[2] > (n - 3) * 4) ++ return -EINVAL; + pci_info(ithc->pci, "debug spi command %u with %u bytes of data\n", a[0], a[2]); + if (!CHECK(ithc_spi_command, ithc, a[0], a[1], a[2], a + 3)) +- for (u32 i = 0; i < (a[2] + 3) / 4; i++) pci_info(ithc->pci, "resp %u = 0x%08x\n", i, a[3+i]); ++ for (u32 i = 0; i < (a[2] + 3) / 4; i++) ++ pci_info(ithc->pci, "resp %u = 0x%08x\n", i, a[3+i]); + break; + case 'd': // dma command: cmd len data... + // get report descriptor: d 7 8 0 0 + // enable multitouch: d 3 2 0x0105 +- if (n < 2 || a[1] > (n - 2) * 4) return -EINVAL; ++ if (n < 2 || a[1] > (n - 2) * 4) ++ return -EINVAL; + pci_info(ithc->pci, "debug dma command %u with %u bytes of data\n", a[0], a[1]); +- if (ithc_dma_tx(ithc, a[0], a[1], a + 2)) pci_err(ithc->pci, "dma tx failed\n"); ++ if (ithc_dma_tx(ithc, a[0], a[1], a + 2)) ++ pci_err(ithc->pci, "dma tx failed\n"); + break; + default: + return -EINVAL; +@@ -75,21 +103,27 @@ static const struct file_operations ithc_debugfops_cmd = { + .write = ithc_debugfs_cmd_write, + }; + +-static void ithc_debugfs_devres_release(struct device *dev, void *res) { ++static void ithc_debugfs_devres_release(struct device *dev, void *res) ++{ + struct dentry **dbgm = res; +- if (*dbgm) debugfs_remove_recursive(*dbgm); ++ if (*dbgm) ++ debugfs_remove_recursive(*dbgm); + } + +-int ithc_debug_init(struct ithc *ithc) { +- struct dentry **dbgm = devres_alloc(ithc_debugfs_devres_release, sizeof *dbgm, GFP_KERNEL); +- if (!dbgm) return -ENOMEM; ++int ithc_debug_init(struct ithc *ithc) ++{ ++ struct dentry **dbgm = devres_alloc(ithc_debugfs_devres_release, sizeof(*dbgm), GFP_KERNEL); ++ if (!dbgm) ++ return -ENOMEM; + devres_add(&ithc->pci->dev, dbgm); + struct dentry *dbg = debugfs_create_dir(DEVNAME, NULL); +- if (IS_ERR(dbg)) return PTR_ERR(dbg); ++ if (IS_ERR(dbg)) ++ return PTR_ERR(dbg); + *dbgm = dbg; + + struct dentry *cmd = debugfs_create_file("cmd", 0220, dbg, ithc, &ithc_debugfops_cmd); +- if (IS_ERR(cmd)) return PTR_ERR(cmd); ++ if (IS_ERR(cmd)) ++ return PTR_ERR(cmd); + + return 0; + } +diff --git a/drivers/hid/ithc/ithc-dma.c b/drivers/hid/ithc/ithc-dma.c +index 7e89b3496918d..ffb8689b8a780 100644 +--- a/drivers/hid/ithc/ithc-dma.c ++++ b/drivers/hid/ithc/ithc-dma.c +@@ -1,59 +1,91 @@ ++// SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause ++ + #include "ithc.h" + +-static int ithc_dma_prd_alloc(struct ithc *ithc, struct ithc_dma_prd_buffer *p, unsigned num_buffers, unsigned num_pages, enum dma_data_direction dir) { ++// The THC uses tables of PRDs (physical region descriptors) to describe the TX and RX data buffers. ++// Each PRD contains the DMA address and size of a block of DMA memory, and some status flags. ++// This allows each data buffer to consist of multiple non-contiguous blocks of memory. ++ ++static int ithc_dma_prd_alloc(struct ithc *ithc, struct ithc_dma_prd_buffer *p, ++ unsigned int num_buffers, unsigned int num_pages, enum dma_data_direction dir) ++{ + p->num_pages = num_pages; + p->dir = dir; ++ // We allocate enough space to have one PRD per data buffer page, however if the data ++ // buffer pages happen to be contiguous, we can describe the buffer using fewer PRDs, so ++ // some will remain unused (which is fine). + p->size = round_up(num_buffers * num_pages * sizeof(struct ithc_phys_region_desc), PAGE_SIZE); + p->addr = dmam_alloc_coherent(&ithc->pci->dev, p->size, &p->dma_addr, GFP_KERNEL); +- if (!p->addr) return -ENOMEM; +- if (p->dma_addr & (PAGE_SIZE - 1)) return -EFAULT; ++ if (!p->addr) ++ return -ENOMEM; ++ if (p->dma_addr & (PAGE_SIZE - 1)) ++ return -EFAULT; + return 0; + } + ++// Devres managed sg_table wrapper. + struct ithc_sg_table { + void *addr; + struct sg_table sgt; + enum dma_data_direction dir; + }; +-static void ithc_dma_sgtable_free(struct sg_table *sgt) { ++static void ithc_dma_sgtable_free(struct sg_table *sgt) ++{ + struct scatterlist *sg; + int i; + for_each_sgtable_sg(sgt, sg, i) { + struct page *p = sg_page(sg); +- if (p) __free_page(p); ++ if (p) ++ __free_page(p); + } + sg_free_table(sgt); + } +-static void ithc_dma_data_devres_release(struct device *dev, void *res) { ++static void ithc_dma_data_devres_release(struct device *dev, void *res) ++{ + struct ithc_sg_table *sgt = res; +- if (sgt->addr) vunmap(sgt->addr); ++ if (sgt->addr) ++ vunmap(sgt->addr); + dma_unmap_sgtable(dev, &sgt->sgt, sgt->dir, 0); + ithc_dma_sgtable_free(&sgt->sgt); + } + +-static int ithc_dma_data_alloc(struct ithc* ithc, struct ithc_dma_prd_buffer *prds, struct ithc_dma_data_buffer *b) { +- // We don't use dma_alloc_coherent for data buffers, because they don't have to be contiguous (we can use one PRD per page) or coherent (they are unidirectional). +- // Instead we use an sg_table of individually allocated pages (5.13 has dma_alloc_noncontiguous for this, but we'd like to support 5.10 for now). ++static int ithc_dma_data_alloc(struct ithc *ithc, struct ithc_dma_prd_buffer *prds, ++ struct ithc_dma_data_buffer *b) ++{ ++ // We don't use dma_alloc_coherent() for data buffers, because they don't have to be ++ // coherent (they are unidirectional) or contiguous (we can use one PRD per page). ++ // We could use dma_alloc_noncontiguous(), however this still always allocates a single ++ // DMA mapped segment, which is more restrictive than what we need. ++ // Instead we use an sg_table of individually allocated pages. + struct page *pages[16]; +- if (prds->num_pages == 0 || prds->num_pages > ARRAY_SIZE(pages)) return -EINVAL; ++ if (prds->num_pages == 0 || prds->num_pages > ARRAY_SIZE(pages)) ++ return -EINVAL; + b->active_idx = -1; +- struct ithc_sg_table *sgt = devres_alloc(ithc_dma_data_devres_release, sizeof *sgt, GFP_KERNEL); +- if (!sgt) return -ENOMEM; ++ struct ithc_sg_table *sgt = devres_alloc( ++ ithc_dma_data_devres_release, sizeof(*sgt), GFP_KERNEL); ++ if (!sgt) ++ return -ENOMEM; + sgt->dir = prds->dir; ++ + if (!sg_alloc_table(&sgt->sgt, prds->num_pages, GFP_KERNEL)) { + struct scatterlist *sg; + int i; + bool ok = true; + for_each_sgtable_sg(&sgt->sgt, sg, i) { +- struct page *p = pages[i] = alloc_page(GFP_KERNEL | __GFP_ZERO); // don't need __GFP_DMA for PCI DMA +- if (!p) { ok = false; break; } ++ // NOTE: don't need __GFP_DMA for PCI DMA ++ struct page *p = pages[i] = alloc_page(GFP_KERNEL | __GFP_ZERO); ++ if (!p) { ++ ok = false; ++ break; ++ } + sg_set_page(sg, p, PAGE_SIZE, 0); + } + if (ok && !dma_map_sgtable(&ithc->pci->dev, &sgt->sgt, prds->dir, 0)) { + devres_add(&ithc->pci->dev, sgt); + b->sgt = &sgt->sgt; + b->addr = sgt->addr = vmap(pages, prds->num_pages, 0, PAGE_KERNEL); +- if (!b->addr) return -ENOMEM; ++ if (!b->addr) ++ return -ENOMEM; + return 0; + } + ithc_dma_sgtable_free(&sgt->sgt); +@@ -62,17 +94,29 @@ static int ithc_dma_data_alloc(struct ithc* ithc, struct ithc_dma_prd_buffer *pr + return -ENOMEM; + } + +-static int ithc_dma_data_buffer_put(struct ithc *ithc, struct ithc_dma_prd_buffer *prds, struct ithc_dma_data_buffer *b, unsigned idx) { ++static int ithc_dma_data_buffer_put(struct ithc *ithc, struct ithc_dma_prd_buffer *prds, ++ struct ithc_dma_data_buffer *b, unsigned int idx) ++{ ++ // Give a buffer to the THC. + struct ithc_phys_region_desc *prd = prds->addr; + prd += idx * prds->num_pages; +- if (b->active_idx >= 0) { pci_err(ithc->pci, "buffer already active\n"); return -EINVAL; } ++ if (b->active_idx >= 0) { ++ pci_err(ithc->pci, "buffer already active\n"); ++ return -EINVAL; ++ } + b->active_idx = idx; + if (prds->dir == DMA_TO_DEVICE) { +- if (b->data_size > PAGE_SIZE) return -EINVAL; ++ // TX buffer: Caller should have already filled the data buffer, so just fill ++ // the PRD and flush. ++ // (TODO: Support multi-page TX buffers. So far no device seems to use or need ++ // these though.) ++ if (b->data_size > PAGE_SIZE) ++ return -EINVAL; + prd->addr = sg_dma_address(b->sgt->sgl) >> 10; + prd->size = b->data_size | PRD_FLAG_END; + flush_kernel_vmap_range(b->addr, b->data_size); + } else if (prds->dir == DMA_FROM_DEVICE) { ++ // RX buffer: Reset PRDs. + struct scatterlist *sg; + int i; + for_each_sgtable_dma_sg(b->sgt, sg, i) { +@@ -87,21 +131,34 @@ static int ithc_dma_data_buffer_put(struct ithc *ithc, struct ithc_dma_prd_buffe + return 0; + } + +-static int ithc_dma_data_buffer_get(struct ithc *ithc, struct ithc_dma_prd_buffer *prds, struct ithc_dma_data_buffer *b, unsigned idx) { ++static int ithc_dma_data_buffer_get(struct ithc *ithc, struct ithc_dma_prd_buffer *prds, ++ struct ithc_dma_data_buffer *b, unsigned int idx) ++{ ++ // Take a buffer from the THC. + struct ithc_phys_region_desc *prd = prds->addr; + prd += idx * prds->num_pages; +- if (b->active_idx != idx) { pci_err(ithc->pci, "wrong buffer index\n"); return -EINVAL; } ++ // This is purely a sanity check. We don't strictly need the idx parameter for this ++ // function, because it should always be the same as active_idx, unless we have a bug. ++ if (b->active_idx != idx) { ++ pci_err(ithc->pci, "wrong buffer index\n"); ++ return -EINVAL; ++ } + b->active_idx = -1; + if (prds->dir == DMA_FROM_DEVICE) { ++ // RX buffer: Calculate actual received data size from PRDs. + dma_rmb(); // for the prds + b->data_size = 0; + struct scatterlist *sg; + int i; + for_each_sgtable_dma_sg(b->sgt, sg, i) { +- unsigned size = prd->size; ++ unsigned int size = prd->size; + b->data_size += size & PRD_SIZE_MASK; +- if (size & PRD_FLAG_END) break; +- if ((size & PRD_SIZE_MASK) != sg_dma_len(sg)) { pci_err(ithc->pci, "truncated prd\n"); break; } ++ if (size & PRD_FLAG_END) ++ break; ++ if ((size & PRD_SIZE_MASK) != sg_dma_len(sg)) { ++ pci_err(ithc->pci, "truncated prd\n"); ++ break; ++ } + prd++; + } + invalidate_kernel_vmap_range(b->addr, b->data_size); +@@ -110,93 +167,139 @@ static int ithc_dma_data_buffer_get(struct ithc *ithc, struct ithc_dma_prd_buffe + return 0; + } + +-int ithc_dma_rx_init(struct ithc *ithc, u8 channel, const char *devname) { ++int ithc_dma_rx_init(struct ithc *ithc, u8 channel) ++{ + struct ithc_dma_rx *rx = &ithc->dma_rx[channel]; + mutex_init(&rx->mutex); ++ ++ // Allocate buffers. + u32 buf_size = DEVCFG_DMA_RX_SIZE(ithc->config.dma_buf_sizes); +- unsigned num_pages = (buf_size + PAGE_SIZE - 1) / PAGE_SIZE; +- pci_dbg(ithc->pci, "allocating rx buffers: num = %u, size = %u, pages = %u\n", NUM_RX_BUF, buf_size, num_pages); ++ unsigned int num_pages = (buf_size + PAGE_SIZE - 1) / PAGE_SIZE; ++ pci_dbg(ithc->pci, "allocating rx buffers: num = %u, size = %u, pages = %u\n", ++ NUM_RX_BUF, buf_size, num_pages); + CHECK_RET(ithc_dma_prd_alloc, ithc, &rx->prds, NUM_RX_BUF, num_pages, DMA_FROM_DEVICE); +- for (unsigned i = 0; i < NUM_RX_BUF; i++) ++ for (unsigned int i = 0; i < NUM_RX_BUF; i++) + CHECK_RET(ithc_dma_data_alloc, ithc, &rx->prds, &rx->bufs[i]); ++ ++ // Init registers. + writeb(DMA_RX_CONTROL2_RESET, &ithc->regs->dma_rx[channel].control2); + lo_hi_writeq(rx->prds.dma_addr, &ithc->regs->dma_rx[channel].addr); + writeb(NUM_RX_BUF - 1, &ithc->regs->dma_rx[channel].num_bufs); + writeb(num_pages - 1, &ithc->regs->dma_rx[channel].num_prds); + u8 head = readb(&ithc->regs->dma_rx[channel].head); +- if (head) { pci_err(ithc->pci, "head is nonzero (%u)\n", head); return -EIO; } +- for (unsigned i = 0; i < NUM_RX_BUF; i++) ++ if (head) { ++ pci_err(ithc->pci, "head is nonzero (%u)\n", head); ++ return -EIO; ++ } ++ ++ // Init buffers. ++ for (unsigned int i = 0; i < NUM_RX_BUF; i++) + CHECK_RET(ithc_dma_data_buffer_put, ithc, &rx->prds, &rx->bufs[i], i); ++ + writeb(head ^ DMA_RX_WRAP_FLAG, &ithc->regs->dma_rx[channel].tail); + return 0; + } +-void ithc_dma_rx_enable(struct ithc *ithc, u8 channel) { +- bitsb_set(&ithc->regs->dma_rx[channel].control, DMA_RX_CONTROL_ENABLE | DMA_RX_CONTROL_IRQ_ERROR | DMA_RX_CONTROL_IRQ_DATA); +- CHECK(waitl, ithc, &ithc->regs->dma_rx[1].status, DMA_RX_STATUS_ENABLED, DMA_RX_STATUS_ENABLED); ++ ++void ithc_dma_rx_enable(struct ithc *ithc, u8 channel) ++{ ++ bitsb_set(&ithc->regs->dma_rx[channel].control, ++ DMA_RX_CONTROL_ENABLE | DMA_RX_CONTROL_IRQ_ERROR | DMA_RX_CONTROL_IRQ_DATA); ++ CHECK(waitl, ithc, &ithc->regs->dma_rx[channel].status, ++ DMA_RX_STATUS_ENABLED, DMA_RX_STATUS_ENABLED); + } + +-int ithc_dma_tx_init(struct ithc *ithc) { ++int ithc_dma_tx_init(struct ithc *ithc) ++{ + struct ithc_dma_tx *tx = &ithc->dma_tx; + mutex_init(&tx->mutex); ++ ++ // Allocate buffers. + tx->max_size = DEVCFG_DMA_TX_SIZE(ithc->config.dma_buf_sizes); +- unsigned num_pages = (tx->max_size + PAGE_SIZE - 1) / PAGE_SIZE; +- pci_dbg(ithc->pci, "allocating tx buffers: size = %u, pages = %u\n", tx->max_size, num_pages); ++ unsigned int num_pages = (tx->max_size + PAGE_SIZE - 1) / PAGE_SIZE; ++ pci_dbg(ithc->pci, "allocating tx buffers: size = %u, pages = %u\n", ++ tx->max_size, num_pages); + CHECK_RET(ithc_dma_prd_alloc, ithc, &tx->prds, 1, num_pages, DMA_TO_DEVICE); + CHECK_RET(ithc_dma_data_alloc, ithc, &tx->prds, &tx->buf); ++ ++ // Init registers. + lo_hi_writeq(tx->prds.dma_addr, &ithc->regs->dma_tx.addr); + writeb(num_pages - 1, &ithc->regs->dma_tx.num_prds); ++ ++ // Init buffers. + CHECK_RET(ithc_dma_data_buffer_put, ithc, &ithc->dma_tx.prds, &ithc->dma_tx.buf, 0); + return 0; + } + +-static int ithc_dma_rx_process_buf(struct ithc *ithc, struct ithc_dma_data_buffer *data, u8 channel, u8 buf) { ++static int ithc_dma_rx_process_buf(struct ithc *ithc, struct ithc_dma_data_buffer *data, ++ u8 channel, u8 buf) ++{ + if (buf >= NUM_RX_BUF) { + pci_err(ithc->pci, "invalid dma ringbuffer index\n"); + return -EINVAL; + } +- ithc_set_active(ithc); + u32 len = data->data_size; + struct ithc_dma_rx_header *hdr = data->addr; + u8 *hiddata = (void *)(hdr + 1); +- if (len >= sizeof *hdr && hdr->code == DMA_RX_CODE_RESET) { ++ if (len >= sizeof(*hdr) && hdr->code == DMA_RX_CODE_RESET) { ++ // The THC sends a reset request when we need to reinitialize the device. ++ // This usually only happens if we send an invalid command or put the device ++ // in a bad state. + CHECK(ithc_reset, ithc); +- } else if (len < sizeof *hdr || len != sizeof *hdr + hdr->data_size) { ++ } else if (len < sizeof(*hdr) || len != sizeof(*hdr) + hdr->data_size) { + if (hdr->code == DMA_RX_CODE_INPUT_REPORT) { +- // When the CPU enters a low power state during DMA, we can get truncated messages. +- // Typically this will be a single touch HID report that is only 1 byte, or a multitouch report that is 257 bytes. ++ // When the CPU enters a low power state during DMA, we can get truncated ++ // messages. For Surface devices, this will typically be a single touch ++ // report that is only 1 byte, or a multitouch report that is 257 bytes. + // See also ithc_set_active(). + } else { +- pci_err(ithc->pci, "invalid dma rx data! channel %u, buffer %u, size %u, code %u, data size %u\n", channel, buf, len, hdr->code, hdr->data_size); +- print_hex_dump_debug(DEVNAME " data: ", DUMP_PREFIX_OFFSET, 32, 1, hdr, min(len, 0x400u), 0); ++ pci_err(ithc->pci, "invalid dma rx data! channel %u, buffer %u, size %u, code %u, data size %u\n", ++ channel, buf, len, hdr->code, hdr->data_size); ++ print_hex_dump_debug(DEVNAME " data: ", DUMP_PREFIX_OFFSET, 32, 1, ++ hdr, min(len, 0x400u), 0); + } + } else if (hdr->code == DMA_RX_CODE_REPORT_DESCRIPTOR && hdr->data_size > 8) { ++ // Response to a 'get report descriptor' request. ++ // The actual descriptor is preceded by 8 nul bytes. + CHECK(hid_parse_report, ithc->hid, hiddata + 8, hdr->data_size - 8); + WRITE_ONCE(ithc->hid_parse_done, true); + wake_up(&ithc->wait_hid_parse); + } else if (hdr->code == DMA_RX_CODE_INPUT_REPORT) { ++ // Standard HID input report containing touch data. + CHECK(hid_input_report, ithc->hid, HID_INPUT_REPORT, hiddata, hdr->data_size, 1); + } else if (hdr->code == DMA_RX_CODE_FEATURE_REPORT) { ++ // Response to a 'get feature' request. + bool done = false; + mutex_lock(&ithc->hid_get_feature_mutex); + if (ithc->hid_get_feature_buf) { +- if (hdr->data_size < ithc->hid_get_feature_size) ithc->hid_get_feature_size = hdr->data_size; ++ if (hdr->data_size < ithc->hid_get_feature_size) ++ ithc->hid_get_feature_size = hdr->data_size; + memcpy(ithc->hid_get_feature_buf, hiddata, ithc->hid_get_feature_size); + ithc->hid_get_feature_buf = NULL; + done = true; + } + mutex_unlock(&ithc->hid_get_feature_mutex); +- if (done) wake_up(&ithc->wait_hid_get_feature); +- else CHECK(hid_input_report, ithc->hid, HID_FEATURE_REPORT, hiddata, hdr->data_size, 1); ++ if (done) { ++ wake_up(&ithc->wait_hid_get_feature); ++ } else { ++ // Received data without a matching request, or the request already ++ // timed out. (XXX What's the correct thing to do here?) ++ CHECK(hid_input_report, ithc->hid, HID_FEATURE_REPORT, ++ hiddata, hdr->data_size, 1); ++ } + } else { +- pci_dbg(ithc->pci, "unhandled dma rx data! channel %u, buffer %u, size %u, code %u\n", channel, buf, len, hdr->code); +- print_hex_dump_debug(DEVNAME " data: ", DUMP_PREFIX_OFFSET, 32, 1, hdr, min(len, 0x400u), 0); ++ pci_dbg(ithc->pci, "unhandled dma rx data! channel %u, buffer %u, size %u, code %u\n", ++ channel, buf, len, hdr->code); ++ print_hex_dump_debug(DEVNAME " data: ", DUMP_PREFIX_OFFSET, 32, 1, ++ hdr, min(len, 0x400u), 0); + } + return 0; + } + +-static int ithc_dma_rx_unlocked(struct ithc *ithc, u8 channel) { ++static int ithc_dma_rx_unlocked(struct ithc *ithc, u8 channel) ++{ ++ // Process all filled RX buffers from the ringbuffer. + struct ithc_dma_rx *rx = &ithc->dma_rx[channel]; +- unsigned n = rx->num_received; ++ unsigned int n = rx->num_received; + u8 head_wrap = readb(&ithc->regs->dma_rx[channel].head); + while (1) { + u8 tail = n % NUM_RX_BUF; +@@ -204,7 +307,8 @@ static int ithc_dma_rx_unlocked(struct ithc *ithc, u8 channel) { + writeb(tail_wrap, &ithc->regs->dma_rx[channel].tail); + // ringbuffer is full if tail_wrap == head_wrap + // ringbuffer is empty if tail_wrap == head_wrap ^ WRAP_FLAG +- if (tail_wrap == (head_wrap ^ DMA_RX_WRAP_FLAG)) return 0; ++ if (tail_wrap == (head_wrap ^ DMA_RX_WRAP_FLAG)) ++ return 0; + + // take the buffer that the device just filled + struct ithc_dma_data_buffer *b = &rx->bufs[n % NUM_RX_BUF]; +@@ -218,7 +322,8 @@ static int ithc_dma_rx_unlocked(struct ithc *ithc, u8 channel) { + CHECK_RET(ithc_dma_data_buffer_put, ithc, &rx->prds, b, tail); + } + } +-int ithc_dma_rx(struct ithc *ithc, u8 channel) { ++int ithc_dma_rx(struct ithc *ithc, u8 channel) ++{ + struct ithc_dma_rx *rx = &ithc->dma_rx[channel]; + mutex_lock(&rx->mutex); + int ret = ithc_dma_rx_unlocked(ithc, channel); +@@ -226,14 +331,21 @@ int ithc_dma_rx(struct ithc *ithc, u8 channel) { + return ret; + } + +-static int ithc_dma_tx_unlocked(struct ithc *ithc, u32 cmdcode, u32 datasize, void *data) { ++static int ithc_dma_tx_unlocked(struct ithc *ithc, u32 cmdcode, u32 datasize, void *data) ++{ ++ ithc_set_active(ithc, 100 * USEC_PER_MSEC); ++ ++ // Send a single TX buffer to the THC. + pci_dbg(ithc->pci, "dma tx command %u, size %u\n", cmdcode, datasize); + struct ithc_dma_tx_header *hdr; ++ // Data must be padded to next 4-byte boundary. + u8 padding = datasize & 3 ? 4 - (datasize & 3) : 0; +- unsigned fullsize = sizeof *hdr + datasize + padding; +- if (fullsize > ithc->dma_tx.max_size || fullsize > PAGE_SIZE) return -EINVAL; ++ unsigned int fullsize = sizeof(*hdr) + datasize + padding; ++ if (fullsize > ithc->dma_tx.max_size || fullsize > PAGE_SIZE) ++ return -EINVAL; + CHECK_RET(ithc_dma_data_buffer_get, ithc, &ithc->dma_tx.prds, &ithc->dma_tx.buf, 0); + ++ // Fill the TX buffer with header and data. + ithc->dma_tx.buf.data_size = fullsize; + hdr = ithc->dma_tx.buf.addr; + hdr->code = cmdcode; +@@ -241,15 +353,18 @@ static int ithc_dma_tx_unlocked(struct ithc *ithc, u32 cmdcode, u32 datasize, vo + u8 *dest = (void *)(hdr + 1); + memcpy(dest, data, datasize); + dest += datasize; +- for (u8 p = 0; p < padding; p++) *dest++ = 0; ++ for (u8 p = 0; p < padding; p++) ++ *dest++ = 0; + CHECK_RET(ithc_dma_data_buffer_put, ithc, &ithc->dma_tx.prds, &ithc->dma_tx.buf, 0); + ++ // Let the THC process the buffer. + bitsb_set(&ithc->regs->dma_tx.control, DMA_TX_CONTROL_SEND); + CHECK_RET(waitb, ithc, &ithc->regs->dma_tx.control, DMA_TX_CONTROL_SEND, 0); + writel(DMA_TX_STATUS_DONE, &ithc->regs->dma_tx.status); + return 0; + } +-int ithc_dma_tx(struct ithc *ithc, u32 cmdcode, u32 datasize, void *data) { ++int ithc_dma_tx(struct ithc *ithc, u32 cmdcode, u32 datasize, void *data) ++{ + mutex_lock(&ithc->dma_tx.mutex); + int ret = ithc_dma_tx_unlocked(ithc, cmdcode, datasize, data); + mutex_unlock(&ithc->dma_tx.mutex); +diff --git a/drivers/hid/ithc/ithc-dma.h b/drivers/hid/ithc/ithc-dma.h +index d9f2c19a13f3a..93652e4476bf8 100644 +--- a/drivers/hid/ithc/ithc-dma.h ++++ b/drivers/hid/ithc/ithc-dma.h +@@ -1,3 +1,5 @@ ++/* SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause */ ++ + #define PRD_SIZE_MASK 0xffffff + #define PRD_FLAG_END 0x1000000 + #define PRD_FLAG_SUCCESS 0x2000000 +@@ -59,7 +61,7 @@ struct ithc_dma_rx { + struct ithc_dma_data_buffer bufs[NUM_RX_BUF]; + }; + +-int ithc_dma_rx_init(struct ithc *ithc, u8 channel, const char *devname); ++int ithc_dma_rx_init(struct ithc *ithc, u8 channel); + void ithc_dma_rx_enable(struct ithc *ithc, u8 channel); + int ithc_dma_tx_init(struct ithc *ithc); + int ithc_dma_rx(struct ithc *ithc, u8 channel); +diff --git a/drivers/hid/ithc/ithc-main.c b/drivers/hid/ithc/ithc-main.c +index 09512b9cb4d31..87ed4aa70fda0 100644 +--- a/drivers/hid/ithc/ithc-main.c ++++ b/drivers/hid/ithc/ithc-main.c +@@ -1,3 +1,5 @@ ++// SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause ++ + #include "ithc.h" + + MODULE_DESCRIPTION("Intel Touch Host Controller driver"); +@@ -42,6 +44,9 @@ static const struct pci_device_id ithc_pci_tbl[] = { + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_RPL_S_PORT2) }, + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_MTL_PORT1) }, + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_THC_MTL_PORT2) }, ++ // XXX So far the THC seems to be the only Intel PCI device with PCI_CLASS_INPUT_PEN, ++ // so instead of the device list we could just do: ++ // { .vendor = PCI_VENDOR_ID_INTEL, .device = PCI_ANY_ID, .subvendor = PCI_ANY_ID, .subdevice = PCI_ANY_ID, .class = PCI_CLASS_INPUT_PEN, .class_mask = ~0, }, + {} + }; + MODULE_DEVICE_TABLE(pci, ithc_pci_tbl); +@@ -52,6 +57,7 @@ static bool ithc_use_polling = false; + module_param_named(poll, ithc_use_polling, bool, 0); + MODULE_PARM_DESC(poll, "Use polling instead of interrupts"); + ++// Since all known devices seem to use only channel 1, by default we disable channel 0. + static bool ithc_use_rx0 = false; + module_param_named(rx0, ithc_use_rx0, bool, 0); + MODULE_PARM_DESC(rx0, "Use DMA RX channel 0"); +@@ -60,37 +66,56 @@ static bool ithc_use_rx1 = true; + module_param_named(rx1, ithc_use_rx1, bool, 0); + MODULE_PARM_DESC(rx1, "Use DMA RX channel 1"); + ++// Values below 250 seem to work well on the SP7+. If this is set too high, you may observe cursor stuttering. ++static int ithc_dma_latency_us = 200; ++module_param_named(dma_latency_us, ithc_dma_latency_us, int, 0); ++MODULE_PARM_DESC(dma_latency_us, "Determines the CPU latency QoS value for DMA transfers (in microseconds), -1 to disable latency QoS"); ++ ++// Values above 1700 seem to work well on the SP7+. If this is set too low, you may observe cursor stuttering. ++static unsigned int ithc_dma_early_us = 2000; ++module_param_named(dma_early_us, ithc_dma_early_us, uint, 0); ++MODULE_PARM_DESC(dma_early_us, "Determines how early the CPU latency QoS value is applied before the next expected IRQ (in microseconds)"); ++ + static bool ithc_log_regs_enabled = false; + module_param_named(logregs, ithc_log_regs_enabled, bool, 0); + MODULE_PARM_DESC(logregs, "Log changes in register values (for debugging)"); + + // Sysfs attributes + +-static bool ithc_is_config_valid(struct ithc *ithc) { ++static bool ithc_is_config_valid(struct ithc *ithc) ++{ + return ithc->config.device_id == DEVCFG_DEVICE_ID_TIC; + } + +-static ssize_t vendor_show(struct device *dev, struct device_attribute *attr, char *buf) { ++static ssize_t vendor_show(struct device *dev, struct device_attribute *attr, char *buf) ++{ + struct ithc *ithc = dev_get_drvdata(dev); +- if (!ithc || !ithc_is_config_valid(ithc)) return -ENODEV; ++ if (!ithc || !ithc_is_config_valid(ithc)) ++ return -ENODEV; + return sprintf(buf, "0x%04x", ithc->config.vendor_id); + } + static DEVICE_ATTR_RO(vendor); +-static ssize_t product_show(struct device *dev, struct device_attribute *attr, char *buf) { ++static ssize_t product_show(struct device *dev, struct device_attribute *attr, char *buf) ++{ + struct ithc *ithc = dev_get_drvdata(dev); +- if (!ithc || !ithc_is_config_valid(ithc)) return -ENODEV; ++ if (!ithc || !ithc_is_config_valid(ithc)) ++ return -ENODEV; + return sprintf(buf, "0x%04x", ithc->config.product_id); + } + static DEVICE_ATTR_RO(product); +-static ssize_t revision_show(struct device *dev, struct device_attribute *attr, char *buf) { ++static ssize_t revision_show(struct device *dev, struct device_attribute *attr, char *buf) ++{ + struct ithc *ithc = dev_get_drvdata(dev); +- if (!ithc || !ithc_is_config_valid(ithc)) return -ENODEV; ++ if (!ithc || !ithc_is_config_valid(ithc)) ++ return -ENODEV; + return sprintf(buf, "%u", ithc->config.revision); + } + static DEVICE_ATTR_RO(revision); +-static ssize_t fw_version_show(struct device *dev, struct device_attribute *attr, char *buf) { ++static ssize_t fw_version_show(struct device *dev, struct device_attribute *attr, char *buf) ++{ + struct ithc *ithc = dev_get_drvdata(dev); +- if (!ithc || !ithc_is_config_valid(ithc)) return -ENODEV; ++ if (!ithc || !ithc_is_config_valid(ithc)) ++ return -ENODEV; + u32 v = ithc->config.fw_version; + return sprintf(buf, "%i.%i.%i.%i", v >> 24, v >> 16 & 0xff, v >> 8 & 0xff, v & 0xff); + } +@@ -117,45 +142,75 @@ static void ithc_hid_stop(struct hid_device *hdev) { } + static int ithc_hid_open(struct hid_device *hdev) { return 0; } + static void ithc_hid_close(struct hid_device *hdev) { } + +-static int ithc_hid_parse(struct hid_device *hdev) { ++static int ithc_hid_parse(struct hid_device *hdev) ++{ + struct ithc *ithc = hdev->driver_data; + u64 val = 0; + WRITE_ONCE(ithc->hid_parse_done, false); +- CHECK_RET(ithc_dma_tx, ithc, DMA_TX_CODE_GET_REPORT_DESCRIPTOR, sizeof val, &val); +- if (!wait_event_timeout(ithc->wait_hid_parse, READ_ONCE(ithc->hid_parse_done), msecs_to_jiffies(1000))) return -ETIMEDOUT; +- return 0; ++ for (int retries = 0; ; retries++) { ++ CHECK_RET(ithc_dma_tx, ithc, DMA_TX_CODE_GET_REPORT_DESCRIPTOR, sizeof(val), &val); ++ if (wait_event_timeout(ithc->wait_hid_parse, READ_ONCE(ithc->hid_parse_done), ++ msecs_to_jiffies(200))) ++ return 0; ++ if (retries > 5) { ++ pci_err(ithc->pci, "failed to read report descriptor\n"); ++ return -ETIMEDOUT; ++ } ++ pci_warn(ithc->pci, "failed to read report descriptor, retrying\n"); ++ } + } + +-static int ithc_hid_raw_request(struct hid_device *hdev, unsigned char reportnum, __u8 *buf, size_t len, unsigned char rtype, int reqtype) { ++static int ithc_hid_raw_request(struct hid_device *hdev, unsigned char reportnum, __u8 *buf, ++ size_t len, unsigned char rtype, int reqtype) ++{ + struct ithc *ithc = hdev->driver_data; +- if (!buf || !len) return -EINVAL; ++ if (!buf || !len) ++ return -EINVAL; + u32 code; +- if (rtype == HID_OUTPUT_REPORT && reqtype == HID_REQ_SET_REPORT) code = DMA_TX_CODE_OUTPUT_REPORT; +- else if (rtype == HID_FEATURE_REPORT && reqtype == HID_REQ_SET_REPORT) code = DMA_TX_CODE_SET_FEATURE; +- else if (rtype == HID_FEATURE_REPORT && reqtype == HID_REQ_GET_REPORT) code = DMA_TX_CODE_GET_FEATURE; +- else { +- pci_err(ithc->pci, "unhandled hid request %i %i for report id %i\n", rtype, reqtype, reportnum); ++ if (rtype == HID_OUTPUT_REPORT && reqtype == HID_REQ_SET_REPORT) { ++ code = DMA_TX_CODE_OUTPUT_REPORT; ++ } else if (rtype == HID_FEATURE_REPORT && reqtype == HID_REQ_SET_REPORT) { ++ code = DMA_TX_CODE_SET_FEATURE; ++ } else if (rtype == HID_FEATURE_REPORT && reqtype == HID_REQ_GET_REPORT) { ++ code = DMA_TX_CODE_GET_FEATURE; ++ } else { ++ pci_err(ithc->pci, "unhandled hid request %i %i for report id %i\n", ++ rtype, reqtype, reportnum); + return -EINVAL; + } + buf[0] = reportnum; ++ + if (reqtype == HID_REQ_GET_REPORT) { ++ // Prepare for response. + mutex_lock(&ithc->hid_get_feature_mutex); + ithc->hid_get_feature_buf = buf; + ithc->hid_get_feature_size = len; + mutex_unlock(&ithc->hid_get_feature_mutex); ++ ++ // Transmit 'get feature' request. + int r = CHECK(ithc_dma_tx, ithc, code, 1, buf); + if (!r) { +- r = wait_event_interruptible_timeout(ithc->wait_hid_get_feature, !ithc->hid_get_feature_buf, msecs_to_jiffies(1000)); +- if (!r) r = -ETIMEDOUT; +- else if (r < 0) r = -EINTR; +- else r = 0; ++ r = wait_event_interruptible_timeout(ithc->wait_hid_get_feature, ++ !ithc->hid_get_feature_buf, msecs_to_jiffies(1000)); ++ if (!r) ++ r = -ETIMEDOUT; ++ else if (r < 0) ++ r = -EINTR; ++ else ++ r = 0; + } ++ ++ // If everything went ok, the buffer has been filled with the response data. ++ // Return the response size. + mutex_lock(&ithc->hid_get_feature_mutex); + ithc->hid_get_feature_buf = NULL; +- if (!r) r = ithc->hid_get_feature_size; ++ if (!r) ++ r = ithc->hid_get_feature_size; + mutex_unlock(&ithc->hid_get_feature_mutex); + return r; + } ++ ++ // 'Set feature', or 'output report'. These don't have a response. + CHECK_RET(ithc_dma_tx, ithc, code, len, buf); + return 0; + } +@@ -169,17 +224,22 @@ static struct hid_ll_driver ithc_ll_driver = { + .raw_request = ithc_hid_raw_request, + }; + +-static void ithc_hid_devres_release(struct device *dev, void *res) { ++static void ithc_hid_devres_release(struct device *dev, void *res) ++{ + struct hid_device **hidm = res; +- if (*hidm) hid_destroy_device(*hidm); ++ if (*hidm) ++ hid_destroy_device(*hidm); + } + +-static int ithc_hid_init(struct ithc *ithc) { +- struct hid_device **hidm = devres_alloc(ithc_hid_devres_release, sizeof *hidm, GFP_KERNEL); +- if (!hidm) return -ENOMEM; ++static int ithc_hid_init(struct ithc *ithc) ++{ ++ struct hid_device **hidm = devres_alloc(ithc_hid_devres_release, sizeof(*hidm), GFP_KERNEL); ++ if (!hidm) ++ return -ENOMEM; + devres_add(&ithc->pci->dev, hidm); + struct hid_device *hid = hid_allocate_device(); +- if (IS_ERR(hid)) return PTR_ERR(hid); ++ if (IS_ERR(hid)) ++ return PTR_ERR(hid); + *hidm = hid; + + strscpy(hid->name, DEVFULLNAME, sizeof(hid->name)); +@@ -198,27 +258,45 @@ static int ithc_hid_init(struct ithc *ithc) { + + // Interrupts/polling + +-static void ithc_activity_timer_callback(struct timer_list *t) { +- struct ithc *ithc = container_of(t, struct ithc, activity_timer); +- cpu_latency_qos_update_request(&ithc->activity_qos, PM_QOS_DEFAULT_VALUE); ++static enum hrtimer_restart ithc_activity_start_timer_callback(struct hrtimer *t) ++{ ++ struct ithc *ithc = container_of(t, struct ithc, activity_start_timer); ++ ithc_set_active(ithc, ithc_dma_early_us * 2 + USEC_PER_MSEC); ++ return HRTIMER_NORESTART; + } + +-void ithc_set_active(struct ithc *ithc) { +- // When CPU usage is very low, the CPU can enter various low power states (C2-C10). +- // This disrupts DMA, causing truncated DMA messages. ERROR_FLAG_DMA_UNKNOWN_12 will be set when this happens. +- // The amount of truncated messages can become very high, resulting in user-visible effects (laggy/stuttering cursor). +- // To avoid this, we use a CPU latency QoS request to prevent the CPU from entering low power states during touch interactions. +- cpu_latency_qos_update_request(&ithc->activity_qos, 0); +- mod_timer(&ithc->activity_timer, jiffies + msecs_to_jiffies(1000)); +-} +- +-static int ithc_set_device_enabled(struct ithc *ithc, bool enable) { +- u32 x = ithc->config.touch_cfg = (ithc->config.touch_cfg & ~(u32)DEVCFG_TOUCH_MASK) | DEVCFG_TOUCH_UNKNOWN_2 +- | (enable ? DEVCFG_TOUCH_ENABLE | DEVCFG_TOUCH_UNKNOWN_3 | DEVCFG_TOUCH_UNKNOWN_4 : 0); +- return ithc_spi_command(ithc, SPI_CMD_CODE_WRITE, offsetof(struct ithc_device_config, touch_cfg), sizeof x, &x); ++static enum hrtimer_restart ithc_activity_end_timer_callback(struct hrtimer *t) ++{ ++ struct ithc *ithc = container_of(t, struct ithc, activity_end_timer); ++ cpu_latency_qos_update_request(&ithc->activity_qos, PM_QOS_DEFAULT_VALUE); ++ return HRTIMER_NORESTART; + } + +-static void ithc_disable_interrupts(struct ithc *ithc) { ++void ithc_set_active(struct ithc *ithc, unsigned int duration_us) ++{ ++ if (ithc_dma_latency_us < 0) ++ return; ++ // When CPU usage is very low, the CPU can enter various low power states (C2-C10). ++ // This disrupts DMA, causing truncated DMA messages. ERROR_FLAG_DMA_RX_TIMEOUT will be ++ // set when this happens. The amount of truncated messages can become very high, resulting ++ // in user-visible effects (laggy/stuttering cursor). To avoid this, we use a CPU latency ++ // QoS request to prevent the CPU from entering low power states during touch interactions. ++ cpu_latency_qos_update_request(&ithc->activity_qos, ithc_dma_latency_us); ++ hrtimer_start_range_ns(&ithc->activity_end_timer, ++ ns_to_ktime(duration_us * NSEC_PER_USEC), duration_us * NSEC_PER_USEC, HRTIMER_MODE_REL); ++} ++ ++static int ithc_set_device_enabled(struct ithc *ithc, bool enable) ++{ ++ u32 x = ithc->config.touch_cfg = ++ (ithc->config.touch_cfg & ~(u32)DEVCFG_TOUCH_MASK) | DEVCFG_TOUCH_UNKNOWN_2 | ++ (enable ? DEVCFG_TOUCH_ENABLE | DEVCFG_TOUCH_UNKNOWN_3 | DEVCFG_TOUCH_UNKNOWN_4 : 0); ++ return ithc_spi_command(ithc, SPI_CMD_CODE_WRITE, ++ offsetof(struct ithc_device_config, touch_cfg), sizeof(x), &x); ++} ++ ++static void ithc_disable_interrupts(struct ithc *ithc) ++{ + writel(0, &ithc->regs->error_control); + bitsb(&ithc->regs->spi_cmd.control, SPI_CMD_CONTROL_IRQ, 0); + bitsb(&ithc->regs->dma_rx[0].control, DMA_RX_CONTROL_IRQ_UNKNOWN_1 | DMA_RX_CONTROL_IRQ_ERROR | DMA_RX_CONTROL_IRQ_UNKNOWN_4 | DMA_RX_CONTROL_IRQ_DATA, 0); +@@ -226,43 +304,85 @@ static void ithc_disable_interrupts(struct ithc *ithc) { + bitsb(&ithc->regs->dma_tx.control, DMA_TX_CONTROL_IRQ, 0); + } + +-static void ithc_clear_dma_rx_interrupts(struct ithc *ithc, unsigned channel) { +- writel(DMA_RX_STATUS_ERROR | DMA_RX_STATUS_UNKNOWN_4 | DMA_RX_STATUS_HAVE_DATA, &ithc->regs->dma_rx[channel].status); ++static void ithc_clear_dma_rx_interrupts(struct ithc *ithc, unsigned int channel) ++{ ++ writel(DMA_RX_STATUS_ERROR | DMA_RX_STATUS_UNKNOWN_4 | DMA_RX_STATUS_HAVE_DATA, ++ &ithc->regs->dma_rx[channel].status); + } + +-static void ithc_clear_interrupts(struct ithc *ithc) { ++static void ithc_clear_interrupts(struct ithc *ithc) ++{ + writel(0xffffffff, &ithc->regs->error_flags); + writel(ERROR_STATUS_DMA | ERROR_STATUS_SPI, &ithc->regs->error_status); + writel(SPI_CMD_STATUS_DONE | SPI_CMD_STATUS_ERROR, &ithc->regs->spi_cmd.status); + ithc_clear_dma_rx_interrupts(ithc, 0); + ithc_clear_dma_rx_interrupts(ithc, 1); +- writel(DMA_TX_STATUS_DONE | DMA_TX_STATUS_ERROR | DMA_TX_STATUS_UNKNOWN_2, &ithc->regs->dma_tx.status); ++ writel(DMA_TX_STATUS_DONE | DMA_TX_STATUS_ERROR | DMA_TX_STATUS_UNKNOWN_2, ++ &ithc->regs->dma_tx.status); + } + +-static void ithc_process(struct ithc *ithc) { ++static void ithc_process(struct ithc *ithc) ++{ + ithc_log_regs(ithc); + +- // read and clear error bits ++ bool rx0 = ithc_use_rx0 && (readl(&ithc->regs->dma_rx[0].status) & (DMA_RX_STATUS_ERROR | DMA_RX_STATUS_HAVE_DATA)) != 0; ++ bool rx1 = ithc_use_rx1 && (readl(&ithc->regs->dma_rx[1].status) & (DMA_RX_STATUS_ERROR | DMA_RX_STATUS_HAVE_DATA)) != 0; ++ ++ // Track time between DMA rx transfers, so we can try to predict when we need to enable CPU latency QoS for the next transfer ++ ktime_t t = ktime_get(); ++ ktime_t dt = ktime_sub(t, ithc->last_rx_time); ++ if (rx0 || rx1) { ++ ithc->last_rx_time = t; ++ if (dt > ms_to_ktime(100)) { ++ ithc->cur_rx_seq_count = 0; ++ ithc->cur_rx_seq_errors = 0; ++ } ++ ithc->cur_rx_seq_count++; ++ if (!ithc_use_polling && ithc_dma_latency_us >= 0) { ++ // Disable QoS, since the DMA transfer has completed (we re-enable it after a delay below) ++ cpu_latency_qos_update_request(&ithc->activity_qos, PM_QOS_DEFAULT_VALUE); ++ hrtimer_try_to_cancel(&ithc->activity_end_timer); ++ } ++ } ++ ++ // Read and clear error bits + u32 err = readl(&ithc->regs->error_flags); + if (err) { +- if (err & ~ERROR_FLAG_DMA_UNKNOWN_12) pci_err(ithc->pci, "error flags: 0x%08x\n", err); + writel(err, &ithc->regs->error_flags); ++ if (err & ~ERROR_FLAG_DMA_RX_TIMEOUT) ++ pci_err(ithc->pci, "error flags: 0x%08x\n", err); ++ if (err & ERROR_FLAG_DMA_RX_TIMEOUT) { ++ // Only log an error if we see a significant number of these errors. ++ ithc->cur_rx_seq_errors++; ++ if (ithc->cur_rx_seq_errors && ithc->cur_rx_seq_errors % 50 == 0 && ithc->cur_rx_seq_errors > ithc->cur_rx_seq_count / 10) ++ pci_err(ithc->pci, "High number of DMA RX timeouts/errors (%u/%u, dt=%lldus). Try adjusting dma_early_us and/or dma_latency_us.\n", ++ ithc->cur_rx_seq_errors, ithc->cur_rx_seq_count, ktime_to_us(dt)); ++ } + } + +- // process DMA rx ++ // Process DMA rx + if (ithc_use_rx0) { + ithc_clear_dma_rx_interrupts(ithc, 0); +- ithc_dma_rx(ithc, 0); ++ if (rx0) ++ ithc_dma_rx(ithc, 0); + } + if (ithc_use_rx1) { + ithc_clear_dma_rx_interrupts(ithc, 1); +- ithc_dma_rx(ithc, 1); ++ if (rx1) ++ ithc_dma_rx(ithc, 1); ++ } ++ ++ // Start timer to re-enable QoS for next rx, but only if we've seen an ERROR_FLAG_DMA_RX_TIMEOUT ++ if ((rx0 || rx1) && !ithc_use_polling && ithc_dma_latency_us >= 0 && ithc->cur_rx_seq_errors > 0) { ++ ktime_t expires = ktime_add(t, ktime_sub_us(dt, ithc_dma_early_us)); ++ hrtimer_start_range_ns(&ithc->activity_start_timer, expires, 10 * NSEC_PER_USEC, HRTIMER_MODE_ABS); + } + + ithc_log_regs(ithc); + } + +-static irqreturn_t ithc_interrupt_thread(int irq, void *arg) { ++static irqreturn_t ithc_interrupt_thread(int irq, void *arg) ++{ + struct ithc *ithc = arg; + pci_dbg(ithc->pci, "IRQ! err=%08x/%08x/%08x, cmd=%02x/%08x, rx0=%02x/%08x, rx1=%02x/%08x, tx=%02x/%08x\n", + readl(&ithc->regs->error_control), readl(&ithc->regs->error_status), readl(&ithc->regs->error_flags), +@@ -274,14 +394,21 @@ static irqreturn_t ithc_interrupt_thread(int irq, void *arg) { + return IRQ_HANDLED; + } + +-static int ithc_poll_thread(void *arg) { ++static int ithc_poll_thread(void *arg) ++{ + struct ithc *ithc = arg; +- unsigned sleep = 100; ++ unsigned int sleep = 100; + while (!kthread_should_stop()) { + u32 n = ithc->dma_rx[1].num_received; + ithc_process(ithc); +- if (n != ithc->dma_rx[1].num_received) sleep = 20; +- else sleep = min(200u, sleep + (sleep >> 4) + 1); ++ // Decrease polling interval to 20ms if we received data, otherwise slowly ++ // increase it up to 200ms. ++ if (n != ithc->dma_rx[1].num_received) { ++ ithc_set_active(ithc, 100 * USEC_PER_MSEC); ++ sleep = 20; ++ } else { ++ sleep = min(200u, sleep + (sleep >> 4) + 1); ++ } + msleep_interruptible(sleep); + } + return 0; +@@ -289,7 +416,8 @@ static int ithc_poll_thread(void *arg) { + + // Device initialization and shutdown + +-static void ithc_disable(struct ithc *ithc) { ++static void ithc_disable(struct ithc *ithc) ++{ + bitsl_set(&ithc->regs->control_bits, CONTROL_QUIESCE); + CHECK(waitl, ithc, &ithc->regs->control_bits, CONTROL_IS_QUIESCED, CONTROL_IS_QUIESCED); + bitsl(&ithc->regs->control_bits, CONTROL_NRESET, 0); +@@ -301,81 +429,112 @@ static void ithc_disable(struct ithc *ithc) { + ithc_clear_interrupts(ithc); + } + +-static int ithc_init_device(struct ithc *ithc) { ++static int ithc_init_device(struct ithc *ithc) ++{ + ithc_log_regs(ithc); + bool was_enabled = (readl(&ithc->regs->control_bits) & CONTROL_NRESET) != 0; + ithc_disable(ithc); + CHECK_RET(waitl, ithc, &ithc->regs->control_bits, CONTROL_READY, CONTROL_READY); ++ ++ // Since we don't yet know which SPI config the device wants, use default speed and mode ++ // initially for reading config data. + ithc_set_spi_config(ithc, 10, 0); +- bitsl_set(&ithc->regs->dma_rx[0].unknown_init_bits, 0x80000000); // seems to help with reading config + +- if (was_enabled) if (msleep_interruptible(100)) return -EINTR; ++ // Setting the following bit seems to make reading the config more reliable. ++ bitsl_set(&ithc->regs->dma_rx[0].unknown_init_bits, 0x80000000); ++ ++ // If the device was previously enabled, wait a bit to make sure it's fully shut down. ++ if (was_enabled) ++ if (msleep_interruptible(100)) ++ return -EINTR; ++ ++ // Take the touch device out of reset. + bitsl(&ithc->regs->control_bits, CONTROL_QUIESCE, 0); + CHECK_RET(waitl, ithc, &ithc->regs->control_bits, CONTROL_IS_QUIESCED, 0); + for (int retries = 0; ; retries++) { + ithc_log_regs(ithc); + bitsl_set(&ithc->regs->control_bits, CONTROL_NRESET); +- if (!waitl(ithc, &ithc->regs->state, 0xf, 2)) break; ++ if (!waitl(ithc, &ithc->regs->state, 0xf, 2)) ++ break; + if (retries > 5) { +- pci_err(ithc->pci, "too many retries, failed to reset device\n"); ++ pci_err(ithc->pci, "failed to reset device, state = 0x%08x\n", readl(&ithc->regs->state)); + return -ETIMEDOUT; + } +- pci_err(ithc->pci, "invalid state, retrying reset\n"); ++ pci_warn(ithc->pci, "invalid state, retrying reset\n"); + bitsl(&ithc->regs->control_bits, CONTROL_NRESET, 0); +- if (msleep_interruptible(1000)) return -EINTR; ++ if (msleep_interruptible(1000)) ++ return -EINTR; + } + ithc_log_regs(ithc); + ++ // Waiting for the following status bit makes reading config much more reliable, ++ // however the official driver does not seem to do this... + CHECK(waitl, ithc, &ithc->regs->dma_rx[0].status, DMA_RX_STATUS_UNKNOWN_4, DMA_RX_STATUS_UNKNOWN_4); + +- // read config ++ // Read configuration data. + for (int retries = 0; ; retries++) { + ithc_log_regs(ithc); +- memset(&ithc->config, 0, sizeof ithc->config); +- CHECK_RET(ithc_spi_command, ithc, SPI_CMD_CODE_READ, 0, sizeof ithc->config, &ithc->config); ++ memset(&ithc->config, 0, sizeof(ithc->config)); ++ CHECK_RET(ithc_spi_command, ithc, SPI_CMD_CODE_READ, 0, sizeof(ithc->config), &ithc->config); + u32 *p = (void *)&ithc->config; + pci_info(ithc->pci, "config: %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x\n", + p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7], p[8], p[9], p[10], p[11], p[12], p[13], p[14], p[15]); +- if (ithc_is_config_valid(ithc)) break; ++ if (ithc_is_config_valid(ithc)) ++ break; + if (retries > 10) { +- pci_err(ithc->pci, "failed to read config, unknown device ID 0x%08x\n", ithc->config.device_id); ++ pci_err(ithc->pci, "failed to read config, unknown device ID 0x%08x\n", ++ ithc->config.device_id); + return -EIO; + } +- pci_err(ithc->pci, "failed to read config, retrying\n"); +- if (msleep_interruptible(100)) return -EINTR; ++ pci_warn(ithc->pci, "failed to read config, retrying\n"); ++ if (msleep_interruptible(100)) ++ return -EINTR; + } + ithc_log_regs(ithc); + +- CHECK_RET(ithc_set_spi_config, ithc, DEVCFG_SPI_MAX_FREQ(ithc->config.spi_config), DEVCFG_SPI_MODE(ithc->config.spi_config)); ++ // Apply SPI config and enable touch device. ++ CHECK_RET(ithc_set_spi_config, ithc, ++ DEVCFG_SPI_MAX_FREQ(ithc->config.spi_config), ++ DEVCFG_SPI_MODE(ithc->config.spi_config)); + CHECK_RET(ithc_set_device_enabled, ithc, true); + ithc_log_regs(ithc); + return 0; + } + +-int ithc_reset(struct ithc *ithc) { +- // FIXME This should probably do devres_release_group()+ithc_start(). But because this is called during DMA +- // processing, that would have to be done asynchronously (schedule_work()?). And with extra locking? ++int ithc_reset(struct ithc *ithc) ++{ ++ // FIXME This should probably do devres_release_group()+ithc_start(). ++ // But because this is called during DMA processing, that would have to be done ++ // asynchronously (schedule_work()?). And with extra locking? + pci_err(ithc->pci, "reset\n"); + CHECK(ithc_init_device, ithc); +- if (ithc_use_rx0) ithc_dma_rx_enable(ithc, 0); +- if (ithc_use_rx1) ithc_dma_rx_enable(ithc, 1); ++ if (ithc_use_rx0) ++ ithc_dma_rx_enable(ithc, 0); ++ if (ithc_use_rx1) ++ ithc_dma_rx_enable(ithc, 1); + ithc_log_regs(ithc); + pci_dbg(ithc->pci, "reset completed\n"); + return 0; + } + +-static void ithc_stop(void *res) { ++static void ithc_stop(void *res) ++{ + struct ithc *ithc = res; + pci_dbg(ithc->pci, "stopping\n"); + ithc_log_regs(ithc); +- if (ithc->poll_thread) CHECK(kthread_stop, ithc->poll_thread); +- if (ithc->irq >= 0) disable_irq(ithc->irq); ++ ++ if (ithc->poll_thread) ++ CHECK(kthread_stop, ithc->poll_thread); ++ if (ithc->irq >= 0) ++ disable_irq(ithc->irq); + CHECK(ithc_set_device_enabled, ithc, false); + ithc_disable(ithc); +- del_timer_sync(&ithc->activity_timer); ++ hrtimer_cancel(&ithc->activity_start_timer); ++ hrtimer_cancel(&ithc->activity_end_timer); + cpu_latency_qos_remove_request(&ithc->activity_qos); +- // clear dma config +- for(unsigned i = 0; i < 2; i++) { ++ ++ // Clear DMA config. ++ for (unsigned int i = 0; i < 2; i++) { + CHECK(waitl, ithc, &ithc->regs->dma_rx[i].status, DMA_RX_STATUS_ENABLED, 0); + lo_hi_writeq(0, &ithc->regs->dma_rx[i].addr); + writeb(0, &ithc->regs->dma_rx[i].num_bufs); +@@ -383,35 +542,43 @@ static void ithc_stop(void *res) { + } + lo_hi_writeq(0, &ithc->regs->dma_tx.addr); + writeb(0, &ithc->regs->dma_tx.num_prds); ++ + ithc_log_regs(ithc); + pci_dbg(ithc->pci, "stopped\n"); + } + +-static void ithc_clear_drvdata(void *res) { ++static void ithc_clear_drvdata(void *res) ++{ + struct pci_dev *pci = res; + pci_set_drvdata(pci, NULL); + } + +-static int ithc_start(struct pci_dev *pci) { ++static int ithc_start(struct pci_dev *pci) ++{ + pci_dbg(pci, "starting\n"); + if (pci_get_drvdata(pci)) { + pci_err(pci, "device already initialized\n"); + return -EINVAL; + } +- if (!devres_open_group(&pci->dev, ithc_start, GFP_KERNEL)) return -ENOMEM; ++ if (!devres_open_group(&pci->dev, ithc_start, GFP_KERNEL)) ++ return -ENOMEM; + +- struct ithc *ithc = devm_kzalloc(&pci->dev, sizeof *ithc, GFP_KERNEL); +- if (!ithc) return -ENOMEM; ++ // Allocate/init main driver struct. ++ struct ithc *ithc = devm_kzalloc(&pci->dev, sizeof(*ithc), GFP_KERNEL); ++ if (!ithc) ++ return -ENOMEM; + ithc->irq = -1; + ithc->pci = pci; +- snprintf(ithc->phys, sizeof ithc->phys, "pci-%s/" DEVNAME, pci_name(pci)); ++ snprintf(ithc->phys, sizeof(ithc->phys), "pci-%s/" DEVNAME, pci_name(pci)); + init_waitqueue_head(&ithc->wait_hid_parse); + init_waitqueue_head(&ithc->wait_hid_get_feature); + mutex_init(&ithc->hid_get_feature_mutex); + pci_set_drvdata(pci, ithc); + CHECK_RET(devm_add_action_or_reset, &pci->dev, ithc_clear_drvdata, pci); +- if (ithc_log_regs_enabled) ithc->prev_regs = devm_kzalloc(&pci->dev, sizeof *ithc->prev_regs, GFP_KERNEL); ++ if (ithc_log_regs_enabled) ++ ithc->prev_regs = devm_kzalloc(&pci->dev, sizeof(*ithc->prev_regs), GFP_KERNEL); + ++ // PCI initialization. + CHECK_RET(pcim_enable_device, pci); + pci_set_master(pci); + CHECK_RET(pcim_iomap_regions, pci, BIT(0), DEVNAME " regs"); +@@ -419,29 +586,39 @@ static int ithc_start(struct pci_dev *pci) { + CHECK_RET(pci_set_power_state, pci, PCI_D0); + ithc->regs = pcim_iomap_table(pci)[0]; + ++ // Allocate IRQ. + if (!ithc_use_polling) { + CHECK_RET(pci_alloc_irq_vectors, pci, 1, 1, PCI_IRQ_MSI | PCI_IRQ_MSIX); + ithc->irq = CHECK(pci_irq_vector, pci, 0); +- if (ithc->irq < 0) return ithc->irq; ++ if (ithc->irq < 0) ++ return ithc->irq; + } + ++ // Initialize THC and touch device. + CHECK_RET(ithc_init_device, ithc); + CHECK(devm_device_add_groups, &pci->dev, ithc_attribute_groups); +- if (ithc_use_rx0) CHECK_RET(ithc_dma_rx_init, ithc, 0, ithc_use_rx1 ? DEVNAME "0" : DEVNAME); +- if (ithc_use_rx1) CHECK_RET(ithc_dma_rx_init, ithc, 1, ithc_use_rx0 ? DEVNAME "1" : DEVNAME); ++ if (ithc_use_rx0) ++ CHECK_RET(ithc_dma_rx_init, ithc, 0); ++ if (ithc_use_rx1) ++ CHECK_RET(ithc_dma_rx_init, ithc, 1); + CHECK_RET(ithc_dma_tx_init, ithc); + +- CHECK_RET(ithc_hid_init, ithc); +- + cpu_latency_qos_add_request(&ithc->activity_qos, PM_QOS_DEFAULT_VALUE); +- timer_setup(&ithc->activity_timer, ithc_activity_timer_callback, 0); ++ hrtimer_init(&ithc->activity_start_timer, CLOCK_MONOTONIC, HRTIMER_MODE_ABS); ++ ithc->activity_start_timer.function = ithc_activity_start_timer_callback; ++ hrtimer_init(&ithc->activity_end_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); ++ ithc->activity_end_timer.function = ithc_activity_end_timer_callback; + +- // add ithc_stop callback AFTER setting up DMA buffers, so that polling/irqs/DMA are disabled BEFORE the buffers are freed ++ // Add ithc_stop() callback AFTER setting up DMA buffers, so that polling/irqs/DMA are ++ // disabled BEFORE the buffers are freed. + CHECK_RET(devm_add_action_or_reset, &pci->dev, ithc_stop, ithc); + ++ CHECK_RET(ithc_hid_init, ithc); ++ ++ // Start polling/IRQ. + if (ithc_use_polling) { + pci_info(pci, "using polling instead of irq\n"); +- // use a thread instead of simple timer because we want to be able to sleep ++ // Use a thread instead of simple timer because we want to be able to sleep. + ithc->poll_thread = kthread_run(ithc_poll_thread, ithc, DEVNAME "poll"); + if (IS_ERR(ithc->poll_thread)) { + int err = PTR_ERR(ithc->poll_thread); +@@ -449,13 +626,17 @@ static int ithc_start(struct pci_dev *pci) { + return err; + } + } else { +- CHECK_RET(devm_request_threaded_irq, &pci->dev, ithc->irq, NULL, ithc_interrupt_thread, IRQF_TRIGGER_HIGH | IRQF_ONESHOT, DEVNAME, ithc); ++ CHECK_RET(devm_request_threaded_irq, &pci->dev, ithc->irq, NULL, ++ ithc_interrupt_thread, IRQF_TRIGGER_HIGH | IRQF_ONESHOT, DEVNAME, ithc); + } + +- if (ithc_use_rx0) ithc_dma_rx_enable(ithc, 0); +- if (ithc_use_rx1) ithc_dma_rx_enable(ithc, 1); ++ if (ithc_use_rx0) ++ ithc_dma_rx_enable(ithc, 0); ++ if (ithc_use_rx1) ++ ithc_dma_rx_enable(ithc, 1); + +- // hid_add_device can only be called after irq/polling is started and DMA is enabled, because it calls ithc_hid_parse which reads the report descriptor via DMA ++ // hid_add_device() can only be called after irq/polling is started and DMA is enabled, ++ // because it calls ithc_hid_parse() which reads the report descriptor via DMA. + CHECK_RET(hid_add_device, ithc->hid); + + CHECK(ithc_debug_init, ithc); +@@ -464,43 +645,54 @@ static int ithc_start(struct pci_dev *pci) { + return 0; + } + +-static int ithc_probe(struct pci_dev *pci, const struct pci_device_id *id) { ++static int ithc_probe(struct pci_dev *pci, const struct pci_device_id *id) ++{ + pci_dbg(pci, "device probe\n"); + return ithc_start(pci); + } + +-static void ithc_remove(struct pci_dev *pci) { ++static void ithc_remove(struct pci_dev *pci) ++{ + pci_dbg(pci, "device remove\n"); + // all cleanup is handled by devres + } + +-static int ithc_suspend(struct device *dev) { ++// For suspend/resume, we just deinitialize and reinitialize everything. ++// TODO It might be cleaner to keep the HID device around, however we would then have to signal ++// to userspace that the touch device has lost state and userspace needs to e.g. resend 'set ++// feature' requests. Hidraw does not seem to have a facility to do that. ++static int ithc_suspend(struct device *dev) ++{ + struct pci_dev *pci = to_pci_dev(dev); + pci_dbg(pci, "pm suspend\n"); + devres_release_group(dev, ithc_start); + return 0; + } + +-static int ithc_resume(struct device *dev) { ++static int ithc_resume(struct device *dev) ++{ + struct pci_dev *pci = to_pci_dev(dev); + pci_dbg(pci, "pm resume\n"); + return ithc_start(pci); + } + +-static int ithc_freeze(struct device *dev) { ++static int ithc_freeze(struct device *dev) ++{ + struct pci_dev *pci = to_pci_dev(dev); + pci_dbg(pci, "pm freeze\n"); + devres_release_group(dev, ithc_start); + return 0; + } + +-static int ithc_thaw(struct device *dev) { ++static int ithc_thaw(struct device *dev) ++{ + struct pci_dev *pci = to_pci_dev(dev); + pci_dbg(pci, "pm thaw\n"); + return ithc_start(pci); + } + +-static int ithc_restore(struct device *dev) { ++static int ithc_restore(struct device *dev) ++{ + struct pci_dev *pci = to_pci_dev(dev); + pci_dbg(pci, "pm restore\n"); + return ithc_start(pci); +@@ -521,11 +713,13 @@ static struct pci_driver ithc_driver = { + //.dev_groups = ithc_attribute_groups, // could use this (since 5.14), however the attributes won't have valid values until config has been read anyway + }; + +-static int __init ithc_init(void) { ++static int __init ithc_init(void) ++{ + return pci_register_driver(&ithc_driver); + } + +-static void __exit ithc_exit(void) { ++static void __exit ithc_exit(void) ++{ + pci_unregister_driver(&ithc_driver); + } + +diff --git a/drivers/hid/ithc/ithc-regs.c b/drivers/hid/ithc/ithc-regs.c +index 85d567b05761f..e058721886e37 100644 +--- a/drivers/hid/ithc/ithc-regs.c ++++ b/drivers/hid/ithc/ithc-regs.c +@@ -1,63 +1,95 @@ ++// SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause ++ + #include "ithc.h" + + #define reg_num(r) (0x1fff & (u16)(__force u64)(r)) + +-void bitsl(__iomem u32 *reg, u32 mask, u32 val) { +- if (val & ~mask) pr_err("register 0x%x: invalid value 0x%x for bitmask 0x%x\n", reg_num(reg), val, mask); ++void bitsl(__iomem u32 *reg, u32 mask, u32 val) ++{ ++ if (val & ~mask) ++ pr_err("register 0x%x: invalid value 0x%x for bitmask 0x%x\n", ++ reg_num(reg), val, mask); + writel((readl(reg) & ~mask) | (val & mask), reg); + } + +-void bitsb(__iomem u8 *reg, u8 mask, u8 val) { +- if (val & ~mask) pr_err("register 0x%x: invalid value 0x%x for bitmask 0x%x\n", reg_num(reg), val, mask); ++void bitsb(__iomem u8 *reg, u8 mask, u8 val) ++{ ++ if (val & ~mask) ++ pr_err("register 0x%x: invalid value 0x%x for bitmask 0x%x\n", ++ reg_num(reg), val, mask); + writeb((readb(reg) & ~mask) | (val & mask), reg); + } + +-int waitl(struct ithc *ithc, __iomem u32 *reg, u32 mask, u32 val) { +- pci_dbg(ithc->pci, "waiting for reg 0x%04x mask 0x%08x val 0x%08x\n", reg_num(reg), mask, val); ++int waitl(struct ithc *ithc, __iomem u32 *reg, u32 mask, u32 val) ++{ ++ pci_dbg(ithc->pci, "waiting for reg 0x%04x mask 0x%08x val 0x%08x\n", ++ reg_num(reg), mask, val); + u32 x; + if (readl_poll_timeout(reg, x, (x & mask) == val, 200, 1000*1000)) { +- pci_err(ithc->pci, "timed out waiting for reg 0x%04x mask 0x%08x val 0x%08x\n", reg_num(reg), mask, val); ++ pci_err(ithc->pci, "timed out waiting for reg 0x%04x mask 0x%08x val 0x%08x\n", ++ reg_num(reg), mask, val); + return -ETIMEDOUT; + } + pci_dbg(ithc->pci, "done waiting\n"); + return 0; + } + +-int waitb(struct ithc *ithc, __iomem u8 *reg, u8 mask, u8 val) { +- pci_dbg(ithc->pci, "waiting for reg 0x%04x mask 0x%02x val 0x%02x\n", reg_num(reg), mask, val); ++int waitb(struct ithc *ithc, __iomem u8 *reg, u8 mask, u8 val) ++{ ++ pci_dbg(ithc->pci, "waiting for reg 0x%04x mask 0x%02x val 0x%02x\n", ++ reg_num(reg), mask, val); + u8 x; + if (readb_poll_timeout(reg, x, (x & mask) == val, 200, 1000*1000)) { +- pci_err(ithc->pci, "timed out waiting for reg 0x%04x mask 0x%02x val 0x%02x\n", reg_num(reg), mask, val); ++ pci_err(ithc->pci, "timed out waiting for reg 0x%04x mask 0x%02x val 0x%02x\n", ++ reg_num(reg), mask, val); + return -ETIMEDOUT; + } + pci_dbg(ithc->pci, "done waiting\n"); + return 0; + } + +-int ithc_set_spi_config(struct ithc *ithc, u8 speed, u8 mode) { ++int ithc_set_spi_config(struct ithc *ithc, u8 speed, u8 mode) ++{ + pci_dbg(ithc->pci, "setting SPI speed to %i, mode %i\n", speed, mode); +- if (mode == 3) mode = 2; ++ if (mode == 3) ++ mode = 2; + bitsl(&ithc->regs->spi_config, + SPI_CONFIG_MODE(0xff) | SPI_CONFIG_SPEED(0xff) | SPI_CONFIG_UNKNOWN_18(0xff) | SPI_CONFIG_SPEED2(0xff), + SPI_CONFIG_MODE(mode) | SPI_CONFIG_SPEED(speed) | SPI_CONFIG_UNKNOWN_18(0) | SPI_CONFIG_SPEED2(speed)); + return 0; + } + +-int ithc_spi_command(struct ithc *ithc, u8 command, u32 offset, u32 size, void *data) { ++int ithc_spi_command(struct ithc *ithc, u8 command, u32 offset, u32 size, void *data) ++{ + pci_dbg(ithc->pci, "SPI command %u, size %u, offset %u\n", command, size, offset); +- if (size > sizeof ithc->regs->spi_cmd.data) return -EINVAL; ++ if (size > sizeof(ithc->regs->spi_cmd.data)) ++ return -EINVAL; ++ ++ // Wait if the device is still busy. + CHECK_RET(waitl, ithc, &ithc->regs->spi_cmd.status, SPI_CMD_STATUS_BUSY, 0); ++ // Clear result flags. + writel(SPI_CMD_STATUS_DONE | SPI_CMD_STATUS_ERROR, &ithc->regs->spi_cmd.status); ++ ++ // Init SPI command data. + writeb(command, &ithc->regs->spi_cmd.code); + writew(size, &ithc->regs->spi_cmd.size); + writel(offset, &ithc->regs->spi_cmd.offset); + u32 *p = data, n = (size + 3) / 4; +- for (u32 i = 0; i < n; i++) writel(p[i], &ithc->regs->spi_cmd.data[i]); ++ for (u32 i = 0; i < n; i++) ++ writel(p[i], &ithc->regs->spi_cmd.data[i]); ++ ++ // Start transmission. + bitsb_set(&ithc->regs->spi_cmd.control, SPI_CMD_CONTROL_SEND); + CHECK_RET(waitl, ithc, &ithc->regs->spi_cmd.status, SPI_CMD_STATUS_BUSY, 0); +- if ((readl(&ithc->regs->spi_cmd.status) & (SPI_CMD_STATUS_DONE | SPI_CMD_STATUS_ERROR)) != SPI_CMD_STATUS_DONE) return -EIO; +- if (readw(&ithc->regs->spi_cmd.size) != size) return -EMSGSIZE; +- for (u32 i = 0; i < n; i++) p[i] = readl(&ithc->regs->spi_cmd.data[i]); ++ ++ // Read response. ++ if ((readl(&ithc->regs->spi_cmd.status) & (SPI_CMD_STATUS_DONE | SPI_CMD_STATUS_ERROR)) != SPI_CMD_STATUS_DONE) ++ return -EIO; ++ if (readw(&ithc->regs->spi_cmd.size) != size) ++ return -EMSGSIZE; ++ for (u32 i = 0; i < n; i++) ++ p[i] = readl(&ithc->regs->spi_cmd.data[i]); ++ + writel(SPI_CMD_STATUS_DONE | SPI_CMD_STATUS_ERROR, &ithc->regs->spi_cmd.status); + return 0; + } +diff --git a/drivers/hid/ithc/ithc-regs.h b/drivers/hid/ithc/ithc-regs.h +index 1a96092ed7eed..d4007d9e2bacc 100644 +--- a/drivers/hid/ithc/ithc-regs.h ++++ b/drivers/hid/ithc/ithc-regs.h +@@ -1,3 +1,5 @@ ++/* SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause */ ++ + #define CONTROL_QUIESCE BIT(1) + #define CONTROL_IS_QUIESCED BIT(2) + #define CONTROL_NRESET BIT(3) +@@ -24,7 +26,7 @@ + + #define ERROR_FLAG_DMA_UNKNOWN_9 BIT(9) + #define ERROR_FLAG_DMA_UNKNOWN_10 BIT(10) +-#define ERROR_FLAG_DMA_UNKNOWN_12 BIT(12) // set when we receive a truncated DMA message ++#define ERROR_FLAG_DMA_RX_TIMEOUT BIT(12) // set when we receive a truncated DMA message + #define ERROR_FLAG_DMA_UNKNOWN_13 BIT(13) + #define ERROR_FLAG_SPI_BUS_TURNAROUND BIT(16) + #define ERROR_FLAG_SPI_RESPONSE_TIMEOUT BIT(17) +@@ -67,6 +69,7 @@ + #define DMA_RX_STATUS_HAVE_DATA BIT(5) + #define DMA_RX_STATUS_ENABLED BIT(8) + ++// COUNTER_RESET can be written to counter registers to reset them to zero. However, in some cases this can mess up the THC. + #define COUNTER_RESET BIT(31) + + struct ithc_registers { +@@ -147,15 +150,15 @@ static_assert(sizeof(struct ithc_registers) == 0x1300); + #define DEVCFG_SPI_MAX_FREQ(x) (((x) >> 1) & 0xf) // high bit = use high speed mode? + #define DEVCFG_SPI_MODE(x) (((x) >> 6) & 3) + #define DEVCFG_SPI_UNKNOWN_8(x) (((x) >> 8) & 0x3f) +-#define DEVCFG_SPI_NEEDS_HEARTBEAT BIT(20) +-#define DEVCFG_SPI_HEARTBEAT_INTERVAL (((x) >> 21) & 7) ++#define DEVCFG_SPI_NEEDS_HEARTBEAT BIT(20) // TODO implement heartbeat ++#define DEVCFG_SPI_HEARTBEAT_INTERVAL(x) (((x) >> 21) & 7) + #define DEVCFG_SPI_UNKNOWN_25 BIT(25) + #define DEVCFG_SPI_UNKNOWN_26 BIT(26) + #define DEVCFG_SPI_UNKNOWN_27 BIT(27) +-#define DEVCFG_SPI_DELAY (((x) >> 28) & 7) +-#define DEVCFG_SPI_USE_EXT_READ_CFG BIT(31) ++#define DEVCFG_SPI_DELAY(x) (((x) >> 28) & 7) // TODO use this ++#define DEVCFG_SPI_USE_EXT_READ_CFG BIT(31) // TODO use this? + +-struct ithc_device_config { ++struct ithc_device_config { // (Example values are from an SP7+.) + u32 _unknown_00; // 00 = 0xe0000402 (0xe0000401 after DMA_RX_CODE_RESET) + u32 _unknown_04; // 04 = 0x00000000 + u32 dma_buf_sizes; // 08 = 0x000a00ff +@@ -166,9 +169,9 @@ struct ithc_device_config { + u16 vendor_id; // 1c = 0x045e = Microsoft Corp. + u16 product_id; // 1e = 0x0c1a + u32 revision; // 20 = 0x00000001 +- u32 fw_version; // 24 = 0x05008a8b = 5.0.138.139 ++ u32 fw_version; // 24 = 0x05008a8b = 5.0.138.139 (this value looks more random on newer devices) + u32 _unknown_28; // 28 = 0x00000000 +- u32 fw_mode; // 2c = 0x00000000 ++ u32 fw_mode; // 2c = 0x00000000 (for fw update?) + u32 _unknown_30; // 30 = 0x00000000 + u32 _unknown_34; // 34 = 0x0404035e (u8,u8,u8,u8 = version?) + u32 _unknown_38; // 38 = 0x000001c0 (0x000001c1 after DMA_RX_CODE_RESET) +diff --git a/drivers/hid/ithc/ithc.h b/drivers/hid/ithc/ithc.h +index 6a9b0d480bc15..028e55a4ec53e 100644 +--- a/drivers/hid/ithc/ithc.h ++++ b/drivers/hid/ithc/ithc.h +@@ -1,3 +1,5 @@ ++/* SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause */ ++ + #include + #include + #include +@@ -21,7 +23,7 @@ + #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + + #define CHECK(fn, ...) ({ int r = fn(__VA_ARGS__); if (r < 0) pci_err(ithc->pci, "%s: %s failed with %i\n", __func__, #fn, r); r; }) +-#define CHECK_RET(...) do { int r = CHECK(__VA_ARGS__); if (r < 0) return r; } while(0) ++#define CHECK_RET(...) do { int r = CHECK(__VA_ARGS__); if (r < 0) return r; } while (0) + + #define NUM_RX_BUF 16 + +@@ -35,8 +37,13 @@ struct ithc { + struct pci_dev *pci; + int irq; + struct task_struct *poll_thread; ++ + struct pm_qos_request activity_qos; +- struct timer_list activity_timer; ++ struct hrtimer activity_start_timer; ++ struct hrtimer activity_end_timer; ++ ktime_t last_rx_time; ++ unsigned int cur_rx_seq_count; ++ unsigned int cur_rx_seq_errors; + + struct hid_device *hid; + bool hid_parse_done; +@@ -54,7 +61,7 @@ struct ithc { + }; + + int ithc_reset(struct ithc *ithc); +-void ithc_set_active(struct ithc *ithc); ++void ithc_set_active(struct ithc *ithc, unsigned int duration_us); + int ithc_debug_init(struct ithc *ithc); + void ithc_log_regs(struct ithc *ithc); + +-- +2.42.0 + +From c4cbbcd24ea10e6558753174ae6dabcc9b54e438 Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Sun, 22 Oct 2023 14:57:11 +0200 +Subject: [PATCH] platform/surface: aggregator_registry: Add support for + Surface Laptop Go 3 + +Add SAM client device nodes for the Surface Laptop Go 3. It seems to use +the same SAM client devices as the Surface Laptop Go 1 and 2, so re-use +their node group. + +Signed-off-by: Maximilian Luz +Patchset: surface-sam +--- + drivers/platform/surface/surface_aggregator_registry.c | 3 +++ + 1 file changed, 3 insertions(+) + +diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c +index 0fe5be5396525..0d8c8395c5886 100644 +--- a/drivers/platform/surface/surface_aggregator_registry.c ++++ b/drivers/platform/surface/surface_aggregator_registry.c +@@ -367,6 +367,9 @@ static const struct acpi_device_id ssam_platform_hub_match[] = { + /* Surface Laptop Go 2 */ + { "MSHW0290", (unsigned long)ssam_node_group_slg1 }, + ++ /* Surface Laptop Go 3 */ ++ { "MSHW0440", (unsigned long)ssam_node_group_slg1 }, ++ + /* Surface Laptop Studio */ + { "MSHW0123", (unsigned long)ssam_node_group_sls }, + +-- +2.42.0 + +From 0bb0adce3efad7a43fc3811f6cc24148c8c75253 Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Mon, 20 Nov 2023 19:47:00 +0100 +Subject: [PATCH] platform/surface: aggregator_registry: Add support for + Surface Laptop Studio 2 + +Add SAM client device nodes for the Surface Laptop Studio 2 (SLS2). The +SLS2 is quite similar to the SLS1, but it does not provide the touchpad +as a SAM-HID device. Therefore, add a new node group for the SLS2 and +update the comments accordingly + +Signed-off-by: Maximilian Luz +Patchset: surface-sam +--- + .../surface/surface_aggregator_registry.c | 25 ++++++++++++++++--- + 1 file changed, 21 insertions(+), 4 deletions(-) + +diff --git a/drivers/platform/surface/surface_aggregator_registry.c b/drivers/platform/surface/surface_aggregator_registry.c +index 0d8c8395c5886..530db4db71aba 100644 +--- a/drivers/platform/surface/surface_aggregator_registry.c ++++ b/drivers/platform/surface/surface_aggregator_registry.c +@@ -247,8 +247,8 @@ static const struct software_node *ssam_node_group_sl5[] = { + NULL, + }; + +-/* Devices for Surface Laptop Studio. */ +-static const struct software_node *ssam_node_group_sls[] = { ++/* Devices for Surface Laptop Studio 1. */ ++static const struct software_node *ssam_node_group_sls1[] = { + &ssam_node_root, + &ssam_node_bat_ac, + &ssam_node_bat_main, +@@ -263,6 +263,20 @@ static const struct software_node *ssam_node_group_sls[] = { + NULL, + }; + ++/* Devices for Surface Laptop Studio 2. */ ++static const struct software_node *ssam_node_group_sls2[] = { ++ &ssam_node_root, ++ &ssam_node_bat_ac, ++ &ssam_node_bat_main, ++ &ssam_node_tmp_pprof, ++ &ssam_node_pos_tablet_switch, ++ &ssam_node_hid_sam_keyboard, ++ &ssam_node_hid_sam_penstash, ++ &ssam_node_hid_sam_sensors, ++ &ssam_node_hid_sam_ucm_ucsi, ++ NULL, ++}; ++ + /* Devices for Surface Laptop Go. */ + static const struct software_node *ssam_node_group_slg1[] = { + &ssam_node_root, +@@ -370,8 +384,11 @@ static const struct acpi_device_id ssam_platform_hub_match[] = { + /* Surface Laptop Go 3 */ + { "MSHW0440", (unsigned long)ssam_node_group_slg1 }, + +- /* Surface Laptop Studio */ +- { "MSHW0123", (unsigned long)ssam_node_group_sls }, ++ /* Surface Laptop Studio 1 */ ++ { "MSHW0123", (unsigned long)ssam_node_group_sls1 }, ++ ++ /* Surface Laptop Studio 2 */ ++ { "MSHW0360", (unsigned long)ssam_node_group_sls2 }, + + { }, + }; +-- +2.42.0 + +From 3772b511c710c369b737fd0a111fbda63b028f1d Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Sat, 25 Jul 2020 17:19:53 +0200 +Subject: [PATCH] i2c: acpi: Implement RawBytes read access + +Microsoft Surface Pro 4 and Book 1 devices access the MSHW0030 I2C +device via a generic serial bus operation region and RawBytes read +access. On the Surface Book 1, this access is required to turn on (and +off) the discrete GPU. + +Multiple things are to note here: + +a) The RawBytes access is device/driver dependent. The ACPI + specification states: + + > Raw accesses assume that the writer has knowledge of the bus that + > the access is made over and the device that is being accessed. The + > protocol may only ensure that the buffer is transmitted to the + > appropriate driver, but the driver must be able to interpret the + > buffer to communicate to a register. + + Thus this implementation may likely not work on other devices + accessing I2C via the RawBytes accessor type. + +b) The MSHW0030 I2C device is an HID-over-I2C device which seems to + serve multiple functions: + + 1. It is the main access point for the legacy-type Surface Aggregator + Module (also referred to as SAM-over-HID, as opposed to the newer + SAM-over-SSH/UART). It has currently not been determined on how + support for the legacy SAM should be implemented. Likely via a + custom HID driver. + + 2. It seems to serve as the HID device for the Integrated Sensor Hub. + This might complicate matters with regards to implementing a + SAM-over-HID driver required by legacy SAM. + +In light of this, the simplest approach has been chosen for now. +However, it may make more sense regarding breakage and compatibility to +either provide functionality for replacing or enhancing the default +operation region handler via some additional API functions, or even to +completely blacklist MSHW0030 from the I2C core and provide a custom +driver for it. + +Replacing/enhancing the default operation region handler would, however, +either require some sort of secondary driver and access point for it, +from which the new API functions would be called and the new handler +(part) would be installed, or hard-coding them via some sort of +quirk-like interface into the I2C core. + +Signed-off-by: Maximilian Luz +Patchset: surface-sam-over-hid +--- + drivers/i2c/i2c-core-acpi.c | 35 +++++++++++++++++++++++++++++++++++ + 1 file changed, 35 insertions(+) + +diff --git a/drivers/i2c/i2c-core-acpi.c b/drivers/i2c/i2c-core-acpi.c +index d6037a3286690..a290ebc77aea2 100644 +--- a/drivers/i2c/i2c-core-acpi.c ++++ b/drivers/i2c/i2c-core-acpi.c +@@ -628,6 +628,28 @@ static int acpi_gsb_i2c_write_bytes(struct i2c_client *client, return (ret == 1) ? 0 : -EIO; } @@ -4888,11 +7066,10 @@ diff '--color=auto' -uraN cachyos/drivers/i2c/i2c-core-acpi.c cachyos-surface/dr static acpi_status i2c_acpi_space_handler(u32 function, acpi_physical_address command, u32 bits, u64 *value64, -@@ -728,6 +750,19 @@ - gsb->data, info->access_length); +@@ -729,6 +751,19 @@ i2c_acpi_space_handler(u32 function, acpi_physical_address command, } break; -+ + + case ACPI_GSB_ACCESS_ATTRIB_RAW_BYTES: + if (action == ACPI_READ) { + dev_warn(&adapter->dev, @@ -4905,13 +7082,262 @@ diff '--color=auto' -uraN cachyos/drivers/i2c/i2c-core-acpi.c cachyos-surface/dr + gsb->data, info->access_length); + } + break; - ++ default: dev_warn(&adapter->dev, "protocol 0x%02x not supported for client 0x%02x\n", -diff '--color=auto' -uraN cachyos/drivers/input/misc/soc_button_array.c cachyos-surface/drivers/input/misc/soc_button_array.c ---- cachyos/drivers/input/misc/soc_button_array.c 2023-10-30 05:31:08.000000000 +0300 -+++ cachyos-surface/drivers/input/misc/soc_button_array.c 2023-11-04 18:32:41.044321279 +0300 -@@ -537,8 +537,8 @@ + accessor_type, client->addr); +-- +2.42.0 + +From f45a16750118da615fca44e7214204c83631ee7f Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Sat, 13 Feb 2021 16:41:18 +0100 +Subject: [PATCH] platform/surface: Add driver for Surface Book 1 dGPU switch + +Add driver exposing the discrete GPU power-switch of the Microsoft +Surface Book 1 to user-space. + +On the Surface Book 1, the dGPU power is controlled via the Surface +System Aggregator Module (SAM). The specific SAM-over-HID command for +this is exposed via ACPI. This module provides a simple driver exposing +the ACPI call via a sysfs parameter to user-space, so that users can +easily power-on/-off the dGPU. + +Patchset: surface-sam-over-hid +--- + drivers/platform/surface/Kconfig | 7 + + drivers/platform/surface/Makefile | 1 + + .../surface/surfacebook1_dgpu_switch.c | 162 ++++++++++++++++++ + 3 files changed, 170 insertions(+) + create mode 100644 drivers/platform/surface/surfacebook1_dgpu_switch.c + +diff --git a/drivers/platform/surface/Kconfig b/drivers/platform/surface/Kconfig +index b629e82af97c0..68656e8f309ed 100644 +--- a/drivers/platform/surface/Kconfig ++++ b/drivers/platform/surface/Kconfig +@@ -149,6 +149,13 @@ config SURFACE_AGGREGATOR_TABLET_SWITCH + Select M or Y here, if you want to provide tablet-mode switch input + events on the Surface Pro 8, Surface Pro X, and Surface Laptop Studio. + ++config SURFACE_BOOK1_DGPU_SWITCH ++ tristate "Surface Book 1 dGPU Switch Driver" ++ depends on SYSFS ++ help ++ This driver provides a sysfs switch to set the power-state of the ++ discrete GPU found on the Microsoft Surface Book 1. ++ + config SURFACE_DTX + tristate "Surface DTX (Detachment System) Driver" + depends on SURFACE_AGGREGATOR +diff --git a/drivers/platform/surface/Makefile b/drivers/platform/surface/Makefile +index 53344330939bf..7efcd0cdb5329 100644 +--- a/drivers/platform/surface/Makefile ++++ b/drivers/platform/surface/Makefile +@@ -12,6 +12,7 @@ obj-$(CONFIG_SURFACE_AGGREGATOR_CDEV) += surface_aggregator_cdev.o + obj-$(CONFIG_SURFACE_AGGREGATOR_HUB) += surface_aggregator_hub.o + obj-$(CONFIG_SURFACE_AGGREGATOR_REGISTRY) += surface_aggregator_registry.o + obj-$(CONFIG_SURFACE_AGGREGATOR_TABLET_SWITCH) += surface_aggregator_tabletsw.o ++obj-$(CONFIG_SURFACE_BOOK1_DGPU_SWITCH) += surfacebook1_dgpu_switch.o + obj-$(CONFIG_SURFACE_DTX) += surface_dtx.o + obj-$(CONFIG_SURFACE_GPE) += surface_gpe.o + obj-$(CONFIG_SURFACE_HOTPLUG) += surface_hotplug.o +diff --git a/drivers/platform/surface/surfacebook1_dgpu_switch.c b/drivers/platform/surface/surfacebook1_dgpu_switch.c +new file mode 100644 +index 0000000000000..8b816ed8f35c6 +--- /dev/null ++++ b/drivers/platform/surface/surfacebook1_dgpu_switch.c +@@ -0,0 +1,162 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++ ++#include ++#include ++#include ++#include ++ ++ ++#ifdef pr_fmt ++#undef pr_fmt ++#endif ++#define pr_fmt(fmt) "%s:%s: " fmt, KBUILD_MODNAME, __func__ ++ ++ ++static const guid_t dgpu_sw_guid = GUID_INIT(0x6fd05c69, 0xcde3, 0x49f4, ++ 0x95, 0xed, 0xab, 0x16, 0x65, 0x49, 0x80, 0x35); ++ ++#define DGPUSW_ACPI_PATH_DSM "\\_SB_.PCI0.LPCB.EC0_.VGBI" ++#define DGPUSW_ACPI_PATH_HGON "\\_SB_.PCI0.RP05.HGON" ++#define DGPUSW_ACPI_PATH_HGOF "\\_SB_.PCI0.RP05.HGOF" ++ ++ ++static int sb1_dgpu_sw_dsmcall(void) ++{ ++ union acpi_object *ret; ++ acpi_handle handle; ++ acpi_status status; ++ ++ status = acpi_get_handle(NULL, DGPUSW_ACPI_PATH_DSM, &handle); ++ if (status) ++ return -EINVAL; ++ ++ ret = acpi_evaluate_dsm_typed(handle, &dgpu_sw_guid, 1, 1, NULL, ACPI_TYPE_BUFFER); ++ if (!ret) ++ return -EINVAL; ++ ++ ACPI_FREE(ret); ++ return 0; ++} ++ ++static int sb1_dgpu_sw_hgon(void) ++{ ++ struct acpi_buffer buf = {ACPI_ALLOCATE_BUFFER, NULL}; ++ acpi_status status; ++ ++ status = acpi_evaluate_object(NULL, DGPUSW_ACPI_PATH_HGON, NULL, &buf); ++ if (status) { ++ pr_err("failed to run HGON: %d\n", status); ++ return -EINVAL; ++ } ++ ++ if (buf.pointer) ++ ACPI_FREE(buf.pointer); ++ ++ pr_info("turned-on dGPU via HGON\n"); ++ return 0; ++} ++ ++static int sb1_dgpu_sw_hgof(void) ++{ ++ struct acpi_buffer buf = {ACPI_ALLOCATE_BUFFER, NULL}; ++ acpi_status status; ++ ++ status = acpi_evaluate_object(NULL, DGPUSW_ACPI_PATH_HGOF, NULL, &buf); ++ if (status) { ++ pr_err("failed to run HGOF: %d\n", status); ++ return -EINVAL; ++ } ++ ++ if (buf.pointer) ++ ACPI_FREE(buf.pointer); ++ ++ pr_info("turned-off dGPU via HGOF\n"); ++ return 0; ++} ++ ++ ++static ssize_t dgpu_dsmcall_store(struct device *dev, struct device_attribute *attr, ++ const char *buf, size_t len) ++{ ++ int status, value; ++ ++ status = kstrtoint(buf, 0, &value); ++ if (status < 0) ++ return status; ++ ++ if (value != 1) ++ return -EINVAL; ++ ++ status = sb1_dgpu_sw_dsmcall(); ++ ++ return status < 0 ? status : len; ++} ++ ++static ssize_t dgpu_power_store(struct device *dev, struct device_attribute *attr, ++ const char *buf, size_t len) ++{ ++ bool power; ++ int status; ++ ++ status = kstrtobool(buf, &power); ++ if (status < 0) ++ return status; ++ ++ if (power) ++ status = sb1_dgpu_sw_hgon(); ++ else ++ status = sb1_dgpu_sw_hgof(); ++ ++ return status < 0 ? status : len; ++} ++ ++static DEVICE_ATTR_WO(dgpu_dsmcall); ++static DEVICE_ATTR_WO(dgpu_power); ++ ++static struct attribute *sb1_dgpu_sw_attrs[] = { ++ &dev_attr_dgpu_dsmcall.attr, ++ &dev_attr_dgpu_power.attr, ++ NULL, ++}; ++ ++static const struct attribute_group sb1_dgpu_sw_attr_group = { ++ .attrs = sb1_dgpu_sw_attrs, ++}; ++ ++ ++static int sb1_dgpu_sw_probe(struct platform_device *pdev) ++{ ++ return sysfs_create_group(&pdev->dev.kobj, &sb1_dgpu_sw_attr_group); ++} ++ ++static int sb1_dgpu_sw_remove(struct platform_device *pdev) ++{ ++ sysfs_remove_group(&pdev->dev.kobj, &sb1_dgpu_sw_attr_group); ++ return 0; ++} ++ ++/* ++ * The dGPU power seems to be actually handled by MSHW0040. However, that is ++ * also the power-/volume-button device with a mainline driver. So let's use ++ * MSHW0041 instead for now, which seems to be the LTCH (latch/DTX) device. ++ */ ++static const struct acpi_device_id sb1_dgpu_sw_match[] = { ++ { "MSHW0041", }, ++ { }, ++}; ++MODULE_DEVICE_TABLE(acpi, sb1_dgpu_sw_match); ++ ++static struct platform_driver sb1_dgpu_sw = { ++ .probe = sb1_dgpu_sw_probe, ++ .remove = sb1_dgpu_sw_remove, ++ .driver = { ++ .name = "surfacebook1_dgpu_switch", ++ .acpi_match_table = sb1_dgpu_sw_match, ++ .probe_type = PROBE_PREFER_ASYNCHRONOUS, ++ }, ++}; ++module_platform_driver(sb1_dgpu_sw); ++ ++MODULE_AUTHOR("Maximilian Luz "); ++MODULE_DESCRIPTION("Discrete GPU Power-Switch for Surface Book 1"); ++MODULE_LICENSE("GPL"); +-- +2.42.0 + +From a5d9cf4762a27e2bf7f38c0d5a223b9df8b4ba8a Mon Sep 17 00:00:00 2001 +From: Sachi King +Date: Tue, 5 Oct 2021 00:05:09 +1100 +Subject: [PATCH] Input: soc_button_array - support AMD variant Surface devices + +The power button on the AMD variant of the Surface Laptop uses the +same MSHW0040 device ID as the 5th and later generation of Surface +devices, however they report 0 for their OEM platform revision. As the +_DSM does not exist on the devices requiring special casing, check for +the existance of the _DSM to determine if soc_button_array should be +loaded. + +Fixes: c394159310d0 ("Input: soc_button_array - add support for newer surface devices") +Co-developed-by: Maximilian Luz + +Signed-off-by: Sachi King +Patchset: surface-button +--- + drivers/input/misc/soc_button_array.c | 33 +++++++-------------------- + 1 file changed, 8 insertions(+), 25 deletions(-) + +diff --git a/drivers/input/misc/soc_button_array.c b/drivers/input/misc/soc_button_array.c +index e79f5497948b8..2bddbe6e9ea4d 100644 +--- a/drivers/input/misc/soc_button_array.c ++++ b/drivers/input/misc/soc_button_array.c +@@ -537,8 +537,8 @@ static const struct soc_device_data soc_device_MSHW0028 = { * Both, the Surface Pro 4 (surfacepro3_button.c) and the above mentioned * devices use MSHW0040 for power and volume buttons, however the way they * have to be addressed differs. Make sure that we only load this drivers @@ -4922,24 +7348,19 @@ diff '--color=auto' -uraN cachyos/drivers/input/misc/soc_button_array.c cachyos- */ #define MSHW0040_DSM_REVISION 0x01 #define MSHW0040_DSM_GET_OMPR 0x02 // get OEM Platform Revision -@@ -549,31 +549,14 @@ +@@ -549,31 +549,14 @@ static const guid_t MSHW0040_DSM_UUID = static int soc_device_check_MSHW0040(struct device *dev) { acpi_handle handle = ACPI_HANDLE(dev); - union acpi_object *result; - u64 oem_platform_rev = 0; // valid revisions are nonzero -+ bool exists; - +- - // get OEM platform revision - result = acpi_evaluate_dsm_typed(handle, &MSHW0040_DSM_UUID, - MSHW0040_DSM_REVISION, - MSHW0040_DSM_GET_OMPR, NULL, - ACPI_TYPE_INTEGER); -+ // check if OEM platform revision DSM call exists -+ exists = acpi_check_dsm(handle, &MSHW0040_DSM_UUID, -+ MSHW0040_DSM_REVISION, -+ BIT(MSHW0040_DSM_GET_OMPR)); - +- - if (result) { - oem_platform_rev = result->integer.value; - ACPI_FREE(result); @@ -4952,18 +7373,926 @@ diff '--color=auto' -uraN cachyos/drivers/input/misc/soc_button_array.c cachyos- - */ - if (oem_platform_rev == 0) - return -ENODEV; -- ++ bool exists; + - dev_dbg(dev, "OEM Platform Revision %llu\n", oem_platform_rev); -- ++ // check if OEM platform revision DSM call exists ++ exists = acpi_check_dsm(handle, &MSHW0040_DSM_UUID, ++ MSHW0040_DSM_REVISION, ++ BIT(MSHW0040_DSM_GET_OMPR)); + - return 0; + return exists ? 0 : -ENODEV; } /* -diff '--color=auto' -uraN cachyos/drivers/iommu/intel/iommu.c cachyos-surface/drivers/iommu/intel/iommu.c ---- cachyos/drivers/iommu/intel/iommu.c 2023-10-30 05:31:08.000000000 +0300 -+++ cachyos-surface/drivers/iommu/intel/iommu.c 2023-11-04 18:32:41.047654661 +0300 -@@ -38,6 +38,14 @@ +-- +2.42.0 + +From 66f0a34801ad81ff08cc3ae0e175e0958959c461 Mon Sep 17 00:00:00 2001 +From: Sachi King +Date: Tue, 5 Oct 2021 00:22:57 +1100 +Subject: [PATCH] platform/surface: surfacepro3_button: don't load on amd + variant + +The AMD variant of the Surface Laptop report 0 for their OEM platform +revision. The Surface devices that require the surfacepro3_button +driver do not have the _DSM that gets the OEM platform revision. If the +method does not exist, load surfacepro3_button. + +Fixes: 64dd243d7356 ("platform/x86: surfacepro3_button: Fix device check") +Co-developed-by: Maximilian Luz + +Signed-off-by: Sachi King +Patchset: surface-button +--- + drivers/platform/surface/surfacepro3_button.c | 30 ++++--------------- + 1 file changed, 6 insertions(+), 24 deletions(-) + +diff --git a/drivers/platform/surface/surfacepro3_button.c b/drivers/platform/surface/surfacepro3_button.c +index 2755601f979cd..4240c98ca2265 100644 +--- a/drivers/platform/surface/surfacepro3_button.c ++++ b/drivers/platform/surface/surfacepro3_button.c +@@ -149,7 +149,8 @@ static int surface_button_resume(struct device *dev) + /* + * Surface Pro 4 and Surface Book 2 / Surface Pro 2017 use the same device + * ID (MSHW0040) for the power/volume buttons. Make sure this is the right +- * device by checking for the _DSM method and OEM Platform Revision. ++ * device by checking for the _DSM method and OEM Platform Revision DSM ++ * function. + * + * Returns true if the driver should bind to this device, i.e. the device is + * either MSWH0028 (Pro 3) or MSHW0040 on a Pro 4 or Book 1. +@@ -157,30 +158,11 @@ static int surface_button_resume(struct device *dev) + static bool surface_button_check_MSHW0040(struct acpi_device *dev) + { + acpi_handle handle = dev->handle; +- union acpi_object *result; +- u64 oem_platform_rev = 0; // valid revisions are nonzero +- +- // get OEM platform revision +- result = acpi_evaluate_dsm_typed(handle, &MSHW0040_DSM_UUID, +- MSHW0040_DSM_REVISION, +- MSHW0040_DSM_GET_OMPR, +- NULL, ACPI_TYPE_INTEGER); +- +- /* +- * If evaluating the _DSM fails, the method is not present. This means +- * that we have either MSHW0028 or MSHW0040 on Pro 4 or Book 1, so we +- * should use this driver. We use revision 0 indicating it is +- * unavailable. +- */ +- +- if (result) { +- oem_platform_rev = result->integer.value; +- ACPI_FREE(result); +- } +- +- dev_dbg(&dev->dev, "OEM Platform Revision %llu\n", oem_platform_rev); + +- return oem_platform_rev == 0; ++ // make sure that OEM platform revision DSM call does not exist ++ return !acpi_check_dsm(handle, &MSHW0040_DSM_UUID, ++ MSHW0040_DSM_REVISION, ++ BIT(MSHW0040_DSM_GET_OMPR)); + } + + +-- +2.42.0 + +From a55587ce4f5065bedb604f9031082ad47612a163 Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Sat, 18 Feb 2023 01:02:49 +0100 +Subject: [PATCH] USB: quirks: Add USB_QUIRK_DELAY_INIT for Surface Go 3 + Type-Cover + +The touchpad on the Type-Cover of the Surface Go 3 is sometimes not +being initialized properly. Apply USB_QUIRK_DELAY_INIT to fix this +issue. + +More specifically, the device in question is a fairly standard modern +touchpad with pointer and touchpad input modes. During setup, the device +needs to be switched from pointer- to touchpad-mode (which is done in +hid-multitouch) to fully utilize it as intended. Unfortunately, however, +this seems to occasionally fail silently, leaving the device in +pointer-mode. Applying USB_QUIRK_DELAY_INIT seems to fix this. + +Link: https://github.com/linux-surface/linux-surface/issues/1059 +Signed-off-by: Maximilian Luz +Patchset: surface-typecover +--- + drivers/usb/core/quirks.c | 3 +++ + 1 file changed, 3 insertions(+) + +diff --git a/drivers/usb/core/quirks.c b/drivers/usb/core/quirks.c +index 15e9bd180a1d2..0d70461d01e16 100644 +--- a/drivers/usb/core/quirks.c ++++ b/drivers/usb/core/quirks.c +@@ -220,6 +220,9 @@ static const struct usb_device_id usb_quirk_list[] = { + /* Microsoft Surface Dock Ethernet (RTL8153 GigE) */ + { USB_DEVICE(0x045e, 0x07c6), .driver_info = USB_QUIRK_NO_LPM }, + ++ /* Microsoft Surface Go 3 Type-Cover */ ++ { USB_DEVICE(0x045e, 0x09b5), .driver_info = USB_QUIRK_DELAY_INIT }, ++ + /* Cherry Stream G230 2.0 (G85-231) and 3.0 (G85-232) */ + { USB_DEVICE(0x046a, 0x0023), .driver_info = USB_QUIRK_RESET_RESUME }, + +-- +2.42.0 + +From 678999792d6b1c72e56c6b63fc3909b93db47b32 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Jonas=20Dre=C3=9Fler?= +Date: Thu, 5 Nov 2020 13:09:45 +0100 +Subject: [PATCH] hid/multitouch: Turn off Type Cover keyboard backlight when + suspending + +The Type Cover for Microsoft Surface devices supports a special usb +control request to disable or enable the built-in keyboard backlight. +On Windows, this request happens when putting the device into suspend or +resuming it, without it the backlight of the Type Cover will remain +enabled for some time even though the computer is suspended, which looks +weird to the user. + +So add support for this special usb control request to hid-multitouch, +which is the driver that's handling the Type Cover. + +The reason we have to use a pm_notifier for this instead of the usual +suspend/resume methods is that those won't get called in case the usb +device is already autosuspended. + +Also, if the device is autosuspended, we have to briefly autoresume it +in order to send the request. Doing that should be fine, the usb-core +driver does something similar during suspend inside choose_wakeup(). + +To make sure we don't send that request to every device but only to +devices which support it, add a new quirk +MT_CLS_WIN_8_MS_SURFACE_TYPE_COVER to hid-multitouch. For now this quirk +is only enabled for the usb id of the Surface Pro 2017 Type Cover, which +is where I confirmed that it's working. + +Patchset: surface-typecover +--- + drivers/hid/hid-multitouch.c | 100 ++++++++++++++++++++++++++++++++++- + 1 file changed, 98 insertions(+), 2 deletions(-) + +diff --git a/drivers/hid/hid-multitouch.c b/drivers/hid/hid-multitouch.c +index 8db4ae05febc8..99a5efef45258 100644 +--- a/drivers/hid/hid-multitouch.c ++++ b/drivers/hid/hid-multitouch.c +@@ -34,7 +34,10 @@ + #include + #include + #include ++#include + #include ++#include ++#include + #include + #include + #include +@@ -47,6 +50,7 @@ MODULE_DESCRIPTION("HID multitouch panels"); + MODULE_LICENSE("GPL"); + + #include "hid-ids.h" ++#include "usbhid/usbhid.h" + + /* quirks to control the device */ + #define MT_QUIRK_NOT_SEEN_MEANS_UP BIT(0) +@@ -72,12 +76,15 @@ MODULE_LICENSE("GPL"); + #define MT_QUIRK_FORCE_MULTI_INPUT BIT(20) + #define MT_QUIRK_DISABLE_WAKEUP BIT(21) + #define MT_QUIRK_ORIENTATION_INVERT BIT(22) ++#define MT_QUIRK_HAS_TYPE_COVER_BACKLIGHT BIT(23) + + #define MT_INPUTMODE_TOUCHSCREEN 0x02 + #define MT_INPUTMODE_TOUCHPAD 0x03 + + #define MT_BUTTONTYPE_CLICKPAD 0 + ++#define MS_TYPE_COVER_FEATURE_REPORT_USAGE 0xff050086 ++ + enum latency_mode { + HID_LATENCY_NORMAL = 0, + HID_LATENCY_HIGH = 1, +@@ -169,6 +176,8 @@ struct mt_device { + + struct list_head applications; + struct list_head reports; ++ ++ struct notifier_block pm_notifier; + }; + + static void mt_post_parse_default_settings(struct mt_device *td, +@@ -213,6 +222,7 @@ static void mt_post_parse(struct mt_device *td, struct mt_application *app); + #define MT_CLS_GOOGLE 0x0111 + #define MT_CLS_RAZER_BLADE_STEALTH 0x0112 + #define MT_CLS_SMART_TECH 0x0113 ++#define MT_CLS_WIN_8_MS_SURFACE_TYPE_COVER 0x0114 + + #define MT_DEFAULT_MAXCONTACT 10 + #define MT_MAX_MAXCONTACT 250 +@@ -397,6 +407,16 @@ static const struct mt_class mt_classes[] = { + MT_QUIRK_CONTACT_CNT_ACCURATE | + MT_QUIRK_SEPARATE_APP_REPORT, + }, ++ { .name = MT_CLS_WIN_8_MS_SURFACE_TYPE_COVER, ++ .quirks = MT_QUIRK_HAS_TYPE_COVER_BACKLIGHT | ++ MT_QUIRK_ALWAYS_VALID | ++ MT_QUIRK_IGNORE_DUPLICATES | ++ MT_QUIRK_HOVERING | ++ MT_QUIRK_CONTACT_CNT_ACCURATE | ++ MT_QUIRK_STICKY_FINGERS | ++ MT_QUIRK_WIN8_PTP_BUTTONS, ++ .export_all_inputs = true ++ }, + { } + }; + +@@ -1721,6 +1741,69 @@ static void mt_expired_timeout(struct timer_list *t) + clear_bit_unlock(MT_IO_FLAGS_RUNNING, &td->mt_io_flags); + } + ++static void get_type_cover_backlight_field(struct hid_device *hdev, ++ struct hid_field **field) ++{ ++ struct hid_report_enum *rep_enum; ++ struct hid_report *rep; ++ struct hid_field *cur_field; ++ int i, j; ++ ++ rep_enum = &hdev->report_enum[HID_FEATURE_REPORT]; ++ list_for_each_entry(rep, &rep_enum->report_list, list) { ++ for (i = 0; i < rep->maxfield; i++) { ++ cur_field = rep->field[i]; ++ ++ for (j = 0; j < cur_field->maxusage; j++) { ++ if (cur_field->usage[j].hid ++ == MS_TYPE_COVER_FEATURE_REPORT_USAGE) { ++ *field = cur_field; ++ return; ++ } ++ } ++ } ++ } ++} ++ ++static void update_keyboard_backlight(struct hid_device *hdev, bool enabled) ++{ ++ struct usb_device *udev = hid_to_usb_dev(hdev); ++ struct hid_field *field = NULL; ++ ++ /* Wake up the device in case it's already suspended */ ++ pm_runtime_get_sync(&udev->dev); ++ ++ get_type_cover_backlight_field(hdev, &field); ++ if (!field) { ++ hid_err(hdev, "couldn't find backlight field\n"); ++ goto out; ++ } ++ ++ field->value[field->index] = enabled ? 0x01ff00ff : 0x00ff00ff; ++ hid_hw_request(hdev, field->report, HID_REQ_SET_REPORT); ++ ++out: ++ pm_runtime_put_sync(&udev->dev); ++} ++ ++static int mt_pm_notifier(struct notifier_block *notifier, ++ unsigned long pm_event, ++ void *unused) ++{ ++ struct mt_device *td = ++ container_of(notifier, struct mt_device, pm_notifier); ++ struct hid_device *hdev = td->hdev; ++ ++ if (td->mtclass.quirks & MT_QUIRK_HAS_TYPE_COVER_BACKLIGHT) { ++ if (pm_event == PM_SUSPEND_PREPARE) ++ update_keyboard_backlight(hdev, 0); ++ else if (pm_event == PM_POST_SUSPEND) ++ update_keyboard_backlight(hdev, 1); ++ } ++ ++ return NOTIFY_DONE; ++} ++ + static int mt_probe(struct hid_device *hdev, const struct hid_device_id *id) + { + int ret, i; +@@ -1744,6 +1827,9 @@ static int mt_probe(struct hid_device *hdev, const struct hid_device_id *id) + td->inputmode_value = MT_INPUTMODE_TOUCHSCREEN; + hid_set_drvdata(hdev, td); + ++ td->pm_notifier.notifier_call = mt_pm_notifier; ++ register_pm_notifier(&td->pm_notifier); ++ + INIT_LIST_HEAD(&td->applications); + INIT_LIST_HEAD(&td->reports); + +@@ -1782,15 +1868,19 @@ static int mt_probe(struct hid_device *hdev, const struct hid_device_id *id) + timer_setup(&td->release_timer, mt_expired_timeout, 0); + + ret = hid_parse(hdev); +- if (ret != 0) ++ if (ret != 0) { ++ unregister_pm_notifier(&td->pm_notifier); + return ret; ++ } + + if (mtclass->quirks & MT_QUIRK_FIX_CONST_CONTACT_ID) + mt_fix_const_fields(hdev, HID_DG_CONTACTID); + + ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT); +- if (ret) ++ if (ret) { ++ unregister_pm_notifier(&td->pm_notifier); + return ret; ++ } + + ret = sysfs_create_group(&hdev->dev.kobj, &mt_attribute_group); + if (ret) +@@ -1842,6 +1932,7 @@ static void mt_remove(struct hid_device *hdev) + { + struct mt_device *td = hid_get_drvdata(hdev); + ++ unregister_pm_notifier(&td->pm_notifier); + del_timer_sync(&td->release_timer); + + sysfs_remove_group(&hdev->dev.kobj, &mt_attribute_group); +@@ -2223,6 +2314,11 @@ static const struct hid_device_id mt_devices[] = { + MT_USB_DEVICE(USB_VENDOR_ID_XIROKU, + USB_DEVICE_ID_XIROKU_CSR2) }, + ++ /* Microsoft Surface type cover */ ++ { .driver_data = MT_CLS_WIN_8_MS_SURFACE_TYPE_COVER, ++ HID_DEVICE(HID_BUS_ANY, HID_GROUP_ANY, ++ USB_VENDOR_ID_MICROSOFT, 0x09c0) }, ++ + /* Google MT devices */ + { .driver_data = MT_CLS_GOOGLE, + HID_DEVICE(HID_BUS_ANY, HID_GROUP_ANY, USB_VENDOR_ID_GOOGLE, +-- +2.42.0 + +From 12427f01e38ebf653ccf44faefdcb92110c43c20 Mon Sep 17 00:00:00 2001 +From: PJungkamp +Date: Fri, 25 Feb 2022 12:04:25 +0100 +Subject: [PATCH] hid/multitouch: Add support for surface pro type cover tablet + switch + +The Surface Pro Type Cover has several non standard HID usages in it's +hid report descriptor. +I noticed that, upon folding the typecover back, a vendor specific range +of 4 32 bit integer hid usages is transmitted. +Only the first byte of the message seems to convey reliable information +about the keyboard state. + +0x22 => Normal (keys enabled) +0x33 => Folded back (keys disabled) +0x53 => Rotated left/right side up (keys disabled) +0x13 => Cover closed (keys disabled) +0x43 => Folded back and Tablet upside down (keys disabled) +This list may not be exhaustive. + +The tablet mode switch will be disabled for a value of 0x22 and enabled +on any other value. + +Patchset: surface-typecover +--- + drivers/hid/hid-multitouch.c | 148 +++++++++++++++++++++++++++++------ + 1 file changed, 122 insertions(+), 26 deletions(-) + +diff --git a/drivers/hid/hid-multitouch.c b/drivers/hid/hid-multitouch.c +index 99a5efef45258..6ae43ea90bcd5 100644 +--- a/drivers/hid/hid-multitouch.c ++++ b/drivers/hid/hid-multitouch.c +@@ -77,6 +77,7 @@ MODULE_LICENSE("GPL"); + #define MT_QUIRK_DISABLE_WAKEUP BIT(21) + #define MT_QUIRK_ORIENTATION_INVERT BIT(22) + #define MT_QUIRK_HAS_TYPE_COVER_BACKLIGHT BIT(23) ++#define MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH BIT(24) + + #define MT_INPUTMODE_TOUCHSCREEN 0x02 + #define MT_INPUTMODE_TOUCHPAD 0x03 +@@ -84,6 +85,8 @@ MODULE_LICENSE("GPL"); + #define MT_BUTTONTYPE_CLICKPAD 0 + + #define MS_TYPE_COVER_FEATURE_REPORT_USAGE 0xff050086 ++#define MS_TYPE_COVER_TABLET_MODE_SWITCH_USAGE 0xff050072 ++#define MS_TYPE_COVER_APPLICATION 0xff050050 + + enum latency_mode { + HID_LATENCY_NORMAL = 0, +@@ -409,6 +412,7 @@ static const struct mt_class mt_classes[] = { + }, + { .name = MT_CLS_WIN_8_MS_SURFACE_TYPE_COVER, + .quirks = MT_QUIRK_HAS_TYPE_COVER_BACKLIGHT | ++ MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH | + MT_QUIRK_ALWAYS_VALID | + MT_QUIRK_IGNORE_DUPLICATES | + MT_QUIRK_HOVERING | +@@ -1390,6 +1394,9 @@ static int mt_input_mapping(struct hid_device *hdev, struct hid_input *hi, + field->application != HID_CP_CONSUMER_CONTROL && + field->application != HID_GD_WIRELESS_RADIO_CTLS && + field->application != HID_GD_SYSTEM_MULTIAXIS && ++ !(field->application == MS_TYPE_COVER_APPLICATION && ++ application->quirks & MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH && ++ usage->hid == MS_TYPE_COVER_TABLET_MODE_SWITCH_USAGE) && + !(field->application == HID_VD_ASUS_CUSTOM_MEDIA_KEYS && + application->quirks & MT_QUIRK_ASUS_CUSTOM_UP)) + return -1; +@@ -1417,6 +1424,21 @@ static int mt_input_mapping(struct hid_device *hdev, struct hid_input *hi, + return 1; + } + ++ /* ++ * The Microsoft Surface Pro Typecover has a non-standard HID ++ * tablet mode switch on a vendor specific usage page with vendor ++ * specific usage. ++ */ ++ if (field->application == MS_TYPE_COVER_APPLICATION && ++ application->quirks & MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH && ++ usage->hid == MS_TYPE_COVER_TABLET_MODE_SWITCH_USAGE) { ++ usage->type = EV_SW; ++ usage->code = SW_TABLET_MODE; ++ *max = SW_MAX; ++ *bit = hi->input->swbit; ++ return 1; ++ } ++ + if (rdata->is_mt_collection) + return mt_touch_input_mapping(hdev, hi, field, usage, bit, max, + application); +@@ -1438,6 +1460,7 @@ static int mt_input_mapped(struct hid_device *hdev, struct hid_input *hi, + { + struct mt_device *td = hid_get_drvdata(hdev); + struct mt_report_data *rdata; ++ struct input_dev *input; + + rdata = mt_find_report_data(td, field->report); + if (rdata && rdata->is_mt_collection) { +@@ -1445,6 +1468,19 @@ static int mt_input_mapped(struct hid_device *hdev, struct hid_input *hi, + return -1; + } + ++ /* ++ * We own an input device which acts as a tablet mode switch for ++ * the Surface Pro Typecover. ++ */ ++ if (field->application == MS_TYPE_COVER_APPLICATION && ++ rdata->application->quirks & MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH && ++ usage->hid == MS_TYPE_COVER_TABLET_MODE_SWITCH_USAGE) { ++ input = hi->input; ++ input_set_capability(input, EV_SW, SW_TABLET_MODE); ++ input_report_switch(input, SW_TABLET_MODE, 0); ++ return -1; ++ } ++ + /* let hid-core decide for the others */ + return 0; + } +@@ -1454,11 +1490,21 @@ static int mt_event(struct hid_device *hid, struct hid_field *field, + { + struct mt_device *td = hid_get_drvdata(hid); + struct mt_report_data *rdata; ++ struct input_dev *input; + + rdata = mt_find_report_data(td, field->report); + if (rdata && rdata->is_mt_collection) + return mt_touch_event(hid, field, usage, value); + ++ if (field->application == MS_TYPE_COVER_APPLICATION && ++ rdata->application->quirks & MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH && ++ usage->hid == MS_TYPE_COVER_TABLET_MODE_SWITCH_USAGE) { ++ input = field->hidinput->input; ++ input_report_switch(input, SW_TABLET_MODE, (value & 0xFF) != 0x22); ++ input_sync(input); ++ return 1; ++ } ++ + return 0; + } + +@@ -1611,6 +1657,42 @@ static void mt_post_parse(struct mt_device *td, struct mt_application *app) + app->quirks &= ~MT_QUIRK_CONTACT_CNT_ACCURATE; + } + ++static int get_type_cover_field(struct hid_report_enum *rep_enum, ++ struct hid_field **field, int usage) ++{ ++ struct hid_report *rep; ++ struct hid_field *cur_field; ++ int i, j; ++ ++ list_for_each_entry(rep, &rep_enum->report_list, list) { ++ for (i = 0; i < rep->maxfield; i++) { ++ cur_field = rep->field[i]; ++ if (cur_field->application != MS_TYPE_COVER_APPLICATION) ++ continue; ++ for (j = 0; j < cur_field->maxusage; j++) { ++ if (cur_field->usage[j].hid == usage) { ++ *field = cur_field; ++ return true; ++ } ++ } ++ } ++ } ++ return false; ++} ++ ++static void request_type_cover_tablet_mode_switch(struct hid_device *hdev) ++{ ++ struct hid_field *field; ++ ++ if (get_type_cover_field(&hdev->report_enum[HID_INPUT_REPORT], ++ &field, ++ MS_TYPE_COVER_TABLET_MODE_SWITCH_USAGE)) { ++ hid_hw_request(hdev, field->report, HID_REQ_GET_REPORT); ++ } else { ++ hid_err(hdev, "couldn't find tablet mode field\n"); ++ } ++} ++ + static int mt_input_configured(struct hid_device *hdev, struct hid_input *hi) + { + struct mt_device *td = hid_get_drvdata(hdev); +@@ -1659,6 +1741,13 @@ static int mt_input_configured(struct hid_device *hdev, struct hid_input *hi) + /* force BTN_STYLUS to allow tablet matching in udev */ + __set_bit(BTN_STYLUS, hi->input->keybit); + break; ++ case MS_TYPE_COVER_APPLICATION: ++ if (td->mtclass.quirks & MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH) { ++ suffix = "Tablet Mode Switch"; ++ request_type_cover_tablet_mode_switch(hdev); ++ break; ++ } ++ fallthrough; + default: + suffix = "UNKNOWN"; + break; +@@ -1741,30 +1830,6 @@ static void mt_expired_timeout(struct timer_list *t) + clear_bit_unlock(MT_IO_FLAGS_RUNNING, &td->mt_io_flags); + } + +-static void get_type_cover_backlight_field(struct hid_device *hdev, +- struct hid_field **field) +-{ +- struct hid_report_enum *rep_enum; +- struct hid_report *rep; +- struct hid_field *cur_field; +- int i, j; +- +- rep_enum = &hdev->report_enum[HID_FEATURE_REPORT]; +- list_for_each_entry(rep, &rep_enum->report_list, list) { +- for (i = 0; i < rep->maxfield; i++) { +- cur_field = rep->field[i]; +- +- for (j = 0; j < cur_field->maxusage; j++) { +- if (cur_field->usage[j].hid +- == MS_TYPE_COVER_FEATURE_REPORT_USAGE) { +- *field = cur_field; +- return; +- } +- } +- } +- } +-} +- + static void update_keyboard_backlight(struct hid_device *hdev, bool enabled) + { + struct usb_device *udev = hid_to_usb_dev(hdev); +@@ -1773,8 +1838,9 @@ static void update_keyboard_backlight(struct hid_device *hdev, bool enabled) + /* Wake up the device in case it's already suspended */ + pm_runtime_get_sync(&udev->dev); + +- get_type_cover_backlight_field(hdev, &field); +- if (!field) { ++ if (!get_type_cover_field(&hdev->report_enum[HID_FEATURE_REPORT], ++ &field, ++ MS_TYPE_COVER_FEATURE_REPORT_USAGE)) { + hid_err(hdev, "couldn't find backlight field\n"); + goto out; + } +@@ -1909,13 +1975,24 @@ static int mt_suspend(struct hid_device *hdev, pm_message_t state) + + static int mt_reset_resume(struct hid_device *hdev) + { ++ struct mt_device *td = hid_get_drvdata(hdev); ++ + mt_release_contacts(hdev); + mt_set_modes(hdev, HID_LATENCY_NORMAL, true, true); ++ ++ /* Request an update on the typecover folding state on resume ++ * after reset. ++ */ ++ if (td->mtclass.quirks & MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH) ++ request_type_cover_tablet_mode_switch(hdev); ++ + return 0; + } + + static int mt_resume(struct hid_device *hdev) + { ++ struct mt_device *td = hid_get_drvdata(hdev); ++ + /* Some Elan legacy devices require SET_IDLE to be set on resume. + * It should be safe to send it to other devices too. + * Tested on 3M, Stantum, Cypress, Zytronic, eGalax, and Elan panels. */ +@@ -1924,6 +2001,10 @@ static int mt_resume(struct hid_device *hdev) + + mt_set_modes(hdev, HID_LATENCY_NORMAL, true, true); + ++ /* Request an update on the typecover folding state on resume. */ ++ if (td->mtclass.quirks & MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH) ++ request_type_cover_tablet_mode_switch(hdev); ++ + return 0; + } + #endif +@@ -1931,6 +2012,21 @@ static int mt_resume(struct hid_device *hdev) + static void mt_remove(struct hid_device *hdev) + { + struct mt_device *td = hid_get_drvdata(hdev); ++ struct hid_field *field; ++ struct input_dev *input; ++ ++ /* Reset tablet mode switch on disconnect. */ ++ if (td->mtclass.quirks & MT_QUIRK_HAS_TYPE_COVER_TABLET_MODE_SWITCH) { ++ if (get_type_cover_field(&hdev->report_enum[HID_INPUT_REPORT], ++ &field, ++ MS_TYPE_COVER_TABLET_MODE_SWITCH_USAGE)) { ++ input = field->hidinput->input; ++ input_report_switch(input, SW_TABLET_MODE, 0); ++ input_sync(input); ++ } else { ++ hid_err(hdev, "couldn't find tablet mode field\n"); ++ } ++ } + + unregister_pm_notifier(&td->pm_notifier); + del_timer_sync(&td->release_timer); +-- +2.42.0 + +From 151f9dba2f3d6d066d160128da109a0173a3ff4c Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Sun, 19 Feb 2023 22:12:24 +0100 +Subject: [PATCH] PCI: Add quirk to prevent calling shutdown mehtod + +Work around buggy EFI firmware: On some Microsoft Surface devices +(Surface Pro 9 and Surface Laptop 5) the EFI ResetSystem call with +EFI_RESET_SHUTDOWN doesn't function properly. Instead of shutting the +system down, it returns and the system stays on. + +It turns out that this only happens after PCI shutdown callbacks ran for +specific devices. Excluding those devices from the shutdown process +makes the ResetSystem call work as expected. + +TODO: Maybe we can find a better way or the root cause of this? + +Not-Signed-off-by: Maximilian Luz +Patchset: surface-shutdown +--- + drivers/pci/pci-driver.c | 3 +++ + drivers/pci/quirks.c | 36 ++++++++++++++++++++++++++++++++++++ + include/linux/pci.h | 1 + + 3 files changed, 40 insertions(+) + +diff --git a/drivers/pci/pci-driver.c b/drivers/pci/pci-driver.c +index 51ec9e7e784f0..40554890d7211 100644 +--- a/drivers/pci/pci-driver.c ++++ b/drivers/pci/pci-driver.c +@@ -507,6 +507,9 @@ static void pci_device_shutdown(struct device *dev) + struct pci_dev *pci_dev = to_pci_dev(dev); + struct pci_driver *drv = pci_dev->driver; + ++ if (pci_dev->no_shutdown) ++ return; ++ + pm_runtime_resume(dev); + + if (drv && drv->shutdown) +diff --git a/drivers/pci/quirks.c b/drivers/pci/quirks.c +index ae95d0950..7a6d76c41 100644 +--- a/drivers/pci/quirks.c ++++ b/drivers/pci/quirks.c +@@ -6212,6 +6212,42 @@ DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_XILINX, 0x5020, of_pci_make_dev_node); + DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_XILINX, 0x5021, of_pci_make_dev_node); + DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_REDHAT, 0x0005, of_pci_make_dev_node); + ++static const struct dmi_system_id no_shutdown_dmi_table[] = { ++ /* ++ * Systems on which some devices should not be touched during shutdown. ++ */ ++ { ++ .ident = "Microsoft Surface Pro 9", ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), ++ DMI_MATCH(DMI_PRODUCT_NAME, "Surface Pro 9"), ++ }, ++ }, ++ { ++ .ident = "Microsoft Surface Laptop 5", ++ .matches = { ++ DMI_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), ++ DMI_MATCH(DMI_PRODUCT_NAME, "Surface Laptop 5"), ++ }, ++ }, ++ {} ++}; ++ ++static void quirk_no_shutdown(struct pci_dev *dev) ++{ ++ if (!dmi_check_system(no_shutdown_dmi_table)) ++ return; ++ ++ dev->no_shutdown = 1; ++ pci_info(dev, "disabling shutdown ops for [%04x:%04x]\n", ++ dev->vendor, dev->device); ++} ++DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x461e, quirk_no_shutdown); // Thunderbolt 4 USB Controller ++DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x461f, quirk_no_shutdown); // Thunderbolt 4 PCI Express Root Port ++DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x462f, quirk_no_shutdown); // Thunderbolt 4 PCI Express Root Port ++DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x466d, quirk_no_shutdown); // Thunderbolt 4 NHI ++DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x46a8, quirk_no_shutdown); // GPU ++ + /* + * Devices known to require a longer delay before first config space access + * after reset recovery or resume from D3cold: + +diff --git a/include/linux/pci.h b/include/linux/pci.h +index 8c7c2c3c6c652..0c223b04dff91 100644 +--- a/include/linux/pci.h ++++ b/include/linux/pci.h +@@ -465,6 +465,7 @@ struct pci_dev { + unsigned int no_command_memory:1; /* No PCI_COMMAND_MEMORY */ + unsigned int rom_bar_overlap:1; /* ROM BAR disable broken */ + unsigned int rom_attr_enabled:1; /* Display of ROM attribute enabled? */ ++ unsigned int no_shutdown:1; /* Do not touch device on shutdown */ + pci_dev_flags_t dev_flags; + atomic_t enable_cnt; /* pci_enable_device has been called */ + +-- +2.42.0 + +From 912e956823b3cadd7203d3ce94418d162ff701be Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Sun, 12 Mar 2023 01:41:57 +0100 +Subject: [PATCH] platform/surface: gpe: Add support for Surface Pro 9 + +Add the lid GPE used by the Surface Pro 9. + +Signed-off-by: Maximilian Luz +Patchset: surface-gpe +--- + drivers/platform/surface/surface_gpe.c | 17 +++++++++++++++++ + 1 file changed, 17 insertions(+) + +diff --git a/drivers/platform/surface/surface_gpe.c b/drivers/platform/surface/surface_gpe.c +index c219b840d491a..69c4352e8406b 100644 +--- a/drivers/platform/surface/surface_gpe.c ++++ b/drivers/platform/surface/surface_gpe.c +@@ -41,6 +41,11 @@ static const struct property_entry lid_device_props_l4F[] = { + {}, + }; + ++static const struct property_entry lid_device_props_l52[] = { ++ PROPERTY_ENTRY_U32("gpe", 0x52), ++ {}, ++}; ++ + static const struct property_entry lid_device_props_l57[] = { + PROPERTY_ENTRY_U32("gpe", 0x57), + {}, +@@ -107,6 +112,18 @@ static const struct dmi_system_id dmi_lid_device_table[] = { + }, + .driver_data = (void *)lid_device_props_l4B, + }, ++ { ++ /* ++ * We match for SKU here due to product name clash with the ARM ++ * version. ++ */ ++ .ident = "Surface Pro 9", ++ .matches = { ++ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), ++ DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_9_2038"), ++ }, ++ .driver_data = (void *)lid_device_props_l52, ++ }, + { + .ident = "Surface Book 1", + .matches = { +-- +2.42.0 + +From df083025f8c63824279c19de8ec3339440f819c9 Mon Sep 17 00:00:00 2001 +From: Hans de Goede +Date: Sun, 10 Oct 2021 20:56:57 +0200 +Subject: [PATCH] ACPI: delay enumeration of devices with a _DEP pointing to an + INT3472 device + +The clk and regulator frameworks expect clk/regulator consumer-devices +to have info about the consumed clks/regulators described in the device's +fw_node. + +To work around cases where this info is not present in the firmware tables, +which is often the case on x86/ACPI devices, both frameworks allow the +provider-driver to attach info about consumers to the clks/regulators +when registering these. + +This causes problems with the probe ordering wrt drivers for consumers +of these clks/regulators. Since the lookups are only registered when the +provider-driver binds, trying to get these clks/regulators before then +results in a -ENOENT error for clks and a dummy regulator for regulators. + +One case where we hit this issue is camera sensors such as e.g. the OV8865 +sensor found on the Microsoft Surface Go. The sensor uses clks, regulators +and GPIOs provided by a TPS68470 PMIC which is described in an INT3472 +ACPI device. There is special platform code handling this and setting +platform_data with the necessary consumer info on the MFD cells +instantiated for the PMIC under: drivers/platform/x86/intel/int3472. + +For this to work properly the ov8865 driver must not bind to the I2C-client +for the OV8865 sensor until after the TPS68470 PMIC gpio, regulator and +clk MFD cells have all been fully setup. + +The OV8865 on the Microsoft Surface Go is just one example, all X86 +devices using the Intel IPU3 camera block found on recent Intel SoCs +have similar issues where there is an INT3472 HID ACPI-device, which +describes the clks and regulators, and the driver for this INT3472 device +must be fully initialized before the sensor driver (any sensor driver) +binds for things to work properly. + +On these devices the ACPI nodes describing the sensors all have a _DEP +dependency on the matching INT3472 ACPI device (there is one per sensor). + +This allows solving the probe-ordering problem by delaying the enumeration +(instantiation of the I2C-client in the ov8865 example) of ACPI-devices +which have a _DEP dependency on an INT3472 device. + +The new acpi_dev_ready_for_enumeration() helper used for this is also +exported because for devices, which have the enumeration_by_parent flag +set, the parent-driver will do its own scan of child ACPI devices and +it will try to enumerate those during its probe(). Code doing this such +as e.g. the i2c-core-acpi.c code must call this new helper to ensure +that it too delays the enumeration until all the _DEP dependencies are +met on devices which have the new honor_deps flag set. + +Signed-off-by: Hans de Goede +Patchset: cameras +--- + drivers/acpi/scan.c | 3 +++ + 1 file changed, 3 insertions(+) + +diff --git a/drivers/acpi/scan.c b/drivers/acpi/scan.c +index 691d4b7686ee7..9283217689279 100644 +--- a/drivers/acpi/scan.c ++++ b/drivers/acpi/scan.c +@@ -2108,6 +2108,9 @@ static acpi_status acpi_bus_check_add_2(acpi_handle handle, u32 lvl_not_used, + + static void acpi_default_enumeration(struct acpi_device *device) + { ++ if (!acpi_dev_ready_for_enumeration(device)) ++ return; ++ + /* + * Do not enumerate devices with enumeration_by_parent flag set as + * they will be enumerated by their respective parents. +-- +2.42.0 + +From 87650a001d3068a8b614fd688e21bb87c2d3a3e6 Mon Sep 17 00:00:00 2001 +From: zouxiaoh +Date: Fri, 25 Jun 2021 08:52:59 +0800 +Subject: [PATCH] iommu: intel-ipu: use IOMMU passthrough mode for Intel IPUs + +Intel IPU(Image Processing Unit) has its own (IO)MMU hardware, +The IPU driver allocates its own page table that is not mapped +via the DMA, and thus the Intel IOMMU driver blocks access giving +this error: DMAR: DRHD: handling fault status reg 3 DMAR: +[DMA Read] Request device [00:05.0] PASID ffffffff +fault addr 76406000 [fault reason 06] PTE Read access is not set +As IPU is not an external facing device which is not risky, so use +IOMMU passthrough mode for Intel IPUs. + +Change-Id: I6dcccdadac308cf42e20a18e1b593381391e3e6b +Depends-On: Iacd67578e8c6a9b9ac73285f52b4081b72fb68a6 +Tracked-On: #JIITL8-411 +Signed-off-by: Bingbu Cao +Signed-off-by: zouxiaoh +Signed-off-by: Xu Chongyang +Patchset: cameras +--- + drivers/iommu/intel/iommu.c | 30 ++++++++++++++++++++++++++++++ + 1 file changed, 30 insertions(+) + +diff --git a/drivers/iommu/intel/iommu.c b/drivers/iommu/intel/iommu.c +index 5a627e081797c..da866ac6b30ba 100644 +--- a/drivers/iommu/intel/iommu.c ++++ b/drivers/iommu/intel/iommu.c +@@ -38,6 +38,12 @@ #define IS_GFX_DEVICE(pdev) ((pdev->class >> 16) == PCI_BASE_CLASS_DISPLAY) #define IS_USB_DEVICE(pdev) ((pdev->class >> 8) == PCI_CLASS_SERIAL_USB) #define IS_ISA_DEVICE(pdev) ((pdev->class >> 8) == PCI_CLASS_BRIDGE_ISA) @@ -4973,16 +8302,13 @@ diff '--color=auto' -uraN cachyos/drivers/iommu/intel/iommu.c cachyos-surface/dr + (pdev)->device == 0x4e19 || \ + (pdev)->device == 0x465d || \ + (pdev)->device == 0x1919)) -+#define IS_IPTS(pdev) ((pdev)->vendor == PCI_VENDOR_ID_INTEL && \ -+ ((pdev)->device == 0x9d3e)) + #define IS_IPTS(pdev) ((pdev)->vendor == PCI_VENDOR_ID_INTEL && \ + ((pdev)->device == 0x9d3e)) #define IS_AZALIA(pdev) ((pdev)->vendor == 0x8086 && (pdev)->device == 0x3a3e) - - #define IOAPIC_RANGE_START (0xfee00000) -@@ -292,12 +300,16 @@ - EXPORT_SYMBOL_GPL(intel_iommu_enabled); +@@ -295,12 +301,14 @@ EXPORT_SYMBOL_GPL(intel_iommu_enabled); static int dmar_map_gfx = 1; -+static int dmar_map_ipts = 1; + static int dmar_map_ipts = 1; +static int dmar_map_ipu = 1; static int intel_iommu_superpage = 1; static int iommu_identity_mapping; @@ -4991,37 +8317,30 @@ diff '--color=auto' -uraN cachyos/drivers/iommu/intel/iommu.c cachyos-surface/dr #define IDENTMAP_GFX 2 #define IDENTMAP_AZALIA 4 +#define IDENTMAP_IPU 8 -+#define IDENTMAP_IPTS 16 + #define IDENTMAP_IPTS 16 const struct iommu_ops intel_iommu_ops; - -@@ -2542,6 +2554,12 @@ - +@@ -2547,6 +2555,9 @@ static int device_def_domain_type(struct device *dev) if ((iommu_identity_mapping & IDENTMAP_GFX) && IS_GFX_DEVICE(pdev)) return IOMMU_DOMAIN_IDENTITY; -+ + + if ((iommu_identity_mapping & IDENTMAP_IPU) && IS_INTEL_IPU(pdev)) + return IOMMU_DOMAIN_IDENTITY; + -+ if ((iommu_identity_mapping & IDENTMAP_IPTS) && IS_IPTS(pdev)) -+ return IOMMU_DOMAIN_IDENTITY; + if ((iommu_identity_mapping & IDENTMAP_IPTS) && IS_IPTS(pdev)) + return IOMMU_DOMAIN_IDENTITY; } - - return 0; -@@ -2849,6 +2867,12 @@ +@@ -2856,6 +2867,9 @@ static int __init init_dmars(void) if (!dmar_map_gfx) iommu_identity_mapping |= IDENTMAP_GFX; + if (!dmar_map_ipu) + iommu_identity_mapping |= IDENTMAP_IPU; + -+ if (!dmar_map_ipts) -+ iommu_identity_mapping |= IDENTMAP_IPTS; -+ - check_tylersburg_isoch(); + if (!dmar_map_ipts) + iommu_identity_mapping |= IDENTMAP_IPTS; - ret = si_domain_init(hw_pass_through); -@@ -4828,6 +4852,30 @@ +@@ -4838,6 +4852,18 @@ static void quirk_iommu_igfx(struct pci_dev *dev) dmar_map_gfx = 0; } @@ -5037,64 +8356,322 @@ diff '--color=auto' -uraN cachyos/drivers/iommu/intel/iommu.c cachyos-surface/dr + dmar_map_ipu = 0; +} + -+static void quirk_iommu_ipts(struct pci_dev *dev) -+{ -+ if (!IS_IPTS(dev)) -+ return; -+ -+ if (risky_device(dev)) -+ return; -+ -+ pci_info(dev, "Passthrough IOMMU for IPTS\n"); -+ dmar_map_ipts = 0; -+} + static void quirk_iommu_ipts(struct pci_dev *dev) + { + if (!IS_IPTS(dev)) +@@ -4849,6 +4875,7 @@ static void quirk_iommu_ipts(struct pci_dev *dev) + pci_info(dev, "Passthrough IOMMU for IPTS\n"); + dmar_map_ipts = 0; + } + /* G4x/GM45 integrated gfx dmar support is totally busted. */ DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x2a40, quirk_iommu_igfx); DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x2e00, quirk_iommu_igfx); -@@ -4863,6 +4911,12 @@ +@@ -4884,6 +4911,9 @@ DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x1632, quirk_iommu_igfx); DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x163A, quirk_iommu_igfx); DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x163D, quirk_iommu_igfx); +/* disable IPU dmar support */ +DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, PCI_ANY_ID, quirk_iommu_ipu); + -+/* disable IPTS dmar support */ -+DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x9D3E, quirk_iommu_ipts); -+ - static void quirk_iommu_rwbf(struct pci_dev *dev) - { - if (risky_device(dev)) -diff '--color=auto' -uraN cachyos/drivers/iommu/intel/irq_remapping.c cachyos-surface/drivers/iommu/intel/irq_remapping.c ---- cachyos/drivers/iommu/intel/irq_remapping.c 2023-10-30 05:31:08.000000000 +0300 -+++ cachyos-surface/drivers/iommu/intel/irq_remapping.c 2023-11-04 18:32:41.040987898 +0300 -@@ -387,6 +387,22 @@ - pci_for_each_dma_alias(dev, set_msi_sid_cb, &data); + /* disable IPTS dmar support */ + DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x9D3E, quirk_iommu_ipts); - /* -+ * The Intel Touch Host Controller is at 00:10.6, but for some reason -+ * the MSI interrupts have request id 01:05.0. -+ * Disable id verification to work around this. -+ * FIXME Find proper fix or turn this into a quirk. -+ */ -+ if (dev->vendor == PCI_VENDOR_ID_INTEL && (dev->class >> 8) == PCI_CLASS_INPUT_PEN) { -+ switch(dev->device) { -+ case 0x98d0: case 0x98d1: // LKF -+ case 0xa0d0: case 0xa0d1: // TGL LP -+ case 0x43d0: case 0x43d1: // TGL H -+ set_irte_sid(irte, SVT_NO_VERIFY, SQ_ALL_16, 0); -+ return 0; -+ } +-- +2.42.0 + +From 76fec27d978bf7708a60862d4aab2e1fe7ec3f27 Mon Sep 17 00:00:00 2001 +From: Daniel Scally +Date: Sun, 10 Oct 2021 20:57:02 +0200 +Subject: [PATCH] platform/x86: int3472: Enable I2c daisy chain + +The TPS68470 PMIC has an I2C passthrough mode through which I2C traffic +can be forwarded to a device connected to the PMIC as though it were +connected directly to the system bus. Enable this mode when the chip +is initialised. + +Signed-off-by: Daniel Scally +Patchset: cameras +--- + drivers/platform/x86/intel/int3472/tps68470.c | 7 +++++++ + 1 file changed, 7 insertions(+) + +diff --git a/drivers/platform/x86/intel/int3472/tps68470.c b/drivers/platform/x86/intel/int3472/tps68470.c +index 1e107fd49f828..e3e1696e7f0ee 100644 +--- a/drivers/platform/x86/intel/int3472/tps68470.c ++++ b/drivers/platform/x86/intel/int3472/tps68470.c +@@ -46,6 +46,13 @@ static int tps68470_chip_init(struct device *dev, struct regmap *regmap) + return ret; + } + ++ /* Enable I2C daisy chain */ ++ ret = regmap_write(regmap, TPS68470_REG_S_I2C_CTL, 0x03); ++ if (ret) { ++ dev_err(dev, "Failed to enable i2c daisy chain\n"); ++ return ret; + } + + dev_info(dev, "TPS68470 REVID: 0x%02x\n", version); + + return 0; +-- +2.42.0 + +From 232a0f88ecc21141c6f0d94cc74eb63c7869c217 Mon Sep 17 00:00:00 2001 +From: Daniel Scally +Date: Thu, 2 Mar 2023 12:59:39 +0000 +Subject: [PATCH] platform/x86: int3472: Remap reset GPIO for INT347E + +ACPI _HID INT347E represents the OmniVision 7251 camera sensor. The +driver for this sensor expects a single pin named "enable", but on +some Microsoft Surface platforms the sensor is assigned a single +GPIO who's type flag is INT3472_GPIO_TYPE_RESET. + +Remap the GPIO pin's function from "reset" to "enable". This is done +outside of the existing remap table since it is a more widespread +discrepancy than that method is designed for. Additionally swap the +polarity of the pin to match the driver's expectation. + +Signed-off-by: Daniel Scally +Patchset: cameras +--- + drivers/platform/x86/intel/int3472/discrete.c | 14 ++++++++++++++ + 1 file changed, 14 insertions(+) + +diff --git a/drivers/platform/x86/intel/int3472/discrete.c b/drivers/platform/x86/intel/int3472/discrete.c +index e33c2d75975cf..c0c90ae66b705 100644 +--- a/drivers/platform/x86/intel/int3472/discrete.c ++++ b/drivers/platform/x86/intel/int3472/discrete.c +@@ -57,6 +57,9 @@ static int skl_int3472_map_gpio_to_sensor(struct int3472_discrete_device *int347 + const char *func, u32 polarity) + { + char *path = agpio->resource_source.string_ptr; ++ const struct acpi_device_id ov7251_ids[] = { ++ { "INT347E" }, ++ }; + struct gpiod_lookup *table_entry; + struct acpi_device *adev; + acpi_handle handle; +@@ -67,6 +70,17 @@ static int skl_int3472_map_gpio_to_sensor(struct int3472_discrete_device *int347 + return -EINVAL; + } + + /* - * DMA alias provides us with a PCI device and alias. The only case - * where the it will return an alias on a different bus than the - * device is the case of a PCIe-to-PCI bridge, where the alias is for -diff '--color=auto' -uraN cachyos/drivers/leds/Kconfig cachyos-surface/drivers/leds/Kconfig ---- cachyos/drivers/leds/Kconfig 2023-10-30 05:31:08.000000000 +0300 -+++ cachyos-surface/drivers/leds/Kconfig 2023-11-04 18:32:41.054321425 +0300 -@@ -873,6 +873,18 @@ ++ * In addition to the function remap table we need to bulk remap the ++ * "reset" GPIO for the OmniVision 7251 sensor, as the driver for that ++ * expects its only GPIO pin to be called "enable" (and to have the ++ * opposite polarity). ++ */ ++ if (!strcmp(func, "reset") && !acpi_match_device_ids(int3472->sensor, ov7251_ids)) { ++ func = "enable"; ++ polarity = GPIO_ACTIVE_HIGH; ++ } ++ + status = acpi_get_handle(NULL, path, &handle); + if (ACPI_FAILURE(status)) + return -EINVAL; +-- +2.42.0 + +From 0cfd5c05a675388bbb2edfa87423dc5ad931cc97 Mon Sep 17 00:00:00 2001 +From: Daniel Scally +Date: Tue, 21 Mar 2023 13:45:26 +0000 +Subject: [PATCH] media: i2c: Clarify that gain is Analogue gain in OV7251 + +Update the control ID for the gain control in the ov7251 driver to +V4L2_CID_ANALOGUE_GAIN. + +Signed-off-by: Daniel Scally +Patchset: cameras +--- + drivers/media/i2c/ov7251.c | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/drivers/media/i2c/ov7251.c b/drivers/media/i2c/ov7251.c +index 675fb37a6feae..43b30db08c9e4 100644 +--- a/drivers/media/i2c/ov7251.c ++++ b/drivers/media/i2c/ov7251.c +@@ -1051,7 +1051,7 @@ static int ov7251_s_ctrl(struct v4l2_ctrl *ctrl) + case V4L2_CID_EXPOSURE: + ret = ov7251_set_exposure(ov7251, ctrl->val); + break; +- case V4L2_CID_GAIN: ++ case V4L2_CID_ANALOGUE_GAIN: + ret = ov7251_set_gain(ov7251, ctrl->val); + break; + case V4L2_CID_TEST_PATTERN: +@@ -1551,7 +1551,7 @@ static int ov7251_init_ctrls(struct ov7251 *ov7251) + ov7251->exposure = v4l2_ctrl_new_std(&ov7251->ctrls, &ov7251_ctrl_ops, + V4L2_CID_EXPOSURE, 1, 32, 1, 32); + ov7251->gain = v4l2_ctrl_new_std(&ov7251->ctrls, &ov7251_ctrl_ops, +- V4L2_CID_GAIN, 16, 1023, 1, 16); ++ V4L2_CID_ANALOGUE_GAIN, 16, 1023, 1, 16); + v4l2_ctrl_new_std_menu_items(&ov7251->ctrls, &ov7251_ctrl_ops, + V4L2_CID_TEST_PATTERN, + ARRAY_SIZE(ov7251_test_pattern_menu) - 1, +-- +2.42.0 + +From 18fa273c21f1dd86160f18242a81947392272443 Mon Sep 17 00:00:00 2001 +From: Daniel Scally +Date: Wed, 22 Mar 2023 11:01:42 +0000 +Subject: [PATCH] media: v4l2-core: Acquire privacy led in + v4l2_async_register_subdev() + +The current call to v4l2_subdev_get_privacy_led() is contained in +v4l2_async_register_subdev_sensor(), but that function isn't used by +all the sensor drivers. Move the acquisition of the privacy led to +v4l2_async_register_subdev() instead. + +Signed-off-by: Daniel Scally +Patchset: cameras +--- + drivers/media/v4l2-core/v4l2-async.c | 4 ++++ + drivers/media/v4l2-core/v4l2-fwnode.c | 4 ---- + 2 files changed, 4 insertions(+), 4 deletions(-) + +diff --git a/drivers/media/v4l2-core/v4l2-async.c b/drivers/media/v4l2-core/v4l2-async.c +index 091e8cf4114ba..cca10f5355844 100644 +--- a/drivers/media/v4l2-core/v4l2-async.c ++++ b/drivers/media/v4l2-core/v4l2-async.c +@@ -796,6 +796,10 @@ int v4l2_async_register_subdev(struct v4l2_subdev *sd) + + INIT_LIST_HEAD(&sd->asc_list); + ++ ret = v4l2_subdev_get_privacy_led(sd); ++ if (ret < 0) ++ return ret; ++ + /* + * No reference taken. The reference is held by the device (struct + * v4l2_subdev.dev), and async sub-device does not exist independently +diff --git a/drivers/media/v4l2-core/v4l2-fwnode.c b/drivers/media/v4l2-core/v4l2-fwnode.c +index 7f181fbbb1407..1c0347de4e216 100644 +--- a/drivers/media/v4l2-core/v4l2-fwnode.c ++++ b/drivers/media/v4l2-core/v4l2-fwnode.c +@@ -1217,10 +1217,6 @@ int v4l2_async_register_subdev_sensor(struct v4l2_subdev *sd) + + v4l2_async_subdev_nf_init(notifier, sd); + +- ret = v4l2_subdev_get_privacy_led(sd); +- if (ret < 0) +- goto out_cleanup; +- + ret = v4l2_async_nf_parse_fwnode_sensor(sd->dev, notifier); + if (ret < 0) + goto out_cleanup; +-- +2.42.0 + +From 07e01113f2641afab78b155d42e9d9d399a9e164 Mon Sep 17 00:00:00 2001 +From: Kate Hsuan +Date: Tue, 21 Mar 2023 23:37:16 +0800 +Subject: [PATCH] platform: x86: int3472: Add MFD cell for tps68470 LED + +Add MFD cell for tps68470-led. + +Reviewed-by: Daniel Scally +Signed-off-by: Kate Hsuan +Reviewed-by: Hans de Goede +Patchset: cameras +--- + drivers/platform/x86/intel/int3472/tps68470.c | 5 +++-- + 1 file changed, 3 insertions(+), 2 deletions(-) + +diff --git a/drivers/platform/x86/intel/int3472/tps68470.c b/drivers/platform/x86/intel/int3472/tps68470.c +index e3e1696e7f0ee..423dc555093f7 100644 +--- a/drivers/platform/x86/intel/int3472/tps68470.c ++++ b/drivers/platform/x86/intel/int3472/tps68470.c +@@ -17,7 +17,7 @@ + #define DESIGNED_FOR_CHROMEOS 1 + #define DESIGNED_FOR_WINDOWS 2 + +-#define TPS68470_WIN_MFD_CELL_COUNT 3 ++#define TPS68470_WIN_MFD_CELL_COUNT 4 + + static const struct mfd_cell tps68470_cros[] = { + { .name = "tps68470-gpio" }, +@@ -200,7 +200,8 @@ static int skl_int3472_tps68470_probe(struct i2c_client *client) + cells[1].name = "tps68470-regulator"; + cells[1].platform_data = (void *)board_data->tps68470_regulator_pdata; + cells[1].pdata_size = sizeof(struct tps68470_regulator_platform_data); +- cells[2].name = "tps68470-gpio"; ++ cells[2].name = "tps68470-led"; ++ cells[3].name = "tps68470-gpio"; + + for (i = 0; i < board_data->n_gpiod_lookups; i++) + gpiod_add_lookup_table(board_data->tps68470_gpio_lookup_tables[i]); +-- +2.42.0 + +From a704bf822539e09b00015110b48bc997692c92ce Mon Sep 17 00:00:00 2001 +From: Kate Hsuan +Date: Tue, 21 Mar 2023 23:37:17 +0800 +Subject: [PATCH] include: mfd: tps68470: Add masks for LEDA and LEDB + +Add flags for both LEDA(TPS68470_ILEDCTL_ENA), LEDB +(TPS68470_ILEDCTL_ENB), and current control mask for LEDB +(TPS68470_ILEDCTL_CTRLB) + +Reviewed-by: Daniel Scally +Reviewed-by: Hans de Goede +Signed-off-by: Kate Hsuan +Patchset: cameras +--- + include/linux/mfd/tps68470.h | 5 +++++ + 1 file changed, 5 insertions(+) + +diff --git a/include/linux/mfd/tps68470.h b/include/linux/mfd/tps68470.h +index 7807fa329db00..2d2abb25b944f 100644 +--- a/include/linux/mfd/tps68470.h ++++ b/include/linux/mfd/tps68470.h +@@ -34,6 +34,7 @@ + #define TPS68470_REG_SGPO 0x22 + #define TPS68470_REG_GPDI 0x26 + #define TPS68470_REG_GPDO 0x27 ++#define TPS68470_REG_ILEDCTL 0x28 + #define TPS68470_REG_VCMVAL 0x3C + #define TPS68470_REG_VAUX1VAL 0x3D + #define TPS68470_REG_VAUX2VAL 0x3E +@@ -94,4 +95,8 @@ + #define TPS68470_GPIO_MODE_OUT_CMOS 2 + #define TPS68470_GPIO_MODE_OUT_ODRAIN 3 + ++#define TPS68470_ILEDCTL_ENA BIT(2) ++#define TPS68470_ILEDCTL_ENB BIT(6) ++#define TPS68470_ILEDCTL_CTRLB GENMASK(5, 4) ++ + #endif /* __LINUX_MFD_TPS68470_H */ +-- +2.42.0 + +From c8a6ce96be3a4dca7e9e99613b28494d10b4ade0 Mon Sep 17 00:00:00 2001 +From: Kate Hsuan +Date: Tue, 21 Mar 2023 23:37:18 +0800 +Subject: [PATCH] leds: tps68470: Add LED control for tps68470 + +There are two LED controllers, LEDA indicator LED and LEDB flash LED for +tps68470. LEDA can be enabled by setting TPS68470_ILEDCTL_ENA. Moreover, +tps68470 provides four levels of power status for LEDB. If the +properties called "ti,ledb-current" can be found, the current will be +set according to the property values. These two LEDs can be controlled +through the LED class of sysfs (tps68470-leda and tps68470-ledb). + +Signed-off-by: Kate Hsuan +Reviewed-by: Hans de Goede +Patchset: cameras +--- + drivers/leds/Kconfig | 12 +++ + drivers/leds/Makefile | 1 + + drivers/leds/leds-tps68470.c | 185 +++++++++++++++++++++++++++++++++++ + 3 files changed, 198 insertions(+) + create mode 100644 drivers/leds/leds-tps68470.c + +diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig +index b92208eccdea9..312c0c21cc5ef 100644 +--- a/drivers/leds/Kconfig ++++ b/drivers/leds/Kconfig +@@ -873,6 +873,18 @@ config LEDS_TPS6105X It is a single boost converter primarily for white LEDs and audio amplifiers. @@ -5113,10 +8690,11 @@ diff '--color=auto' -uraN cachyos/drivers/leds/Kconfig cachyos-surface/drivers/l config LEDS_IP30 tristate "LED support for SGI Octane machines" depends on LEDS_CLASS -diff '--color=auto' -uraN cachyos/drivers/leds/Makefile cachyos-surface/drivers/leds/Makefile ---- cachyos/drivers/leds/Makefile 2023-10-30 05:31:08.000000000 +0300 -+++ cachyos-surface/drivers/leds/Makefile 2023-11-04 18:32:41.054321425 +0300 -@@ -84,6 +84,7 @@ +diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile +index d7348e8bc019a..10caea4e7c614 100644 +--- a/drivers/leds/Makefile ++++ b/drivers/leds/Makefile +@@ -84,6 +84,7 @@ obj-$(CONFIG_LEDS_TCA6507) += leds-tca6507.o obj-$(CONFIG_LEDS_TI_LMU_COMMON) += leds-ti-lmu-common.o obj-$(CONFIG_LEDS_TLC591XX) += leds-tlc591xx.o obj-$(CONFIG_LEDS_TPS6105X) += leds-tps6105x.o @@ -5124,9 +8702,11 @@ diff '--color=auto' -uraN cachyos/drivers/leds/Makefile cachyos-surface/drivers/ obj-$(CONFIG_LEDS_TURRIS_OMNIA) += leds-turris-omnia.o obj-$(CONFIG_LEDS_WM831X_STATUS) += leds-wm831x-status.o obj-$(CONFIG_LEDS_WM8350) += leds-wm8350.o -diff '--color=auto' -uraN cachyos/drivers/leds/leds-tps68470.c cachyos-surface/drivers/leds/leds-tps68470.c ---- cachyos/drivers/leds/leds-tps68470.c 1970-01-01 03:00:00.000000000 +0300 -+++ cachyos-surface/drivers/leds/leds-tps68470.c 2023-11-04 18:32:41.057654806 +0300 +diff --git a/drivers/leds/leds-tps68470.c b/drivers/leds/leds-tps68470.c +new file mode 100644 +index 0000000000000..35aeb5db89c8f +--- /dev/null ++++ b/drivers/leds/leds-tps68470.c @@ -0,0 +1,185 @@ +// SPDX-License-Identifier: GPL-2.0 +/* @@ -5313,930 +8893,225 @@ diff '--color=auto' -uraN cachyos/drivers/leds/leds-tps68470.c cachyos-surface/d +MODULE_ALIAS("platform:tps68470-led"); +MODULE_DESCRIPTION("LED driver for TPS68470 PMIC"); +MODULE_LICENSE("GPL v2"); -diff '--color=auto' -uraN cachyos/drivers/media/i2c/Kconfig cachyos-surface/drivers/media/i2c/Kconfig ---- cachyos/drivers/media/i2c/Kconfig 2023-10-30 05:31:08.000000000 +0300 -+++ cachyos-surface/drivers/media/i2c/Kconfig 2023-11-04 18:32:41.054321425 +0300 -@@ -665,6 +665,17 @@ - This is designed for linear control of voice coil motors, - controlled via I2C serial interface. +-- +2.42.0 + +From 82252c3764ecee6c09218077759072f15001f9ee Mon Sep 17 00:00:00 2001 +From: Sachi King +Date: Sat, 29 May 2021 17:47:38 +1000 +Subject: [PATCH] ACPI: Add quirk for Surface Laptop 4 AMD missing irq 7 + override + +This patch is the work of Thomas Gleixner and is +copied from: +https://lore.kernel.org/lkml/87lf8ddjqx.ffs@nanos.tec.linutronix.de/ + +This patch adds a quirk to the ACPI setup to patch in the the irq 7 pin +setup that is missing in the laptops ACPI table. + +This patch was used for validation of the issue, and is not a proper +fix, but is probably a better temporary hack than continuing to probe +the Legacy PIC and run with the PIC in an unknown state. + +Patchset: amd-gpio +--- + arch/x86/kernel/acpi/boot.c | 17 +++++++++++++++++ + 1 file changed, 17 insertions(+) + +diff --git a/arch/x86/kernel/acpi/boot.c b/arch/x86/kernel/acpi/boot.c +index c55c0ef47a187..f29740cf89ff6 100644 +--- a/arch/x86/kernel/acpi/boot.c ++++ b/arch/x86/kernel/acpi/boot.c +@@ -22,6 +22,7 @@ + #include + #include + #include ++#include -+config VIDEO_DW9719 -+ tristate "DW9719 lens voice coil support" -+ depends on I2C && VIDEO_DEV -+ select MEDIA_CONTROLLER -+ select VIDEO_V4L2_SUBDEV_API -+ select V4L2_ASYNC -+ help -+ This is a driver for the DW9719 camera lens voice coil. -+ This is designed for linear control of voice coil motors, -+ controlled via I2C serial interface. -+ - config VIDEO_DW9768 - tristate "DW9768 lens voice coil support" - depends on I2C && VIDEO_DEV -diff '--color=auto' -uraN cachyos/drivers/media/i2c/ov7251.c cachyos-surface/drivers/media/i2c/ov7251.c ---- cachyos/drivers/media/i2c/ov7251.c 2023-10-30 05:31:08.000000000 +0300 -+++ cachyos-surface/drivers/media/i2c/ov7251.c 2023-11-04 18:32:41.054321425 +0300 -@@ -1051,7 +1051,7 @@ - case V4L2_CID_EXPOSURE: - ret = ov7251_set_exposure(ov7251, ctrl->val); - break; -- case V4L2_CID_GAIN: -+ case V4L2_CID_ANALOGUE_GAIN: - ret = ov7251_set_gain(ov7251, ctrl->val); - break; - case V4L2_CID_TEST_PATTERN: -@@ -1551,7 +1551,7 @@ - ov7251->exposure = v4l2_ctrl_new_std(&ov7251->ctrls, &ov7251_ctrl_ops, - V4L2_CID_EXPOSURE, 1, 32, 1, 32); - ov7251->gain = v4l2_ctrl_new_std(&ov7251->ctrls, &ov7251_ctrl_ops, -- V4L2_CID_GAIN, 16, 1023, 1, 16); -+ V4L2_CID_ANALOGUE_GAIN, 16, 1023, 1, 16); - v4l2_ctrl_new_std_menu_items(&ov7251->ctrls, &ov7251_ctrl_ops, - V4L2_CID_TEST_PATTERN, - ARRAY_SIZE(ov7251_test_pattern_menu) - 1, -diff '--color=auto' -uraN cachyos/drivers/media/pci/intel/ipu3/ipu3-cio2.c cachyos-surface/drivers/media/pci/intel/ipu3/ipu3-cio2.c ---- cachyos/drivers/media/pci/intel/ipu3/ipu3-cio2.c 2023-10-30 05:31:08.000000000 +0300 -+++ cachyos-surface/drivers/media/pci/intel/ipu3/ipu3-cio2.c 2023-11-04 18:40:31.635277734 +0300 -@@ -1403,7 +1403,24 @@ - q->sensor = sd; - q->csi_rx_base = cio2->base + CIO2_REG_PIPE_BASE(q->csi2.port); - -- return 0; -+ ret = media_entity_get_fwnode_pad(&q->sensor->entity, -+ s_asd->asd.match.fwnode, -+ MEDIA_PAD_FL_SOURCE); -+ if (ret < 0) { -+ dev_err(dev, "no pad for endpoint %pfw (%d)\n", -+ s_asd->asd.match.fwnode, ret); -+ return ret; -+ } -+ -+ ret = media_create_pad_link(&q->sensor->entity, ret, &q->subdev.entity, -+ CIO2_PAD_SINK, 0); -+ if (ret) { -+ dev_err(dev, "failed to create link for %s\n", -+ q->sensor->name); -+ return ret; -+ } -+ -+ return v4l2_device_register_subdev_nodes(&cio2->v4l2_dev); - } - - /* The .unbind callback */ -@@ -1421,34 +1438,6 @@ - static int cio2_notifier_complete(struct v4l2_async_notifier *notifier) - { - struct cio2_device *cio2 = to_cio2_device(notifier); -- struct device *dev = &cio2->pci_dev->dev; -- struct sensor_async_subdev *s_asd; -- struct v4l2_async_connection *asd; -- struct cio2_queue *q; -- int ret; -- -- list_for_each_entry(asd, &cio2->notifier.done_list, asc_entry) { -- s_asd = to_sensor_asd(asd); -- q = &cio2->queue[s_asd->csi2.port]; -- -- ret = media_entity_get_fwnode_pad(&q->sensor->entity, -- s_asd->asd.match.fwnode, -- MEDIA_PAD_FL_SOURCE); -- if (ret < 0) { -- dev_err(dev, "no pad for endpoint %pfw (%d)\n", -- s_asd->asd.match.fwnode, ret); -- return ret; -- } -- -- ret = media_create_pad_link(&q->sensor->entity, ret, -- &q->subdev.entity, CIO2_PAD_SINK, -- 0); -- if (ret) { -- dev_err(dev, "failed to create link for %s (endpoint %pfw, error %d)\n", -- q->sensor->name, s_asd->asd.match.fwnode, ret); -- return ret; -- } -- } - - return v4l2_device_register_subdev_nodes(&cio2->v4l2_dev); - } -diff '--color=auto' -uraN cachyos/drivers/media/v4l2-core/v4l2-async.c cachyos-surface/drivers/media/v4l2-core/v4l2-async.c ---- cachyos/drivers/media/v4l2-core/v4l2-async.c 2023-10-30 05:31:08.000000000 +0300 -+++ cachyos-surface/drivers/media/v4l2-core/v4l2-async.c 2023-11-04 18:32:41.054321425 +0300 -@@ -796,6 +796,10 @@ - - INIT_LIST_HEAD(&sd->asc_list); - -+ ret = v4l2_subdev_get_privacy_led(sd); -+ if (ret < 0) -+ return ret; -+ - /* - * No reference taken. The reference is held by the device (struct - * v4l2_subdev.dev), and async sub-device does not exist independently -diff '--color=auto' -uraN cachyos/drivers/media/v4l2-core/v4l2-fwnode.c cachyos-surface/drivers/media/v4l2-core/v4l2-fwnode.c ---- cachyos/drivers/media/v4l2-core/v4l2-fwnode.c 2023-10-30 05:31:08.000000000 +0300 -+++ cachyos-surface/drivers/media/v4l2-core/v4l2-fwnode.c 2023-11-04 18:32:41.054321425 +0300 -@@ -1217,10 +1217,6 @@ - - v4l2_async_subdev_nf_init(notifier, sd); - -- ret = v4l2_subdev_get_privacy_led(sd); -- if (ret < 0) -- goto out_cleanup; -- - ret = v4l2_async_nf_parse_fwnode_sensor(sd->dev, notifier); - if (ret < 0) - goto out_cleanup; -diff '--color=auto' -uraN cachyos/drivers/misc/mei/hw-me-regs.h cachyos-surface/drivers/misc/mei/hw-me-regs.h ---- cachyos/drivers/misc/mei/hw-me-regs.h 2023-10-30 05:31:08.000000000 +0300 -+++ cachyos-surface/drivers/misc/mei/hw-me-regs.h 2023-11-04 18:32:41.037654516 +0300 -@@ -92,6 +92,7 @@ - #define MEI_DEV_ID_CDF 0x18D3 /* Cedar Fork */ - - #define MEI_DEV_ID_ICP_LP 0x34E0 /* Ice Lake Point LP */ -+#define MEI_DEV_ID_ICP_LP_3 0x34E4 /* Ice Lake Point LP 3 (iTouch) */ - #define MEI_DEV_ID_ICP_N 0x38E0 /* Ice Lake Point N */ - - #define MEI_DEV_ID_JSP_N 0x4DE0 /* Jasper Lake Point N */ -diff '--color=auto' -uraN cachyos/drivers/misc/mei/pci-me.c cachyos-surface/drivers/misc/mei/pci-me.c ---- cachyos/drivers/misc/mei/pci-me.c 2023-10-30 05:31:08.000000000 +0300 -+++ cachyos-surface/drivers/misc/mei/pci-me.c 2023-11-04 18:32:41.037654516 +0300 -@@ -97,6 +97,7 @@ - {MEI_PCI_DEVICE(MEI_DEV_ID_CMP_H_3, MEI_ME_PCH8_ITOUCH_CFG)}, - - {MEI_PCI_DEVICE(MEI_DEV_ID_ICP_LP, MEI_ME_PCH12_CFG)}, -+ {MEI_PCI_DEVICE(MEI_DEV_ID_ICP_LP_3, MEI_ME_PCH12_CFG)}, - {MEI_PCI_DEVICE(MEI_DEV_ID_ICP_N, MEI_ME_PCH12_CFG)}, - - {MEI_PCI_DEVICE(MEI_DEV_ID_TGP_LP, MEI_ME_PCH15_CFG)}, -diff '--color=auto' -uraN cachyos/drivers/net/wireless/ath/ath10k/core.c cachyos-surface/drivers/net/wireless/ath/ath10k/core.c ---- cachyos/drivers/net/wireless/ath/ath10k/core.c 2023-10-30 05:31:08.000000000 +0300 -+++ cachyos-surface/drivers/net/wireless/ath/ath10k/core.c 2023-11-04 18:32:41.037654516 +0300 -@@ -38,6 +38,9 @@ - /* frame mode values are mapped as per enum ath10k_hw_txrx_mode */ - unsigned int ath10k_frame_mode = ATH10K_HW_TXRX_NATIVE_WIFI; - -+static char *override_board = ""; -+static char *override_board2 = ""; -+ - unsigned long ath10k_coredump_mask = BIT(ATH10K_FW_CRASH_DUMP_REGISTERS) | - BIT(ATH10K_FW_CRASH_DUMP_CE_DATA); - -@@ -50,6 +53,9 @@ - module_param_named(frame_mode, ath10k_frame_mode, uint, 0644); - module_param_named(coredump_mask, ath10k_coredump_mask, ulong, 0444); - -+module_param(override_board, charp, 0644); -+module_param(override_board2, charp, 0644); -+ - MODULE_PARM_DESC(debug_mask, "Debugging mask"); - MODULE_PARM_DESC(uart_print, "Uart target debugging"); - MODULE_PARM_DESC(skip_otp, "Skip otp failure for calibration in testmode"); -@@ -59,6 +65,9 @@ - MODULE_PARM_DESC(coredump_mask, "Bitfield of what to include in firmware crash file"); - MODULE_PARM_DESC(fw_diag_log, "Diag based fw log debugging"); - -+MODULE_PARM_DESC(override_board, "Override for board.bin file"); -+MODULE_PARM_DESC(override_board2, "Override for board-2.bin file"); -+ - static const struct ath10k_hw_params ath10k_hw_params_list[] = { - { - .id = QCA988X_HW_2_0_VERSION, -@@ -911,6 +920,42 @@ - return 0; - } - -+static const char *ath10k_override_board_fw_file(struct ath10k *ar, -+ const char *file) -+{ -+ if (strcmp(file, "board.bin") == 0) { -+ if (strcmp(override_board, "") == 0) -+ return file; -+ -+ if (strcmp(override_board, "none") == 0) { -+ dev_info(ar->dev, "firmware override: pretending 'board.bin' does not exist\n"); -+ return NULL; -+ } -+ -+ dev_info(ar->dev, "firmware override: replacing 'board.bin' with '%s'\n", -+ override_board); -+ -+ return override_board; -+ } -+ -+ if (strcmp(file, "board-2.bin") == 0) { -+ if (strcmp(override_board2, "") == 0) -+ return file; -+ -+ if (strcmp(override_board2, "none") == 0) { -+ dev_info(ar->dev, "firmware override: pretending 'board-2.bin' does not exist\n"); -+ return NULL; -+ } -+ -+ dev_info(ar->dev, "firmware override: replacing 'board-2.bin' with '%s'\n", -+ override_board2); -+ -+ return override_board2; -+ } -+ -+ return file; -+} -+ - static const struct firmware *ath10k_fetch_fw_file(struct ath10k *ar, - const char *dir, - const char *file) -@@ -925,6 +970,19 @@ - if (dir == NULL) - dir = "."; - -+ /* HACK: Override board.bin and board-2.bin files if specified. -+ * -+ * Some Surface devices perform better with a different board -+ * configuration. To this end, one would need to replace the board.bin -+ * file with the modified config and remove the board-2.bin file. -+ * Unfortunately, that's not a solution that we can easily package. So -+ * we add module options to perform these overrides here. -+ */ -+ -+ file = ath10k_override_board_fw_file(ar, file); -+ if (!file) -+ return ERR_PTR(-ENOENT); -+ - snprintf(filename, sizeof(filename), "%s/%s", dir, file); - ret = firmware_request_nowarn(&fw, filename, ar->dev); - ath10k_dbg(ar, ATH10K_DBG_BOOT, "boot fw request '%s': %d\n", -diff '--color=auto' -uraN cachyos/drivers/net/wireless/marvell/mwifiex/pcie.c cachyos-surface/drivers/net/wireless/marvell/mwifiex/pcie.c ---- cachyos/drivers/net/wireless/marvell/mwifiex/pcie.c 2023-10-30 05:31:08.000000000 +0300 -+++ cachyos-surface/drivers/net/wireless/marvell/mwifiex/pcie.c 2023-11-04 18:32:41.034321134 +0300 -@@ -370,6 +370,7 @@ - const struct pci_device_id *ent) - { - struct pcie_service_card *card; -+ struct pci_dev *parent_pdev = pci_upstream_bridge(pdev); - int ret; - - pr_debug("info: vendor=0x%4.04X device=0x%4.04X rev=%d\n", -@@ -411,6 +412,12 @@ - return -1; + #include + #include +@@ -1255,6 +1256,17 @@ static void __init mp_config_acpi_legacy_irqs(void) } - -+ /* disable bridge_d3 for Surface gen4+ devices to fix fw crashing -+ * after suspend -+ */ -+ if (card->quirks & QUIRK_NO_BRIDGE_D3) -+ parent_pdev->bridge_d3 = false; -+ - return 0; } -@@ -1771,9 +1778,21 @@ - static int mwifiex_pcie_init_fw_port(struct mwifiex_adapter *adapter) - { - struct pcie_service_card *card = adapter->card; -+ struct pci_dev *pdev = card->dev; -+ struct pci_dev *parent_pdev = pci_upstream_bridge(pdev); - const struct mwifiex_pcie_card_reg *reg = card->pcie.reg; - int tx_wrap = card->txbd_wrptr & reg->tx_wrap_mask; - -+ /* Trigger a function level reset of the PCI bridge device, this makes -+ * the firmware of PCIe 88W8897 cards stop reporting a fixed LTR value -+ * that prevents the system from entering package C10 and S0ix powersaving -+ * states. -+ * We need to do it here because it must happen after firmware -+ * initialization and this function is called after that is done. -+ */ -+ if (card->quirks & QUIRK_DO_FLR_ON_BRIDGE) -+ pci_reset_function(parent_pdev); -+ - /* Write the RX ring read pointer in to reg->rx_rdptr */ - if (mwifiex_write_reg(adapter, reg->rx_rdptr, card->rxbd_rdptr | - tx_wrap)) { -diff '--color=auto' -uraN cachyos/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c cachyos-surface/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c ---- cachyos/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c 2023-10-30 05:31:08.000000000 +0300 -+++ cachyos-surface/drivers/net/wireless/marvell/mwifiex/pcie_quirks.c 2023-11-04 18:32:41.034321134 +0300 -@@ -13,7 +13,9 @@ - DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 4"), - }, -- .driver_data = (void *)QUIRK_FW_RST_D3COLD, -+ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -+ QUIRK_DO_FLR_ON_BRIDGE | -+ QUIRK_NO_BRIDGE_D3), - }, - { - .ident = "Surface Pro 5", -@@ -22,7 +24,9 @@ - DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), - DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1796"), - }, -- .driver_data = (void *)QUIRK_FW_RST_D3COLD, -+ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -+ QUIRK_DO_FLR_ON_BRIDGE | -+ QUIRK_NO_BRIDGE_D3), - }, - { - .ident = "Surface Pro 5 (LTE)", -@@ -31,7 +35,9 @@ - DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), - DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_1807"), - }, -- .driver_data = (void *)QUIRK_FW_RST_D3COLD, -+ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -+ QUIRK_DO_FLR_ON_BRIDGE | -+ QUIRK_NO_BRIDGE_D3), - }, - { - .ident = "Surface Pro 6", -@@ -39,7 +45,9 @@ - DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Pro 6"), - }, -- .driver_data = (void *)QUIRK_FW_RST_D3COLD, -+ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -+ QUIRK_DO_FLR_ON_BRIDGE | -+ QUIRK_NO_BRIDGE_D3), - }, - { - .ident = "Surface Book 1", -@@ -47,7 +55,9 @@ - DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book"), - }, -- .driver_data = (void *)QUIRK_FW_RST_D3COLD, -+ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -+ QUIRK_DO_FLR_ON_BRIDGE | -+ QUIRK_NO_BRIDGE_D3), - }, - { - .ident = "Surface Book 2", -@@ -55,7 +65,9 @@ - DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Book 2"), - }, -- .driver_data = (void *)QUIRK_FW_RST_D3COLD, -+ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -+ QUIRK_DO_FLR_ON_BRIDGE | -+ QUIRK_NO_BRIDGE_D3), - }, - { - .ident = "Surface Laptop 1", -@@ -63,7 +75,9 @@ - DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop"), - }, -- .driver_data = (void *)QUIRK_FW_RST_D3COLD, -+ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -+ QUIRK_DO_FLR_ON_BRIDGE | -+ QUIRK_NO_BRIDGE_D3), - }, - { - .ident = "Surface Laptop 2", -@@ -71,7 +85,9 @@ - DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), - DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Surface Laptop 2"), - }, -- .driver_data = (void *)QUIRK_FW_RST_D3COLD, -+ .driver_data = (void *)(QUIRK_FW_RST_D3COLD | -+ QUIRK_DO_FLR_ON_BRIDGE | -+ QUIRK_NO_BRIDGE_D3), - }, - {} - }; -@@ -89,6 +105,11 @@ - dev_info(&pdev->dev, "no quirks enabled\n"); - if (card->quirks & QUIRK_FW_RST_D3COLD) - dev_info(&pdev->dev, "quirk reset_d3cold enabled\n"); -+ if (card->quirks & QUIRK_DO_FLR_ON_BRIDGE) -+ dev_info(&pdev->dev, "quirk do_flr_on_bridge enabled\n"); -+ if (card->quirks & QUIRK_NO_BRIDGE_D3) -+ dev_info(&pdev->dev, -+ "quirk no_brigde_d3 enabled\n"); - } - - static void mwifiex_pcie_set_power_d3cold(struct pci_dev *pdev) -diff '--color=auto' -uraN cachyos/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h cachyos-surface/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h ---- cachyos/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h 2023-10-30 05:31:08.000000000 +0300 -+++ cachyos-surface/drivers/net/wireless/marvell/mwifiex/pcie_quirks.h 2023-11-04 18:32:41.034321134 +0300 -@@ -4,6 +4,8 @@ - #include "pcie.h" - - #define QUIRK_FW_RST_D3COLD BIT(0) -+#define QUIRK_DO_FLR_ON_BRIDGE BIT(1) -+#define QUIRK_NO_BRIDGE_D3 BIT(2) - - void mwifiex_initialize_quirks(struct pcie_service_card *card); - int mwifiex_pcie_reset_d3cold_quirk(struct pci_dev *pdev); -diff '--color=auto' -uraN cachyos/drivers/pci/pci-driver.c cachyos-surface/drivers/pci/pci-driver.c ---- cachyos/drivers/pci/pci-driver.c 2023-10-30 05:31:08.000000000 +0300 -+++ cachyos-surface/drivers/pci/pci-driver.c 2023-11-04 18:32:41.044321279 +0300 -@@ -507,6 +507,9 @@ - struct pci_dev *pci_dev = to_pci_dev(dev); - struct pci_driver *drv = pci_dev->driver; - -+ if (pci_dev->no_shutdown) -+ return; -+ - pm_runtime_resume(dev); - - if (drv && drv->shutdown) -diff '--color=auto' -uraN cachyos/drivers/pci/quirks.c cachyos-surface/drivers/pci/quirks.c ---- cachyos/drivers/pci/quirks.c 2023-11-04 17:51:36.237334796 +0300 -+++ cachyos-surface/drivers/pci/quirks.c 2023-11-04 18:35:31.917446780 +0300 -@@ -6278,6 +6278,43 @@ - DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x9a31, dpc_log_size); - #endif - -+static const struct dmi_system_id no_shutdown_dmi_table[] = { -+ /* -+ * Systems on which some devices should not be touched during shutdown. -+ */ ++static const struct dmi_system_id surface_quirk[] __initconst = { + { -+ .ident = "Microsoft Surface Pro 9", ++ .ident = "Microsoft Surface Laptop 4 (AMD)", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_MATCH(DMI_PRODUCT_NAME, "Surface Pro 9"), -+ }, -+ }, -+ { -+ .ident = "Microsoft Surface Laptop 5", -+ .matches = { -+ DMI_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_MATCH(DMI_PRODUCT_NAME, "Surface Laptop 5"), ++ DMI_MATCH(DMI_PRODUCT_SKU, "Surface_Laptop_4_1952:1953") + }, + }, + {} +}; -+ -+static void quirk_no_shutdown(struct pci_dev *dev) -+{ -+ if (!dmi_check_system(no_shutdown_dmi_table)) -+ return; -+ -+ dev->no_shutdown = 1; -+ pci_info(dev, "disabling shutdown ops for [%04x:%04x]\n", -+ dev->vendor, dev->device); -+} -+DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x461e, quirk_no_shutdown); // Thunderbolt 4 USB Controller -+DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x461f, quirk_no_shutdown); // Thunderbolt 4 PCI Express Root Port -+DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x462f, quirk_no_shutdown); // Thunderbolt 4 PCI Express Root Port -+DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x466d, quirk_no_shutdown); // Thunderbolt 4 NHI -+DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_INTEL, 0x46a8, quirk_no_shutdown); // GPU -+ + /* - * For a PCI device with multiple downstream devices, its driver may use - * a flattened device tree to describe the downstream devices. -diff '--color=auto' -uraN cachyos/drivers/platform/surface/Kconfig cachyos-surface/drivers/platform/surface/Kconfig ---- cachyos/drivers/platform/surface/Kconfig 2023-10-30 05:31:08.000000000 +0300 -+++ cachyos-surface/drivers/platform/surface/Kconfig 2023-11-04 18:32:41.040987898 +0300 -@@ -149,6 +149,13 @@ - Select M or Y here, if you want to provide tablet-mode switch input - events on the Surface Pro 8, Surface Pro X, and Surface Laptop Studio. + * Parse IOAPIC related entries in MADT + * returns 0 on success, < 0 on error +@@ -1310,6 +1322,11 @@ static int __init acpi_parse_madt_ioapic_entries(void) + acpi_sci_ioapic_setup(acpi_gbl_FADT.sci_interrupt, 0, 0, + acpi_gbl_FADT.sci_interrupt); -+config SURFACE_BOOK1_DGPU_SWITCH -+ tristate "Surface Book 1 dGPU Switch Driver" -+ depends on SYSFS -+ help -+ This driver provides a sysfs switch to set the power-state of the -+ discrete GPU found on the Microsoft Surface Book 1. ++ if (dmi_check_system(surface_quirk)) { ++ pr_warn("Surface hack: Override irq 7\n"); ++ mp_override_legacy_irq(7, 3, 3, 7); ++ } + - config SURFACE_DTX - tristate "Surface DTX (Detachment System) Driver" - depends on SURFACE_AGGREGATOR -diff '--color=auto' -uraN cachyos/drivers/platform/surface/Makefile cachyos-surface/drivers/platform/surface/Makefile ---- cachyos/drivers/platform/surface/Makefile 2023-10-30 05:31:08.000000000 +0300 -+++ cachyos-surface/drivers/platform/surface/Makefile 2023-11-04 18:32:41.040987898 +0300 -@@ -12,6 +12,7 @@ - obj-$(CONFIG_SURFACE_AGGREGATOR_HUB) += surface_aggregator_hub.o - obj-$(CONFIG_SURFACE_AGGREGATOR_REGISTRY) += surface_aggregator_registry.o - obj-$(CONFIG_SURFACE_AGGREGATOR_TABLET_SWITCH) += surface_aggregator_tabletsw.o -+obj-$(CONFIG_SURFACE_BOOK1_DGPU_SWITCH) += surfacebook1_dgpu_switch.o - obj-$(CONFIG_SURFACE_DTX) += surface_dtx.o - obj-$(CONFIG_SURFACE_GPE) += surface_gpe.o - obj-$(CONFIG_SURFACE_HOTPLUG) += surface_hotplug.o -diff '--color=auto' -uraN cachyos/drivers/platform/surface/surface3-wmi.c cachyos-surface/drivers/platform/surface/surface3-wmi.c ---- cachyos/drivers/platform/surface/surface3-wmi.c 2023-10-30 05:31:08.000000000 +0300 -+++ cachyos-surface/drivers/platform/surface/surface3-wmi.c 2023-11-04 18:32:41.030987752 +0300 -@@ -37,6 +37,13 @@ - DMI_MATCH(DMI_PRODUCT_NAME, "Surface 3"), + /* Fill in identity legacy mappings where no override */ + mp_config_acpi_legacy_irqs(); + +-- +2.42.0 + +From 52e3f50633128a93bf99ca5c97f98929da66a9ed Mon Sep 17 00:00:00 2001 +From: Maximilian Luz +Date: Thu, 3 Jun 2021 14:04:26 +0200 +Subject: [PATCH] ACPI: Add AMD 13" Surface Laptop 4 model to irq 7 override + quirk + +The 13" version of the Surface Laptop 4 has the same problem as the 15" +version, but uses a different SKU. Add that SKU to the quirk as well. + +Patchset: amd-gpio +--- + arch/x86/kernel/acpi/boot.c | 9 ++++++++- + 1 file changed, 8 insertions(+), 1 deletion(-) + +diff --git a/arch/x86/kernel/acpi/boot.c b/arch/x86/kernel/acpi/boot.c +index f29740cf89ff6..247d2a8bcdf4b 100644 +--- a/arch/x86/kernel/acpi/boot.c ++++ b/arch/x86/kernel/acpi/boot.c +@@ -1258,12 +1258,19 @@ static void __init mp_config_acpi_legacy_irqs(void) + + static const struct dmi_system_id surface_quirk[] __initconst = { + { +- .ident = "Microsoft Surface Laptop 4 (AMD)", ++ .ident = "Microsoft Surface Laptop 4 (AMD 15\")", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), + DMI_MATCH(DMI_PRODUCT_SKU, "Surface_Laptop_4_1952:1953") }, }, + { ++ .ident = "Microsoft Surface Laptop 4 (AMD 13\")", + .matches = { -+ DMI_MATCH(DMI_BIOS_VENDOR, "American Megatrends Inc."), -+ DMI_MATCH(DMI_SYS_VENDOR, "OEMB"), -+ DMI_MATCH(DMI_PRODUCT_NAME, "OEMB"), ++ DMI_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), ++ DMI_MATCH(DMI_PRODUCT_SKU, "Surface_Laptop_4_1958:1959") + }, + }, - #endif - { } - }; -diff '--color=auto' -uraN cachyos/drivers/platform/surface/surface_aggregator_registry.c cachyos-surface/drivers/platform/surface/surface_aggregator_registry.c ---- cachyos/drivers/platform/surface/surface_aggregator_registry.c 2023-10-30 05:31:08.000000000 +0300 -+++ cachyos-surface/drivers/platform/surface/surface_aggregator_registry.c 2023-11-04 18:32:41.040987898 +0300 -@@ -367,6 +367,9 @@ - /* Surface Laptop Go 2 */ - { "MSHW0290", (unsigned long)ssam_node_group_slg1 }, - -+ /* Surface Laptop Go 3 */ -+ { "MSHW0440", (unsigned long)ssam_node_group_slg1 }, -+ - /* Surface Laptop Studio */ - { "MSHW0123", (unsigned long)ssam_node_group_sls }, - -diff '--color=auto' -uraN cachyos/drivers/platform/surface/surface_gpe.c cachyos-surface/drivers/platform/surface/surface_gpe.c ---- cachyos/drivers/platform/surface/surface_gpe.c 2023-10-30 05:31:08.000000000 +0300 -+++ cachyos-surface/drivers/platform/surface/surface_gpe.c 2023-11-04 18:32:41.047654661 +0300 -@@ -41,6 +41,11 @@ - {}, + {} }; -+static const struct property_entry lid_device_props_l52[] = { -+ PROPERTY_ENTRY_U32("gpe", 0x52), -+ {}, -+}; -+ - static const struct property_entry lid_device_props_l57[] = { - PROPERTY_ENTRY_U32("gpe", 0x57), - {}, -@@ -108,6 +113,18 @@ - .driver_data = (void *)lid_device_props_l4B, - }, - { -+ /* -+ * We match for SKU here due to product name clash with the ARM -+ * version. -+ */ -+ .ident = "Surface Pro 9", -+ .matches = { -+ DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -+ DMI_EXACT_MATCH(DMI_PRODUCT_SKU, "Surface_Pro_9_2038"), -+ }, -+ .driver_data = (void *)lid_device_props_l52, -+ }, -+ { - .ident = "Surface Book 1", - .matches = { - DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Microsoft Corporation"), -diff '--color=auto' -uraN cachyos/drivers/platform/surface/surfacebook1_dgpu_switch.c cachyos-surface/drivers/platform/surface/surfacebook1_dgpu_switch.c ---- cachyos/drivers/platform/surface/surfacebook1_dgpu_switch.c 1970-01-01 03:00:00.000000000 +0300 -+++ cachyos-surface/drivers/platform/surface/surfacebook1_dgpu_switch.c 2023-11-04 18:32:41.040987898 +0300 -@@ -0,0 +1,162 @@ -+// SPDX-License-Identifier: GPL-2.0-or-later -+ -+#include -+#include -+#include -+#include -+ -+ -+#ifdef pr_fmt -+#undef pr_fmt -+#endif -+#define pr_fmt(fmt) "%s:%s: " fmt, KBUILD_MODNAME, __func__ -+ -+ -+static const guid_t dgpu_sw_guid = GUID_INIT(0x6fd05c69, 0xcde3, 0x49f4, -+ 0x95, 0xed, 0xab, 0x16, 0x65, 0x49, 0x80, 0x35); -+ -+#define DGPUSW_ACPI_PATH_DSM "\\_SB_.PCI0.LPCB.EC0_.VGBI" -+#define DGPUSW_ACPI_PATH_HGON "\\_SB_.PCI0.RP05.HGON" -+#define DGPUSW_ACPI_PATH_HGOF "\\_SB_.PCI0.RP05.HGOF" -+ -+ -+static int sb1_dgpu_sw_dsmcall(void) -+{ -+ union acpi_object *ret; -+ acpi_handle handle; -+ acpi_status status; -+ -+ status = acpi_get_handle(NULL, DGPUSW_ACPI_PATH_DSM, &handle); -+ if (status) -+ return -EINVAL; -+ -+ ret = acpi_evaluate_dsm_typed(handle, &dgpu_sw_guid, 1, 1, NULL, ACPI_TYPE_BUFFER); -+ if (!ret) -+ return -EINVAL; -+ -+ ACPI_FREE(ret); -+ return 0; -+} -+ -+static int sb1_dgpu_sw_hgon(void) -+{ -+ struct acpi_buffer buf = {ACPI_ALLOCATE_BUFFER, NULL}; -+ acpi_status status; -+ -+ status = acpi_evaluate_object(NULL, DGPUSW_ACPI_PATH_HGON, NULL, &buf); -+ if (status) { -+ pr_err("failed to run HGON: %d\n", status); -+ return -EINVAL; -+ } -+ -+ if (buf.pointer) -+ ACPI_FREE(buf.pointer); -+ -+ pr_info("turned-on dGPU via HGON\n"); -+ return 0; -+} -+ -+static int sb1_dgpu_sw_hgof(void) -+{ -+ struct acpi_buffer buf = {ACPI_ALLOCATE_BUFFER, NULL}; -+ acpi_status status; -+ -+ status = acpi_evaluate_object(NULL, DGPUSW_ACPI_PATH_HGOF, NULL, &buf); -+ if (status) { -+ pr_err("failed to run HGOF: %d\n", status); -+ return -EINVAL; -+ } -+ -+ if (buf.pointer) -+ ACPI_FREE(buf.pointer); -+ -+ pr_info("turned-off dGPU via HGOF\n"); -+ return 0; -+} -+ -+ -+static ssize_t dgpu_dsmcall_store(struct device *dev, struct device_attribute *attr, -+ const char *buf, size_t len) -+{ -+ int status, value; -+ -+ status = kstrtoint(buf, 0, &value); -+ if (status < 0) -+ return status; -+ -+ if (value != 1) -+ return -EINVAL; -+ -+ status = sb1_dgpu_sw_dsmcall(); -+ -+ return status < 0 ? status : len; -+} -+ -+static ssize_t dgpu_power_store(struct device *dev, struct device_attribute *attr, -+ const char *buf, size_t len) -+{ -+ bool power; -+ int status; -+ -+ status = kstrtobool(buf, &power); -+ if (status < 0) -+ return status; -+ -+ if (power) -+ status = sb1_dgpu_sw_hgon(); -+ else -+ status = sb1_dgpu_sw_hgof(); -+ -+ return status < 0 ? status : len; -+} -+ -+static DEVICE_ATTR_WO(dgpu_dsmcall); -+static DEVICE_ATTR_WO(dgpu_power); -+ -+static struct attribute *sb1_dgpu_sw_attrs[] = { -+ &dev_attr_dgpu_dsmcall.attr, -+ &dev_attr_dgpu_power.attr, +-- +2.42.0 + +From 8cd23b1bb3a8b7a3ef7cec2c37e7e46e6397a858 Mon Sep 17 00:00:00 2001 +From: "Bart Groeneveld | GPX Solutions B.V" +Date: Mon, 5 Dec 2022 16:08:46 +0100 +Subject: [PATCH] acpi: allow usage of acpi_tad on HW-reduced platforms + +The specification [1] allows so-called HW-reduced platforms, +which do not implement everything, especially the wakeup related stuff. + +In that case, it is still usable as a RTC. This is helpful for [2] +and [3], which is about a device with no other working RTC, +but it does have an HW-reduced TAD, which can be used as a RTC instead. + +[1]: https://uefi.org/specs/ACPI/6.5/09_ACPI_Defined_Devices_and_Device_Specific_Objects.html#time-and-alarm-device +[2]: https://bugzilla.kernel.org/show_bug.cgi?id=212313 +[3]: https://github.com/linux-surface/linux-surface/issues/415 + +Signed-off-by: Bart Groeneveld | GPX Solutions B.V. +Patchset: rtc +--- + drivers/acpi/acpi_tad.c | 36 ++++++++++++++++++++++++------------ + 1 file changed, 24 insertions(+), 12 deletions(-) + +diff --git a/drivers/acpi/acpi_tad.c b/drivers/acpi/acpi_tad.c +index 33c3b16af556b..900445d06623d 100644 +--- a/drivers/acpi/acpi_tad.c ++++ b/drivers/acpi/acpi_tad.c +@@ -432,6 +432,14 @@ static ssize_t caps_show(struct device *dev, struct device_attribute *attr, + + static DEVICE_ATTR_RO(caps); + ++static struct attribute *acpi_tad_attrs[] = { ++ &dev_attr_caps.attr, + NULL, +}; -+ -+static const struct attribute_group sb1_dgpu_sw_attr_group = { -+ .attrs = sb1_dgpu_sw_attrs, ++static const struct attribute_group acpi_tad_attr_group = { ++ .attrs = acpi_tad_attrs, +}; + -+ -+static int sb1_dgpu_sw_probe(struct platform_device *pdev) -+{ -+ return sysfs_create_group(&pdev->dev.kobj, &sb1_dgpu_sw_attr_group); -+} -+ -+static int sb1_dgpu_sw_remove(struct platform_device *pdev) -+{ -+ sysfs_remove_group(&pdev->dev.kobj, &sb1_dgpu_sw_attr_group); -+ return 0; -+} -+ -+/* -+ * The dGPU power seems to be actually handled by MSHW0040. However, that is -+ * also the power-/volume-button device with a mainline driver. So let's use -+ * MSHW0041 instead for now, which seems to be the LTCH (latch/DTX) device. -+ */ -+static const struct acpi_device_id sb1_dgpu_sw_match[] = { -+ { "MSHW0041", }, -+ { }, -+}; -+MODULE_DEVICE_TABLE(acpi, sb1_dgpu_sw_match); -+ -+static struct platform_driver sb1_dgpu_sw = { -+ .probe = sb1_dgpu_sw_probe, -+ .remove = sb1_dgpu_sw_remove, -+ .driver = { -+ .name = "surfacebook1_dgpu_switch", -+ .acpi_match_table = sb1_dgpu_sw_match, -+ .probe_type = PROBE_PREFER_ASYNCHRONOUS, -+ }, -+}; -+module_platform_driver(sb1_dgpu_sw); -+ -+MODULE_AUTHOR("Maximilian Luz "); -+MODULE_DESCRIPTION("Discrete GPU Power-Switch for Surface Book 1"); -+MODULE_LICENSE("GPL"); -diff '--color=auto' -uraN cachyos/drivers/platform/surface/surfacepro3_button.c cachyos-surface/drivers/platform/surface/surfacepro3_button.c ---- cachyos/drivers/platform/surface/surfacepro3_button.c 2023-10-30 05:31:08.000000000 +0300 -+++ cachyos-surface/drivers/platform/surface/surfacepro3_button.c 2023-11-04 18:32:41.044321279 +0300 -@@ -149,7 +149,8 @@ - /* - * Surface Pro 4 and Surface Book 2 / Surface Pro 2017 use the same device - * ID (MSHW0040) for the power/volume buttons. Make sure this is the right -- * device by checking for the _DSM method and OEM Platform Revision. -+ * device by checking for the _DSM method and OEM Platform Revision DSM -+ * function. - * - * Returns true if the driver should bind to this device, i.e. the device is - * either MSWH0028 (Pro 3) or MSHW0040 on a Pro 4 or Book 1. -@@ -157,30 +158,11 @@ - static bool surface_button_check_MSHW0040(struct acpi_device *dev) + static ssize_t ac_alarm_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) { - acpi_handle handle = dev->handle; -- union acpi_object *result; -- u64 oem_platform_rev = 0; // valid revisions are nonzero +@@ -480,15 +488,14 @@ static ssize_t ac_status_show(struct device *dev, struct device_attribute *attr, -- // get OEM platform revision -- result = acpi_evaluate_dsm_typed(handle, &MSHW0040_DSM_UUID, -- MSHW0040_DSM_REVISION, -- MSHW0040_DSM_GET_OMPR, -- NULL, ACPI_TYPE_INTEGER); -- -- /* -- * If evaluating the _DSM fails, the method is not present. This means -- * that we have either MSHW0028 or MSHW0040 on Pro 4 or Book 1, so we -- * should use this driver. We use revision 0 indicating it is -- * unavailable. -- */ -- -- if (result) { -- oem_platform_rev = result->integer.value; -- ACPI_FREE(result); -- } -- -- dev_dbg(&dev->dev, "OEM Platform Revision %llu\n", oem_platform_rev); -- -- return oem_platform_rev == 0; -+ // make sure that OEM platform revision DSM call does not exist -+ return !acpi_check_dsm(handle, &MSHW0040_DSM_UUID, -+ MSHW0040_DSM_REVISION, -+ BIT(MSHW0040_DSM_GET_OMPR)); - } + static DEVICE_ATTR_RW(ac_status); - -diff '--color=auto' -uraN cachyos/drivers/platform/x86/intel/int3472/discrete.c cachyos-surface/drivers/platform/x86/intel/int3472/discrete.c ---- cachyos/drivers/platform/x86/intel/int3472/discrete.c 2023-10-30 05:31:08.000000000 +0300 -+++ cachyos-surface/drivers/platform/x86/intel/int3472/discrete.c 2023-11-04 18:32:41.054321425 +0300 -@@ -57,6 +57,9 @@ - const char *func, u32 polarity) - { - char *path = agpio->resource_source.string_ptr; -+ const struct acpi_device_id ov7251_ids[] = { -+ { "INT347E" }, -+ }; - struct gpiod_lookup *table_entry; - struct acpi_device *adev; - acpi_handle handle; -@@ -67,6 +70,17 @@ - return -EINVAL; - } - -+ /* -+ * In addition to the function remap table we need to bulk remap the -+ * "reset" GPIO for the OmniVision 7251 sensor, as the driver for that -+ * expects its only GPIO pin to be called "enable" (and to have the -+ * opposite polarity). -+ */ -+ if (!strcmp(func, "reset") && !acpi_match_device_ids(int3472->sensor, ov7251_ids)) { -+ func = "enable"; -+ polarity = GPIO_ACTIVE_HIGH; -+ } -+ - status = acpi_get_handle(NULL, path, &handle); - if (ACPI_FAILURE(status)) - return -EINVAL; -diff '--color=auto' -uraN cachyos/drivers/platform/x86/intel/int3472/tps68470.c cachyos-surface/drivers/platform/x86/intel/int3472/tps68470.c ---- cachyos/drivers/platform/x86/intel/int3472/tps68470.c 2023-10-30 05:31:08.000000000 +0300 -+++ cachyos-surface/drivers/platform/x86/intel/int3472/tps68470.c 2023-11-04 18:32:41.054321425 +0300 -@@ -17,7 +17,7 @@ - #define DESIGNED_FOR_CHROMEOS 1 - #define DESIGNED_FOR_WINDOWS 2 - --#define TPS68470_WIN_MFD_CELL_COUNT 3 -+#define TPS68470_WIN_MFD_CELL_COUNT 4 - - static const struct mfd_cell tps68470_cros[] = { - { .name = "tps68470-gpio" }, -@@ -46,6 +46,13 @@ - return ret; - } - -+ /* Enable I2C daisy chain */ -+ ret = regmap_write(regmap, TPS68470_REG_S_I2C_CTL, 0x03); -+ if (ret) { -+ dev_err(dev, "Failed to enable i2c daisy chain\n"); -+ return ret; -+ } -+ - dev_info(dev, "TPS68470 REVID: 0x%02x\n", version); - - return 0; -@@ -193,7 +200,8 @@ - cells[1].name = "tps68470-regulator"; - cells[1].platform_data = (void *)board_data->tps68470_regulator_pdata; - cells[1].pdata_size = sizeof(struct tps68470_regulator_platform_data); -- cells[2].name = "tps68470-gpio"; -+ cells[2].name = "tps68470-led"; -+ cells[3].name = "tps68470-gpio"; - - for (i = 0; i < board_data->n_gpiod_lookups; i++) - gpiod_add_lookup_table(board_data->tps68470_gpio_lookup_tables[i]); -diff '--color=auto' -uraN cachyos/drivers/usb/core/quirks.c cachyos-surface/drivers/usb/core/quirks.c ---- cachyos/drivers/usb/core/quirks.c 2023-10-30 05:31:08.000000000 +0300 -+++ cachyos-surface/drivers/usb/core/quirks.c 2023-11-04 18:32:41.044321279 +0300 -@@ -220,6 +220,9 @@ - /* Microsoft Surface Dock Ethernet (RTL8153 GigE) */ - { USB_DEVICE(0x045e, 0x07c6), .driver_info = USB_QUIRK_NO_LPM }, - -+ /* Microsoft Surface Go 3 Type-Cover */ -+ { USB_DEVICE(0x045e, 0x09b5), .driver_info = USB_QUIRK_DELAY_INIT }, -+ - /* Cherry Stream G230 2.0 (G85-231) and 3.0 (G85-232) */ - { USB_DEVICE(0x046a, 0x0023), .driver_info = USB_QUIRK_RESET_RESUME }, - -diff '--color=auto' -uraN cachyos/include/linux/mfd/tps68470.h cachyos-surface/include/linux/mfd/tps68470.h ---- cachyos/include/linux/mfd/tps68470.h 2023-10-30 05:31:08.000000000 +0300 -+++ cachyos-surface/include/linux/mfd/tps68470.h 2023-11-04 18:32:41.054321425 +0300 -@@ -34,6 +34,7 @@ - #define TPS68470_REG_SGPO 0x22 - #define TPS68470_REG_GPDI 0x26 - #define TPS68470_REG_GPDO 0x27 -+#define TPS68470_REG_ILEDCTL 0x28 - #define TPS68470_REG_VCMVAL 0x3C - #define TPS68470_REG_VAUX1VAL 0x3D - #define TPS68470_REG_VAUX2VAL 0x3E -@@ -94,4 +95,8 @@ - #define TPS68470_GPIO_MODE_OUT_CMOS 2 - #define TPS68470_GPIO_MODE_OUT_ODRAIN 3 - -+#define TPS68470_ILEDCTL_ENA BIT(2) -+#define TPS68470_ILEDCTL_ENB BIT(6) -+#define TPS68470_ILEDCTL_CTRLB GENMASK(5, 4) -+ - #endif /* __LINUX_MFD_TPS68470_H */ -diff '--color=auto' -uraN cachyos/include/linux/pci.h cachyos-surface/include/linux/pci.h ---- cachyos/include/linux/pci.h 2023-10-30 05:31:08.000000000 +0300 -+++ cachyos-surface/include/linux/pci.h 2023-11-04 18:33:26.555030308 +0300 -@@ -465,6 +465,7 @@ - unsigned int no_command_memory:1; /* No PCI_COMMAND_MEMORY */ - unsigned int rom_bar_overlap:1; /* ROM BAR disable broken */ - unsigned int rom_attr_enabled:1; /* Display of ROM attribute enabled? */ -+ unsigned int no_shutdown:1; /* Do not touch device on shutdown */ - pci_dev_flags_t dev_flags; - atomic_t enable_cnt; /* pci_enable_device has been called */ - -diff '--color=auto' -uraN cachyos/sound/soc/codecs/rt5645.c cachyos-surface/sound/soc/codecs/rt5645.c ---- cachyos/sound/soc/codecs/rt5645.c 2023-10-30 05:31:08.000000000 +0300 -+++ cachyos-surface/sound/soc/codecs/rt5645.c 2023-11-04 18:32:41.030987752 +0300 -@@ -3747,6 +3747,15 @@ - .driver_data = (void *)&intel_braswell_platform_data, - }, - { -+ .ident = "Microsoft Surface 3", -+ .matches = { -+ DMI_MATCH(DMI_BIOS_VENDOR, "American Megatrends Inc."), -+ DMI_MATCH(DMI_SYS_VENDOR, "OEMB"), -+ DMI_MATCH(DMI_PRODUCT_NAME, "OEMB"), -+ }, -+ .driver_data = (void *)&intel_braswell_platform_data, -+ }, -+ { - /* - * Match for the GPDwin which unfortunately uses somewhat - * generic dmi strings, which is why we test for 4 strings. -diff '--color=auto' -uraN cachyos/sound/soc/intel/common/soc-acpi-intel-cht-match.c cachyos-surface/sound/soc/intel/common/soc-acpi-intel-cht-match.c ---- cachyos/sound/soc/intel/common/soc-acpi-intel-cht-match.c 2023-10-30 05:31:08.000000000 +0300 -+++ cachyos-surface/sound/soc/intel/common/soc-acpi-intel-cht-match.c 2023-11-04 18:32:41.034321134 +0300 -@@ -27,6 +27,14 @@ - DMI_MATCH(DMI_PRODUCT_NAME, "Surface 3"), - }, - }, -+ { -+ .callback = cht_surface_quirk_cb, -+ .matches = { -+ DMI_MATCH(DMI_BIOS_VENDOR, "American Megatrends Inc."), -+ DMI_MATCH(DMI_SYS_VENDOR, "OEMB"), -+ DMI_MATCH(DMI_PRODUCT_NAME, "OEMB"), -+ }, -+ }, - { } +-static struct attribute *acpi_tad_attrs[] = { +- &dev_attr_caps.attr, ++static struct attribute *acpi_tad_ac_attrs[] = { + &dev_attr_ac_alarm.attr, + &dev_attr_ac_policy.attr, + &dev_attr_ac_status.attr, + NULL, + }; +-static const struct attribute_group acpi_tad_attr_group = { +- .attrs = acpi_tad_attrs, ++static const struct attribute_group acpi_tad_ac_attr_group = { ++ .attrs = acpi_tad_ac_attrs, }; + static ssize_t dc_alarm_store(struct device *dev, struct device_attribute *attr, +@@ -564,13 +571,18 @@ static int acpi_tad_remove(struct platform_device *pdev) + + pm_runtime_get_sync(dev); + ++ if (dd->capabilities & ACPI_TAD_AC_WAKE) ++ sysfs_remove_group(&dev->kobj, &acpi_tad_ac_attr_group); ++ + if (dd->capabilities & ACPI_TAD_DC_WAKE) + sysfs_remove_group(&dev->kobj, &acpi_tad_dc_attr_group); + + sysfs_remove_group(&dev->kobj, &acpi_tad_attr_group); + +- acpi_tad_disable_timer(dev, ACPI_TAD_AC_TIMER); +- acpi_tad_clear_status(dev, ACPI_TAD_AC_TIMER); ++ if (dd->capabilities & ACPI_TAD_AC_WAKE) { ++ acpi_tad_disable_timer(dev, ACPI_TAD_AC_TIMER); ++ acpi_tad_clear_status(dev, ACPI_TAD_AC_TIMER); ++ } + if (dd->capabilities & ACPI_TAD_DC_WAKE) { + acpi_tad_disable_timer(dev, ACPI_TAD_DC_TIMER); + acpi_tad_clear_status(dev, ACPI_TAD_DC_TIMER); +@@ -613,12 +625,6 @@ static int acpi_tad_probe(struct platform_device *pdev) + goto remove_handler; + } + +- if (!acpi_has_method(handle, "_PRW")) { +- dev_info(dev, "Missing _PRW\n"); +- ret = -ENODEV; +- goto remove_handler; +- } +- + dd = devm_kzalloc(dev, sizeof(*dd), GFP_KERNEL); + if (!dd) { + ret = -ENOMEM; +@@ -649,6 +655,12 @@ static int acpi_tad_probe(struct platform_device *pdev) + if (ret) + goto fail; + ++ if (caps & ACPI_TAD_AC_WAKE) { ++ ret = sysfs_create_group(&dev->kobj, &acpi_tad_ac_attr_group); ++ if (ret) ++ goto fail; ++ } ++ + if (caps & ACPI_TAD_DC_WAKE) { + ret = sysfs_create_group(&dev->kobj, &acpi_tad_dc_attr_group); + if (ret) +-- +2.42.0 + diff --git a/patches/nobara/mt76:-mt7921:-Disable-powersave-features-by-default.patch b/patches/nobara/mt76:-mt7921:-Disable-powersave-features-by-default.patch new file mode 100644 index 0000000..a397014 --- /dev/null +++ b/patches/nobara/mt76:-mt7921:-Disable-powersave-features-by-default.patch @@ -0,0 +1,42 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jan200101 +Date: Mon, 27 Nov 2023 15:25:48 +0100 +Subject: [PATCH] mt76: mt7921: Disable powersave features by default + +This brings WiFi latency down considerably and makes latency consistent by +disabling runtime PM and typical powersave features by default. The actual +power consumption difference is inconsequential on desktops and laptops, +while the performance difference is monumental. Latencies of 20+ ms are no +longer observed after this change, and the connection is much more stable. + +Signed-off-by: Jan200101 +--- + drivers/net/wireless/mediatek/mt76/mt7921/init.c | 8 ++------ + 1 file changed, 2 insertions(+), 6 deletions(-) + +diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/init.c b/drivers/net/wireless/mediatek/mt76/mt7921/init.c +index ff63f37f67d9..840b4c606c83 100644 +--- a/drivers/net/wireless/mediatek/mt76/mt7921/init.c ++++ b/drivers/net/wireless/mediatek/mt76/mt7921/init.c +@@ -220,12 +220,6 @@ int mt7921_register_device(struct mt792x_dev *dev) + dev->pm.idle_timeout = MT792x_PM_TIMEOUT; + dev->pm.stats.last_wake_event = jiffies; + dev->pm.stats.last_doze_event = jiffies; +- if (!mt76_is_usb(&dev->mt76)) { +- dev->pm.enable_user = true; +- dev->pm.enable = true; +- dev->pm.ds_enable_user = true; +- dev->pm.ds_enable = true; +- } + + if (!mt76_is_mmio(&dev->mt76)) + hw->extra_tx_headroom += MT_SDIO_TXD_SIZE + MT_SDIO_HDR_SIZE; +@@ -240,6 +234,8 @@ int mt7921_register_device(struct mt792x_dev *dev) + if (ret) + return ret; + ++ hw->wiphy->flags &= ~WIPHY_FLAG_PS_ON_BY_DEFAULT; ++ + hw->wiphy->reg_notifier = mt7921_regd_notifier; + dev->mphy.sband_2g.sband.ht_cap.cap |= + IEEE80211_HT_CAP_LDPC_CODING | diff --git a/patches/nobara/rog-ally-alsa.patch b/patches/nobara/rog-ally-alsa.patch deleted file mode 100644 index 6790d0e..0000000 --- a/patches/nobara/rog-ally-alsa.patch +++ /dev/null @@ -1,1007 +0,0 @@ -From: Cristian Ciocaltea @ 2023-09-07 17:10 UTC (permalink / raw) - To: James Schulman, David Rhodes, Richard Fitzgerald, - Jaroslav Kysela, Takashi Iwai, Liam Girdwood, Mark Brown, - Stefan Binding, Charles Keepax, Vitaly Rodionov - Cc: alsa-devel, patches, linux-kernel, kernel - -The return code of regmap_multi_reg_write() call related to "MDSYNC -down" sequence is shadowed by the subsequent -wait_for_completion_timeout() invocation, which is expected to time -timeout in case the write operation failed. - -Let cs35l41_global_enable() return the correct error code instead of --ETIMEDOUT. - -Fixes: f5030564938b ("ALSA: cs35l41: Add shared boost feature") -Signed-off-by: Cristian Ciocaltea -Acked-by: Charles Keepax ---- - sound/soc/codecs/cs35l41-lib.c | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/sound/soc/codecs/cs35l41-lib.c b/sound/soc/codecs/cs35l41-lib.c -index 4ec306cd2f47..a018f1d98428 100644 ---- a/sound/soc/codecs/cs35l41-lib.c -+++ b/sound/soc/codecs/cs35l41-lib.c -@@ -1243,7 +1243,7 @@ int cs35l41_global_enable(struct device *dev, struct regmap *regmap, enum cs35l4 - cs35l41_mdsync_down_seq[2].def = pwr_ctrl1; - ret = regmap_multi_reg_write(regmap, cs35l41_mdsync_down_seq, - ARRAY_SIZE(cs35l41_mdsync_down_seq)); -- if (!enable) -+ if (ret || !enable) - break; - - if (!pll_lock) --- -2.41.0 -From: Cristian Ciocaltea @ 2023-09-07 17:10 UTC (permalink / raw) - To: James Schulman, David Rhodes, Richard Fitzgerald, - Jaroslav Kysela, Takashi Iwai, Liam Girdwood, Mark Brown, - Stefan Binding, Charles Keepax, Vitaly Rodionov - Cc: alsa-devel, patches, linux-kernel, kernel - -The return code of regmap_multi_reg_write() call related to "MDSYNC up" -sequence is shadowed by the subsequent regmap_read_poll_timeout() -invocation, which will hit a timeout in case the write operation above -fails. - -Make sure cs35l41_global_enable() returns the correct error code instead -of -ETIMEDOUT. - -Additionally, to be able to distinguish between the timeouts of -wait_for_completion_timeout() and regmap_read_poll_timeout(), print an -error message for the former and return immediately. This also avoids -having to wait unnecessarily for the second time. - -Fixes: f8264c759208 ("ALSA: cs35l41: Poll for Power Up/Down rather than waiting a fixed delay") -Signed-off-by: Cristian Ciocaltea -Acked-by: Charles Keepax ---- - sound/soc/codecs/cs35l41-lib.c | 17 ++++++++++------- - 1 file changed, 10 insertions(+), 7 deletions(-) - -diff --git a/sound/soc/codecs/cs35l41-lib.c b/sound/soc/codecs/cs35l41-lib.c -index a018f1d98428..a6c6bb23b957 100644 ---- a/sound/soc/codecs/cs35l41-lib.c -+++ b/sound/soc/codecs/cs35l41-lib.c -@@ -1251,15 +1251,18 @@ int cs35l41_global_enable(struct device *dev, struct regmap *regmap, enum cs35l4 - - ret = wait_for_completion_timeout(pll_lock, msecs_to_jiffies(1000)); - if (ret == 0) { -- ret = -ETIMEDOUT; -- } else { -- regmap_read(regmap, CS35L41_PWR_CTRL3, &pwr_ctrl3); -- pwr_ctrl3 |= CS35L41_SYNC_EN_MASK; -- cs35l41_mdsync_up_seq[0].def = pwr_ctrl3; -- ret = regmap_multi_reg_write(regmap, cs35l41_mdsync_up_seq, -- ARRAY_SIZE(cs35l41_mdsync_up_seq)); -+ dev_err(dev, "Timed out waiting for pll_lock\n"); -+ return -ETIMEDOUT; - } - -+ regmap_read(regmap, CS35L41_PWR_CTRL3, &pwr_ctrl3); -+ pwr_ctrl3 |= CS35L41_SYNC_EN_MASK; -+ cs35l41_mdsync_up_seq[0].def = pwr_ctrl3; -+ ret = regmap_multi_reg_write(regmap, cs35l41_mdsync_up_seq, -+ ARRAY_SIZE(cs35l41_mdsync_up_seq)); -+ if (ret) -+ return ret; -+ - ret = regmap_read_poll_timeout(regmap, CS35L41_IRQ1_STATUS1, - int_status, int_status & pup_pdn_mask, - 1000, 100000); --- -2.41.0 -From: Cristian Ciocaltea @ 2023-09-07 17:10 UTC (permalink / raw) - To: James Schulman, David Rhodes, Richard Fitzgerald, - Jaroslav Kysela, Takashi Iwai, Liam Girdwood, Mark Brown, - Stefan Binding, Charles Keepax, Vitaly Rodionov - Cc: alsa-devel, patches, linux-kernel, kernel - -Technically, an interrupt handler can be called before probe() finishes -its execution, hence ensure the pll_lock completion object is always -initialized before being accessed in cs35l41_irq(). - -Fixes: f5030564938b ("ALSA: cs35l41: Add shared boost feature") -Signed-off-by: Cristian Ciocaltea -Acked-by: Charles Keepax ---- - sound/soc/codecs/cs35l41.c | 4 ++-- - 1 file changed, 2 insertions(+), 2 deletions(-) - -diff --git a/sound/soc/codecs/cs35l41.c b/sound/soc/codecs/cs35l41.c -index 722b69a6de26..fe5376b3e01b 100644 ---- a/sound/soc/codecs/cs35l41.c -+++ b/sound/soc/codecs/cs35l41.c -@@ -1273,6 +1273,8 @@ int cs35l41_probe(struct cs35l41_private *cs35l41, const struct cs35l41_hw_cfg * - regmap_update_bits(cs35l41->regmap, CS35L41_IRQ1_MASK3, CS35L41_INT3_PLL_LOCK_MASK, - 0 << CS35L41_INT3_PLL_LOCK_SHIFT); - -+ init_completion(&cs35l41->pll_lock); -+ - ret = devm_request_threaded_irq(cs35l41->dev, cs35l41->irq, NULL, cs35l41_irq, - IRQF_ONESHOT | IRQF_SHARED | irq_pol, - "cs35l41", cs35l41); -@@ -1295,8 +1297,6 @@ int cs35l41_probe(struct cs35l41_private *cs35l41, const struct cs35l41_hw_cfg * - if (ret < 0) - goto err; - -- init_completion(&cs35l41->pll_lock); -- - pm_runtime_set_autosuspend_delay(cs35l41->dev, 3000); - pm_runtime_use_autosuspend(cs35l41->dev); - pm_runtime_mark_last_busy(cs35l41->dev); --- -2.41.0 -From: Cristian Ciocaltea @ 2023-09-07 17:10 UTC (permalink / raw) - To: James Schulman, David Rhodes, Richard Fitzgerald, - Jaroslav Kysela, Takashi Iwai, Liam Girdwood, Mark Brown, - Stefan Binding, Charles Keepax, Vitaly Rodionov - Cc: alsa-devel, patches, linux-kernel, kernel - -Enabling the active/passive shared boosts requires setting SYNC_EN, but -*not* before receiving the PLL Lock signal. - -Due to improper error handling, it was not obvious that waiting for the -completion operation times out and, consequently, the shared boost is -never activated. - -Further investigations revealed the signal is triggered while -snd_pcm_start() is executed, right after receiving the -SNDRV_PCM_TRIGGER_START command, which happens long after the -SND_SOC_DAPM_PRE_PMU event handler is invoked as part of -snd_pcm_prepare(). That is where cs35l41_global_enable() is called -from. - -Increasing the wait duration doesn't help, as it only causes an -unnecessary delay in the invocation of snd_pcm_start(). Moving the wait -and the subsequent regmap operations to the SNDRV_PCM_TRIGGER_START -callback is not a solution either, since they would be executed in an -IRQ-off atomic context. - -Solve the issue by setting the SYNC_EN bit in PWR_CTRL3 register right -after receiving the PLL Lock interrupt. - -Additionally, drop the unnecessary writes to PWR_CTRL1 register, part of -the original mdsync_up_seq, which would have toggled GLOBAL_EN with -unwanted consequences on PLL locking behavior. - -Fixes: f5030564938b ("ALSA: cs35l41: Add shared boost feature") -Signed-off-by: Cristian Ciocaltea ---- - include/sound/cs35l41.h | 4 +-- - sound/pci/hda/cs35l41_hda.c | 4 +-- - sound/soc/codecs/cs35l41-lib.c | 61 ++++++++++++++++++++-------------- - sound/soc/codecs/cs35l41.c | 24 ++++++++----- - sound/soc/codecs/cs35l41.h | 1 - - 5 files changed, 55 insertions(+), 39 deletions(-) - -diff --git a/include/sound/cs35l41.h b/include/sound/cs35l41.h -index 1bf757901d02..2fe8c6b0d4cf 100644 ---- a/include/sound/cs35l41.h -+++ b/include/sound/cs35l41.h -@@ -11,7 +11,6 @@ - #define __CS35L41_H - - #include --#include - #include - - #define CS35L41_FIRSTREG 0x00000000 -@@ -902,7 +901,8 @@ int cs35l41_exit_hibernate(struct device *dev, struct regmap *regmap); - int cs35l41_init_boost(struct device *dev, struct regmap *regmap, - struct cs35l41_hw_cfg *hw_cfg); - bool cs35l41_safe_reset(struct regmap *regmap, enum cs35l41_boost_type b_type); -+int cs35l41_mdsync_up(struct regmap *regmap); - int cs35l41_global_enable(struct device *dev, struct regmap *regmap, enum cs35l41_boost_type b_type, -- int enable, struct completion *pll_lock, bool firmware_running); -+ int enable, bool firmware_running); - - #endif /* __CS35L41_H */ -diff --git a/sound/pci/hda/cs35l41_hda.c b/sound/pci/hda/cs35l41_hda.c -index f9b77353c266..09a9c135d9b6 100644 ---- a/sound/pci/hda/cs35l41_hda.c -+++ b/sound/pci/hda/cs35l41_hda.c -@@ -527,7 +527,7 @@ static void cs35l41_hda_play_done(struct device *dev) - - dev_dbg(dev, "Play (Complete)\n"); - -- cs35l41_global_enable(dev, reg, cs35l41->hw_cfg.bst_type, 1, NULL, -+ cs35l41_global_enable(dev, reg, cs35l41->hw_cfg.bst_type, 1, - cs35l41->firmware_running); - if (cs35l41->firmware_running) { - regmap_multi_reg_write(reg, cs35l41_hda_unmute_dsp, -@@ -546,7 +546,7 @@ static void cs35l41_hda_pause_start(struct device *dev) - dev_dbg(dev, "Pause (Start)\n"); - - regmap_multi_reg_write(reg, cs35l41_hda_mute, ARRAY_SIZE(cs35l41_hda_mute)); -- cs35l41_global_enable(dev, reg, cs35l41->hw_cfg.bst_type, 0, NULL, -+ cs35l41_global_enable(dev, reg, cs35l41->hw_cfg.bst_type, 0, - cs35l41->firmware_running); - } - -diff --git a/sound/soc/codecs/cs35l41-lib.c b/sound/soc/codecs/cs35l41-lib.c -index a6c6bb23b957..2ec5fdc875b1 100644 ---- a/sound/soc/codecs/cs35l41-lib.c -+++ b/sound/soc/codecs/cs35l41-lib.c -@@ -1192,8 +1192,28 @@ bool cs35l41_safe_reset(struct regmap *regmap, enum cs35l41_boost_type b_type) - } - EXPORT_SYMBOL_GPL(cs35l41_safe_reset); - -+/* -+ * Enabling the CS35L41_SHD_BOOST_ACTV and CS35L41_SHD_BOOST_PASS shared boosts -+ * does also require a call to cs35l41_mdsync_up(), but not before getting the -+ * PLL Lock signal. -+ * -+ * PLL Lock seems to be triggered soon after snd_pcm_start() is executed and -+ * SNDRV_PCM_TRIGGER_START command is processed, which happens (long) after the -+ * SND_SOC_DAPM_PRE_PMU event handler is invoked as part of snd_pcm_prepare(). -+ * -+ * This event handler is where cs35l41_global_enable() is normally called from, -+ * but waiting for PLL Lock here will time out. Increasing the wait duration -+ * will not help, as the only consequence of it would be to add an unnecessary -+ * delay in the invocation of snd_pcm_start(). -+ * -+ * Trying to move the wait in the SNDRV_PCM_TRIGGER_START callback is not a -+ * solution either, as the trigger is executed in an IRQ-off atomic context. -+ * -+ * The current approach is to invoke cs35l41_mdsync_up() right after receiving -+ * the PLL Lock interrupt, in the IRQ handler. -+ */ - int cs35l41_global_enable(struct device *dev, struct regmap *regmap, enum cs35l41_boost_type b_type, -- int enable, struct completion *pll_lock, bool firmware_running) -+ int enable, bool firmware_running) - { - int ret; - unsigned int gpio1_func, pad_control, pwr_ctrl1, pwr_ctrl3, int_status, pup_pdn_mask; -@@ -1203,11 +1223,6 @@ int cs35l41_global_enable(struct device *dev, struct regmap *regmap, enum cs35l4 - {CS35L41_GPIO_PAD_CONTROL, 0}, - {CS35L41_PWR_CTRL1, 0, 3000}, - }; -- struct reg_sequence cs35l41_mdsync_up_seq[] = { -- {CS35L41_PWR_CTRL3, 0}, -- {CS35L41_PWR_CTRL1, 0x00000000, 3000}, -- {CS35L41_PWR_CTRL1, 0x00000001, 3000}, -- }; - - pup_pdn_mask = enable ? CS35L41_PUP_DONE_MASK : CS35L41_PDN_DONE_MASK; - -@@ -1241,26 +1256,11 @@ int cs35l41_global_enable(struct device *dev, struct regmap *regmap, enum cs35l4 - cs35l41_mdsync_down_seq[0].def = pwr_ctrl3; - cs35l41_mdsync_down_seq[1].def = pad_control; - cs35l41_mdsync_down_seq[2].def = pwr_ctrl1; -+ - ret = regmap_multi_reg_write(regmap, cs35l41_mdsync_down_seq, - ARRAY_SIZE(cs35l41_mdsync_down_seq)); -- if (ret || !enable) -- break; -- -- if (!pll_lock) -- return -EINVAL; -- -- ret = wait_for_completion_timeout(pll_lock, msecs_to_jiffies(1000)); -- if (ret == 0) { -- dev_err(dev, "Timed out waiting for pll_lock\n"); -- return -ETIMEDOUT; -- } -- -- regmap_read(regmap, CS35L41_PWR_CTRL3, &pwr_ctrl3); -- pwr_ctrl3 |= CS35L41_SYNC_EN_MASK; -- cs35l41_mdsync_up_seq[0].def = pwr_ctrl3; -- ret = regmap_multi_reg_write(regmap, cs35l41_mdsync_up_seq, -- ARRAY_SIZE(cs35l41_mdsync_up_seq)); -- if (ret) -+ /* Activation to be completed later via cs35l41_mdsync_up() */ -+ if (ret || enable) - return ret; - - ret = regmap_read_poll_timeout(regmap, CS35L41_IRQ1_STATUS1, -@@ -1269,7 +1269,7 @@ int cs35l41_global_enable(struct device *dev, struct regmap *regmap, enum cs35l4 - if (ret) - dev_err(dev, "Enable(%d) failed: %d\n", enable, ret); - -- // Clear PUP/PDN status -+ /* Clear PUP/PDN status */ - regmap_write(regmap, CS35L41_IRQ1_STATUS1, pup_pdn_mask); - break; - case CS35L41_INT_BOOST: -@@ -1351,6 +1351,17 @@ int cs35l41_global_enable(struct device *dev, struct regmap *regmap, enum cs35l4 - } - EXPORT_SYMBOL_GPL(cs35l41_global_enable); - -+/* -+ * To be called after receiving the IRQ Lock interrupt, in order to complete -+ * any shared boost activation initiated by cs35l41_global_enable(). -+ */ -+int cs35l41_mdsync_up(struct regmap *regmap) -+{ -+ return regmap_update_bits(regmap, CS35L41_PWR_CTRL3, -+ CS35L41_SYNC_EN_MASK, CS35L41_SYNC_EN_MASK); -+} -+EXPORT_SYMBOL_GPL(cs35l41_mdsync_up); -+ - int cs35l41_gpio_config(struct regmap *regmap, struct cs35l41_hw_cfg *hw_cfg) - { - struct cs35l41_gpio_cfg *gpio1 = &hw_cfg->gpio1; -diff --git a/sound/soc/codecs/cs35l41.c b/sound/soc/codecs/cs35l41.c -index fe5376b3e01b..12327b4c3d56 100644 ---- a/sound/soc/codecs/cs35l41.c -+++ b/sound/soc/codecs/cs35l41.c -@@ -459,7 +459,19 @@ static irqreturn_t cs35l41_irq(int irq, void *data) - - if (status[2] & CS35L41_PLL_LOCK) { - regmap_write(cs35l41->regmap, CS35L41_IRQ1_STATUS3, CS35L41_PLL_LOCK); -- complete(&cs35l41->pll_lock); -+ -+ if (cs35l41->hw_cfg.bst_type == CS35L41_SHD_BOOST_ACTV || -+ cs35l41->hw_cfg.bst_type == CS35L41_SHD_BOOST_PASS) { -+ ret = cs35l41_mdsync_up(cs35l41->regmap); -+ if (ret) -+ dev_err(cs35l41->dev, "MDSYNC-up failed: %d\n", ret); -+ else -+ dev_dbg(cs35l41->dev, "MDSYNC-up done\n"); -+ -+ dev_dbg(cs35l41->dev, "PUP-done status: %d\n", -+ !!(status[0] & CS35L41_PUP_DONE_MASK)); -+ } -+ - ret = IRQ_HANDLED; - } - -@@ -500,11 +512,11 @@ static int cs35l41_main_amp_event(struct snd_soc_dapm_widget *w, - ARRAY_SIZE(cs35l41_pup_patch)); - - ret = cs35l41_global_enable(cs35l41->dev, cs35l41->regmap, cs35l41->hw_cfg.bst_type, -- 1, &cs35l41->pll_lock, cs35l41->dsp.cs_dsp.running); -+ 1, cs35l41->dsp.cs_dsp.running); - break; - case SND_SOC_DAPM_POST_PMD: - ret = cs35l41_global_enable(cs35l41->dev, cs35l41->regmap, cs35l41->hw_cfg.bst_type, -- 0, &cs35l41->pll_lock, cs35l41->dsp.cs_dsp.running); -+ 0, cs35l41->dsp.cs_dsp.running); - - regmap_multi_reg_write_bypassed(cs35l41->regmap, - cs35l41_pdn_patch, -@@ -802,10 +814,6 @@ static const struct snd_pcm_hw_constraint_list cs35l41_constraints = { - static int cs35l41_pcm_startup(struct snd_pcm_substream *substream, - struct snd_soc_dai *dai) - { -- struct cs35l41_private *cs35l41 = snd_soc_component_get_drvdata(dai->component); -- -- reinit_completion(&cs35l41->pll_lock); -- - if (substream->runtime) - return snd_pcm_hw_constraint_list(substream->runtime, 0, - SNDRV_PCM_HW_PARAM_RATE, -@@ -1273,8 +1281,6 @@ int cs35l41_probe(struct cs35l41_private *cs35l41, const struct cs35l41_hw_cfg * - regmap_update_bits(cs35l41->regmap, CS35L41_IRQ1_MASK3, CS35L41_INT3_PLL_LOCK_MASK, - 0 << CS35L41_INT3_PLL_LOCK_SHIFT); - -- init_completion(&cs35l41->pll_lock); -- - ret = devm_request_threaded_irq(cs35l41->dev, cs35l41->irq, NULL, cs35l41_irq, - IRQF_ONESHOT | IRQF_SHARED | irq_pol, - "cs35l41", cs35l41); -diff --git a/sound/soc/codecs/cs35l41.h b/sound/soc/codecs/cs35l41.h -index 34d967d4372b..c85cbc1dd333 100644 ---- a/sound/soc/codecs/cs35l41.h -+++ b/sound/soc/codecs/cs35l41.h -@@ -33,7 +33,6 @@ struct cs35l41_private { - int irq; - /* GPIO for /RST */ - struct gpio_desc *reset_gpio; -- struct completion pll_lock; - }; - - int cs35l41_probe(struct cs35l41_private *cs35l41, const struct cs35l41_hw_cfg *hw_cfg); --- -2.41.0 -From: Cristian Ciocaltea @ 2023-09-07 17:10 UTC (permalink / raw) - To: James Schulman, David Rhodes, Richard Fitzgerald, - Jaroslav Kysela, Takashi Iwai, Liam Girdwood, Mark Brown, - Stefan Binding, Charles Keepax, Vitaly Rodionov - Cc: alsa-devel, patches, linux-kernel, kernel - -The interrupt handler invokes pm_runtime_get_sync() without checking the -returned error code. - -Add a proper verification and switch to pm_runtime_resume_and_get(), to -avoid the need to call pm_runtime_put_noidle() for decrementing the PM -usage counter before returning from the error condition. - -Fixes: f517ba4924ad ("ASoC: cs35l41: Add support for hibernate memory retention mode") -Signed-off-by: Cristian Ciocaltea -Acked-by: Charles Keepax ---- - sound/soc/codecs/cs35l41.c | 12 ++++++++++-- - 1 file changed, 10 insertions(+), 2 deletions(-) - -diff --git a/sound/soc/codecs/cs35l41.c b/sound/soc/codecs/cs35l41.c -index 12327b4c3d56..a31cb9ba7f7d 100644 ---- a/sound/soc/codecs/cs35l41.c -+++ b/sound/soc/codecs/cs35l41.c -@@ -386,10 +386,18 @@ static irqreturn_t cs35l41_irq(int irq, void *data) - struct cs35l41_private *cs35l41 = data; - unsigned int status[4] = { 0, 0, 0, 0 }; - unsigned int masks[4] = { 0, 0, 0, 0 }; -- int ret = IRQ_NONE; - unsigned int i; -+ int ret; - -- pm_runtime_get_sync(cs35l41->dev); -+ ret = pm_runtime_resume_and_get(cs35l41->dev); -+ if (ret < 0) { -+ dev_err(cs35l41->dev, -+ "pm_runtime_resume_and_get failed in %s: %d\n", -+ __func__, ret); -+ return IRQ_NONE; -+ } -+ -+ ret = IRQ_NONE; - - for (i = 0; i < ARRAY_SIZE(status); i++) { - regmap_read(cs35l41->regmap, --- -2.41.0 -From: Cristian Ciocaltea @ 2023-09-07 17:10 UTC (permalink / raw) - To: James Schulman, David Rhodes, Richard Fitzgerald, - Jaroslav Kysela, Takashi Iwai, Liam Girdwood, Mark Brown, - Stefan Binding, Charles Keepax, Vitaly Rodionov - Cc: alsa-devel, patches, linux-kernel, kernel - -According to the documentation, drivers are responsible for undoing at -removal time all runtime PM changes done during probing. - -Hence, add the missing calls to pm_runtime_dont_use_autosuspend(), which -are necessary for undoing pm_runtime_use_autosuspend(). - -Note this would have been handled implicitly by -devm_pm_runtime_enable(), but there is a need to continue using -pm_runtime_enable()/pm_runtime_disable() in order to ensure the runtime -PM is disabled as soon as the remove() callback is entered. - -Fixes: f517ba4924ad ("ASoC: cs35l41: Add support for hibernate memory retention mode") -Signed-off-by: Cristian Ciocaltea ---- - sound/soc/codecs/cs35l41.c | 2 ++ - 1 file changed, 2 insertions(+) - -diff --git a/sound/soc/codecs/cs35l41.c b/sound/soc/codecs/cs35l41.c -index a31cb9ba7f7d..5456e6bfa242 100644 ---- a/sound/soc/codecs/cs35l41.c -+++ b/sound/soc/codecs/cs35l41.c -@@ -1334,6 +1334,7 @@ int cs35l41_probe(struct cs35l41_private *cs35l41, const struct cs35l41_hw_cfg * - return 0; - - err_pm: -+ pm_runtime_dont_use_autosuspend(cs35l41->dev); - pm_runtime_disable(cs35l41->dev); - pm_runtime_put_noidle(cs35l41->dev); - -@@ -1350,6 +1351,7 @@ EXPORT_SYMBOL_GPL(cs35l41_probe); - void cs35l41_remove(struct cs35l41_private *cs35l41) - { - pm_runtime_get_sync(cs35l41->dev); -+ pm_runtime_dont_use_autosuspend(cs35l41->dev); - pm_runtime_disable(cs35l41->dev); - - regmap_write(cs35l41->regmap, CS35L41_IRQ1_MASK1, 0xFFFFFFFF); --- -2.41.0 -From: Cristian Ciocaltea @ 2023-09-07 17:10 UTC (permalink / raw) - To: James Schulman, David Rhodes, Richard Fitzgerald, - Jaroslav Kysela, Takashi Iwai, Liam Girdwood, Mark Brown, - Stefan Binding, Charles Keepax, Vitaly Rodionov - Cc: alsa-devel, patches, linux-kernel, kernel - -Use dev_err_probe() helper where possible, to simplify error handling -during probe. - -Signed-off-by: Cristian Ciocaltea -Acked-by: Charles Keepax ---- - sound/soc/codecs/cs35l41-i2c.c | 9 +++------ - sound/soc/codecs/cs35l41-spi.c | 9 +++------ - sound/soc/codecs/cs35l41.c | 34 ++++++++++++++++------------------ - 3 files changed, 22 insertions(+), 30 deletions(-) - -diff --git a/sound/soc/codecs/cs35l41-i2c.c b/sound/soc/codecs/cs35l41-i2c.c -index 7ea890d7d387..9109203a7f25 100644 ---- a/sound/soc/codecs/cs35l41-i2c.c -+++ b/sound/soc/codecs/cs35l41-i2c.c -@@ -35,7 +35,6 @@ static int cs35l41_i2c_probe(struct i2c_client *client) - struct device *dev = &client->dev; - struct cs35l41_hw_cfg *hw_cfg = dev_get_platdata(dev); - const struct regmap_config *regmap_config = &cs35l41_regmap_i2c; -- int ret; - - cs35l41 = devm_kzalloc(dev, sizeof(struct cs35l41_private), GFP_KERNEL); - -@@ -47,11 +46,9 @@ static int cs35l41_i2c_probe(struct i2c_client *client) - - i2c_set_clientdata(client, cs35l41); - cs35l41->regmap = devm_regmap_init_i2c(client, regmap_config); -- if (IS_ERR(cs35l41->regmap)) { -- ret = PTR_ERR(cs35l41->regmap); -- dev_err(cs35l41->dev, "Failed to allocate register map: %d\n", ret); -- return ret; -- } -+ if (IS_ERR(cs35l41->regmap)) -+ return dev_err_probe(cs35l41->dev, PTR_ERR(cs35l41->regmap), -+ "Failed to allocate register map\n"); - - return cs35l41_probe(cs35l41, hw_cfg); - } -diff --git a/sound/soc/codecs/cs35l41-spi.c b/sound/soc/codecs/cs35l41-spi.c -index 5c8bb24909eb..28e9c9473e60 100644 ---- a/sound/soc/codecs/cs35l41-spi.c -+++ b/sound/soc/codecs/cs35l41-spi.c -@@ -32,7 +32,6 @@ static int cs35l41_spi_probe(struct spi_device *spi) - const struct regmap_config *regmap_config = &cs35l41_regmap_spi; - struct cs35l41_hw_cfg *hw_cfg = dev_get_platdata(&spi->dev); - struct cs35l41_private *cs35l41; -- int ret; - - cs35l41 = devm_kzalloc(&spi->dev, sizeof(struct cs35l41_private), GFP_KERNEL); - if (!cs35l41) -@@ -43,11 +42,9 @@ static int cs35l41_spi_probe(struct spi_device *spi) - - spi_set_drvdata(spi, cs35l41); - cs35l41->regmap = devm_regmap_init_spi(spi, regmap_config); -- if (IS_ERR(cs35l41->regmap)) { -- ret = PTR_ERR(cs35l41->regmap); -- dev_err(&spi->dev, "Failed to allocate register map: %d\n", ret); -- return ret; -- } -+ if (IS_ERR(cs35l41->regmap)) -+ return dev_err_probe(cs35l41->dev, PTR_ERR(cs35l41->regmap), -+ "Failed to allocate register map\n"); - - cs35l41->dev = &spi->dev; - cs35l41->irq = spi->irq; -diff --git a/sound/soc/codecs/cs35l41.c b/sound/soc/codecs/cs35l41.c -index 5456e6bfa242..7ddaa9bd8911 100644 ---- a/sound/soc/codecs/cs35l41.c -+++ b/sound/soc/codecs/cs35l41.c -@@ -1190,16 +1190,14 @@ int cs35l41_probe(struct cs35l41_private *cs35l41, const struct cs35l41_hw_cfg * - - ret = devm_regulator_bulk_get(cs35l41->dev, CS35L41_NUM_SUPPLIES, - cs35l41->supplies); -- if (ret != 0) { -- dev_err(cs35l41->dev, "Failed to request core supplies: %d\n", ret); -- return ret; -- } -+ if (ret != 0) -+ return dev_err_probe(cs35l41->dev, ret, -+ "Failed to request core supplies\n"); - - ret = regulator_bulk_enable(CS35L41_NUM_SUPPLIES, cs35l41->supplies); -- if (ret != 0) { -- dev_err(cs35l41->dev, "Failed to enable core supplies: %d\n", ret); -- return ret; -- } -+ if (ret != 0) -+ return dev_err_probe(cs35l41->dev, ret, -+ "Failed to enable core supplies\n"); - - /* returning NULL can be an option if in stereo mode */ - cs35l41->reset_gpio = devm_gpiod_get_optional(cs35l41->dev, "reset", -@@ -1211,8 +1209,8 @@ int cs35l41_probe(struct cs35l41_private *cs35l41, const struct cs35l41_hw_cfg * - dev_info(cs35l41->dev, - "Reset line busy, assuming shared reset\n"); - } else { -- dev_err(cs35l41->dev, -- "Failed to get reset GPIO: %d\n", ret); -+ dev_err_probe(cs35l41->dev, ret, -+ "Failed to get reset GPIO\n"); - goto err; - } - } -@@ -1228,8 +1226,8 @@ int cs35l41_probe(struct cs35l41_private *cs35l41, const struct cs35l41_hw_cfg * - int_status, int_status & CS35L41_OTP_BOOT_DONE, - 1000, 100000); - if (ret) { -- dev_err(cs35l41->dev, -- "Failed waiting for OTP_BOOT_DONE: %d\n", ret); -+ dev_err_probe(cs35l41->dev, ret, -+ "Failed waiting for OTP_BOOT_DONE\n"); - goto err; - } - -@@ -1242,13 +1240,13 @@ int cs35l41_probe(struct cs35l41_private *cs35l41, const struct cs35l41_hw_cfg * - - ret = regmap_read(cs35l41->regmap, CS35L41_DEVID, ®id); - if (ret < 0) { -- dev_err(cs35l41->dev, "Get Device ID failed: %d\n", ret); -+ dev_err_probe(cs35l41->dev, ret, "Get Device ID failed\n"); - goto err; - } - - ret = regmap_read(cs35l41->regmap, CS35L41_REVID, ®_revid); - if (ret < 0) { -- dev_err(cs35l41->dev, "Get Revision ID failed: %d\n", ret); -+ dev_err_probe(cs35l41->dev, ret, "Get Revision ID failed\n"); - goto err; - } - -@@ -1273,7 +1271,7 @@ int cs35l41_probe(struct cs35l41_private *cs35l41, const struct cs35l41_hw_cfg * - - ret = cs35l41_otp_unpack(cs35l41->dev, cs35l41->regmap); - if (ret < 0) { -- dev_err(cs35l41->dev, "OTP Unpack failed: %d\n", ret); -+ dev_err_probe(cs35l41->dev, ret, "OTP Unpack failed\n"); - goto err; - } - -@@ -1293,13 +1291,13 @@ int cs35l41_probe(struct cs35l41_private *cs35l41, const struct cs35l41_hw_cfg * - IRQF_ONESHOT | IRQF_SHARED | irq_pol, - "cs35l41", cs35l41); - if (ret != 0) { -- dev_err(cs35l41->dev, "Failed to request IRQ: %d\n", ret); -+ dev_err_probe(cs35l41->dev, ret, "Failed to request IRQ\n"); - goto err; - } - - ret = cs35l41_set_pdata(cs35l41); - if (ret < 0) { -- dev_err(cs35l41->dev, "Set pdata failed: %d\n", ret); -+ dev_err_probe(cs35l41->dev, ret, "Set pdata failed\n"); - goto err; - } - -@@ -1322,7 +1320,7 @@ int cs35l41_probe(struct cs35l41_private *cs35l41, const struct cs35l41_hw_cfg * - &soc_component_dev_cs35l41, - cs35l41_dai, ARRAY_SIZE(cs35l41_dai)); - if (ret < 0) { -- dev_err(cs35l41->dev, "Register codec failed: %d\n", ret); -+ dev_err_probe(cs35l41->dev, ret, "Register codec failed\n"); - goto err_pm; - } - --- -2.41.0 -From: Cristian Ciocaltea @ 2023-09-07 17:10 UTC (permalink / raw) - To: James Schulman, David Rhodes, Richard Fitzgerald, - Jaroslav Kysela, Takashi Iwai, Liam Girdwood, Mark Brown, - Stefan Binding, Charles Keepax, Vitaly Rodionov - Cc: alsa-devel, patches, linux-kernel, kernel - -Make use of the recently introduced EXPORT_GPL_DEV_PM_OPS() macro, to -conditionally export the runtime/system PM functions. - -Replace the old SET_{RUNTIME,SYSTEM_SLEEP,NOIRQ_SYSTEM_SLEEP}_PM_OPS() -helpers with their modern alternatives and get rid of the now -unnecessary '__maybe_unused' annotations on all PM functions. - -Additionally, use the pm_ptr() macro to fix the following errors when -building with CONFIG_PM disabled: - -ERROR: modpost: "cs35l41_pm_ops" [sound/soc/codecs/snd-soc-cs35l41-spi.ko] undefined! -ERROR: modpost: "cs35l41_pm_ops" [sound/soc/codecs/snd-soc-cs35l41-i2c.ko] undefined! - -Signed-off-by: Cristian Ciocaltea -Acked-by: Charles Keepax ---- - sound/soc/codecs/cs35l41-i2c.c | 2 +- - sound/soc/codecs/cs35l41-spi.c | 2 +- - sound/soc/codecs/cs35l41.c | 21 ++++++++++----------- - 3 files changed, 12 insertions(+), 13 deletions(-) - -diff --git a/sound/soc/codecs/cs35l41-i2c.c b/sound/soc/codecs/cs35l41-i2c.c -index 9109203a7f25..96414ee35285 100644 ---- a/sound/soc/codecs/cs35l41-i2c.c -+++ b/sound/soc/codecs/cs35l41-i2c.c -@@ -80,7 +80,7 @@ MODULE_DEVICE_TABLE(acpi, cs35l41_acpi_match); - static struct i2c_driver cs35l41_i2c_driver = { - .driver = { - .name = "cs35l41", -- .pm = &cs35l41_pm_ops, -+ .pm = pm_ptr(&cs35l41_pm_ops), - .of_match_table = of_match_ptr(cs35l41_of_match), - .acpi_match_table = ACPI_PTR(cs35l41_acpi_match), - }, -diff --git a/sound/soc/codecs/cs35l41-spi.c b/sound/soc/codecs/cs35l41-spi.c -index 28e9c9473e60..a6db44520c06 100644 ---- a/sound/soc/codecs/cs35l41-spi.c -+++ b/sound/soc/codecs/cs35l41-spi.c -@@ -80,7 +80,7 @@ MODULE_DEVICE_TABLE(acpi, cs35l41_acpi_match); - static struct spi_driver cs35l41_spi_driver = { - .driver = { - .name = "cs35l41", -- .pm = &cs35l41_pm_ops, -+ .pm = pm_ptr(&cs35l41_pm_ops), - .of_match_table = of_match_ptr(cs35l41_of_match), - .acpi_match_table = ACPI_PTR(cs35l41_acpi_match), - }, -diff --git a/sound/soc/codecs/cs35l41.c b/sound/soc/codecs/cs35l41.c -index 7ddaa9bd8911..4bc64ba71cd6 100644 ---- a/sound/soc/codecs/cs35l41.c -+++ b/sound/soc/codecs/cs35l41.c -@@ -1368,7 +1368,7 @@ void cs35l41_remove(struct cs35l41_private *cs35l41) - } - EXPORT_SYMBOL_GPL(cs35l41_remove); - --static int __maybe_unused cs35l41_runtime_suspend(struct device *dev) -+static int cs35l41_runtime_suspend(struct device *dev) - { - struct cs35l41_private *cs35l41 = dev_get_drvdata(dev); - -@@ -1385,7 +1385,7 @@ static int __maybe_unused cs35l41_runtime_suspend(struct device *dev) - return 0; - } - --static int __maybe_unused cs35l41_runtime_resume(struct device *dev) -+static int cs35l41_runtime_resume(struct device *dev) - { - struct cs35l41_private *cs35l41 = dev_get_drvdata(dev); - int ret; -@@ -1414,7 +1414,7 @@ static int __maybe_unused cs35l41_runtime_resume(struct device *dev) - return 0; - } - --static int __maybe_unused cs35l41_sys_suspend(struct device *dev) -+static int cs35l41_sys_suspend(struct device *dev) - { - struct cs35l41_private *cs35l41 = dev_get_drvdata(dev); - -@@ -1424,7 +1424,7 @@ static int __maybe_unused cs35l41_sys_suspend(struct device *dev) - return 0; - } - --static int __maybe_unused cs35l41_sys_suspend_noirq(struct device *dev) -+static int cs35l41_sys_suspend_noirq(struct device *dev) - { - struct cs35l41_private *cs35l41 = dev_get_drvdata(dev); - -@@ -1434,7 +1434,7 @@ static int __maybe_unused cs35l41_sys_suspend_noirq(struct device *dev) - return 0; - } - --static int __maybe_unused cs35l41_sys_resume_noirq(struct device *dev) -+static int cs35l41_sys_resume_noirq(struct device *dev) - { - struct cs35l41_private *cs35l41 = dev_get_drvdata(dev); - -@@ -1444,7 +1444,7 @@ static int __maybe_unused cs35l41_sys_resume_noirq(struct device *dev) - return 0; - } - --static int __maybe_unused cs35l41_sys_resume(struct device *dev) -+static int cs35l41_sys_resume(struct device *dev) - { - struct cs35l41_private *cs35l41 = dev_get_drvdata(dev); - -@@ -1454,13 +1454,12 @@ static int __maybe_unused cs35l41_sys_resume(struct device *dev) - return 0; - } - --const struct dev_pm_ops cs35l41_pm_ops = { -- SET_RUNTIME_PM_OPS(cs35l41_runtime_suspend, cs35l41_runtime_resume, NULL) -+EXPORT_GPL_DEV_PM_OPS(cs35l41_pm_ops) = { -+ RUNTIME_PM_OPS(cs35l41_runtime_suspend, cs35l41_runtime_resume, NULL) - -- SET_SYSTEM_SLEEP_PM_OPS(cs35l41_sys_suspend, cs35l41_sys_resume) -- SET_NOIRQ_SYSTEM_SLEEP_PM_OPS(cs35l41_sys_suspend_noirq, cs35l41_sys_resume_noirq) -+ SYSTEM_SLEEP_PM_OPS(cs35l41_sys_suspend, cs35l41_sys_resume) -+ NOIRQ_SYSTEM_SLEEP_PM_OPS(cs35l41_sys_suspend_noirq, cs35l41_sys_resume_noirq) - }; --EXPORT_SYMBOL_GPL(cs35l41_pm_ops); - - MODULE_DESCRIPTION("ASoC CS35L41 driver"); - MODULE_AUTHOR("David Rhodes, Cirrus Logic Inc, "); --- -2.41.0 -From: Cristian Ciocaltea @ 2023-09-07 17:10 UTC (permalink / raw) - To: James Schulman, David Rhodes, Richard Fitzgerald, - Jaroslav Kysela, Takashi Iwai, Liam Girdwood, Mark Brown, - Stefan Binding, Charles Keepax, Vitaly Rodionov - Cc: alsa-devel, patches, linux-kernel, kernel - -If component_add() fails, probe() returns without calling -pm_runtime_put(), which leaves the runtime PM usage counter incremented. - -Fix the issue by jumping to err_pm label and drop the now unnecessary -pm_runtime_disable() call. - -Fixes: 7b2f3eb492da ("ALSA: hda: cs35l41: Add support for CS35L41 in HDA systems") -Signed-off-by: Cristian Ciocaltea ---- - sound/pci/hda/cs35l41_hda.c | 3 +-- - 1 file changed, 1 insertion(+), 2 deletions(-) - -diff --git a/sound/pci/hda/cs35l41_hda.c b/sound/pci/hda/cs35l41_hda.c -index 09a9c135d9b6..6fd827093c92 100644 ---- a/sound/pci/hda/cs35l41_hda.c -+++ b/sound/pci/hda/cs35l41_hda.c -@@ -1625,8 +1625,7 @@ int cs35l41_hda_probe(struct device *dev, const char *device_name, int id, int i - ret = component_add(cs35l41->dev, &cs35l41_hda_comp_ops); - if (ret) { - dev_err(cs35l41->dev, "Register component failed: %d\n", ret); -- pm_runtime_disable(cs35l41->dev); -- goto err; -+ goto err_pm; - } - - dev_info(cs35l41->dev, "Cirrus Logic CS35L41 (%x), Revision: %02X\n", regid, reg_revid); --- -2.41.0 -From: Cristian Ciocaltea @ 2023-09-07 17:10 UTC (permalink / raw) - To: James Schulman, David Rhodes, Richard Fitzgerald, - Jaroslav Kysela, Takashi Iwai, Liam Girdwood, Mark Brown, - Stefan Binding, Charles Keepax, Vitaly Rodionov - Cc: alsa-devel, patches, linux-kernel, kernel - -According to the documentation, drivers are responsible for undoing at -removal time all runtime PM changes done during probing. - -Hence, add the missing calls to pm_runtime_dont_use_autosuspend(), which -are necessary for undoing pm_runtime_use_autosuspend(). - -Fixes: 1873ebd30cc8 ("ALSA: hda: cs35l41: Support Hibernation during Suspend") -Signed-off-by: Cristian Ciocaltea ---- - sound/pci/hda/cs35l41_hda.c | 2 ++ - 1 file changed, 2 insertions(+) - -diff --git a/sound/pci/hda/cs35l41_hda.c b/sound/pci/hda/cs35l41_hda.c -index 6fd827093c92..565f7b897436 100644 ---- a/sound/pci/hda/cs35l41_hda.c -+++ b/sound/pci/hda/cs35l41_hda.c -@@ -1633,6 +1633,7 @@ int cs35l41_hda_probe(struct device *dev, const char *device_name, int id, int i - return 0; - - err_pm: -+ pm_runtime_dont_use_autosuspend(cs35l41->dev); - pm_runtime_disable(cs35l41->dev); - pm_runtime_put_noidle(cs35l41->dev); - -@@ -1651,6 +1652,7 @@ void cs35l41_hda_remove(struct device *dev) - struct cs35l41_hda *cs35l41 = dev_get_drvdata(dev); - - pm_runtime_get_sync(cs35l41->dev); -+ pm_runtime_dont_use_autosuspend(cs35l41->dev); - pm_runtime_disable(cs35l41->dev); - - if (cs35l41->halo_initialized) --- -2.41.0 -From: Cristian Ciocaltea @ 2023-09-07 17:10 UTC (permalink / raw) - To: James Schulman, David Rhodes, Richard Fitzgerald, - Jaroslav Kysela, Takashi Iwai, Liam Girdwood, Mark Brown, - Stefan Binding, Charles Keepax, Vitaly Rodionov - Cc: alsa-devel, patches, linux-kernel, kernel - -Replace the remaining dev_err() calls in probe() with dev_err_probe(), -to improve consistency. - -Signed-off-by: Cristian Ciocaltea ---- - sound/pci/hda/cs35l41_hda.c | 14 +++++++------- - 1 file changed, 7 insertions(+), 7 deletions(-) - -diff --git a/sound/pci/hda/cs35l41_hda.c b/sound/pci/hda/cs35l41_hda.c -index 565f7b897436..c74faa2ff46c 100644 ---- a/sound/pci/hda/cs35l41_hda.c -+++ b/sound/pci/hda/cs35l41_hda.c -@@ -1550,27 +1550,27 @@ int cs35l41_hda_probe(struct device *dev, const char *device_name, int id, int i - ret = regmap_read_poll_timeout(cs35l41->regmap, CS35L41_IRQ1_STATUS4, int_status, - int_status & CS35L41_OTP_BOOT_DONE, 1000, 100000); - if (ret) { -- dev_err(cs35l41->dev, "Failed waiting for OTP_BOOT_DONE: %d\n", ret); -+ dev_err_probe(cs35l41->dev, ret, "Failed waiting for OTP_BOOT_DONE\n"); - goto err; - } - - ret = regmap_read(cs35l41->regmap, CS35L41_IRQ1_STATUS3, &int_sts); - if (ret || (int_sts & CS35L41_OTP_BOOT_ERR)) { -- dev_err(cs35l41->dev, "OTP Boot status %x error: %d\n", -- int_sts & CS35L41_OTP_BOOT_ERR, ret); -+ dev_err_probe(cs35l41->dev, ret, "OTP Boot status %x error\n", -+ int_sts & CS35L41_OTP_BOOT_ERR); - ret = -EIO; - goto err; - } - - ret = regmap_read(cs35l41->regmap, CS35L41_DEVID, ®id); - if (ret) { -- dev_err(cs35l41->dev, "Get Device ID failed: %d\n", ret); -+ dev_err_probe(cs35l41->dev, ret, "Get Device ID failed\n"); - goto err; - } - - ret = regmap_read(cs35l41->regmap, CS35L41_REVID, ®_revid); - if (ret) { -- dev_err(cs35l41->dev, "Get Revision ID failed: %d\n", ret); -+ dev_err_probe(cs35l41->dev, ret, "Get Revision ID failed\n"); - goto err; - } - -@@ -1593,7 +1593,7 @@ int cs35l41_hda_probe(struct device *dev, const char *device_name, int id, int i - - ret = cs35l41_otp_unpack(cs35l41->dev, cs35l41->regmap); - if (ret) { -- dev_err(cs35l41->dev, "OTP Unpack failed: %d\n", ret); -+ dev_err_probe(cs35l41->dev, ret, "OTP Unpack failed\n"); - goto err; - } - -@@ -1624,7 +1624,7 @@ int cs35l41_hda_probe(struct device *dev, const char *device_name, int id, int i - - ret = component_add(cs35l41->dev, &cs35l41_hda_comp_ops); - if (ret) { -- dev_err(cs35l41->dev, "Register component failed: %d\n", ret); -+ dev_err_probe(cs35l41->dev, ret, "Register component failed\n"); - goto err_pm; - } - --- -2.41.0 - -diff --git a/sound/pci/hda/cs35l41_hda_property.c b/sound/pci/hda/cs35l41_hda_property.c -index b39f944..b67c636 100644 ---- a/sound/pci/hda/cs35l41_hda_property.c -+++ b/sound/pci/hda/cs35l41_hda_property.c -@@ -6,7 +6,9 @@ - // - // Author: Stefan Binding - -+#include - #include -+#include - #include - #include "cs35l41_hda_property.h" - -@@ -78,6 +80,40 @@ static int asus_rog_2023_spkr_id2(struct cs35l41_hda *cs35l41, struct device *ph - return 0; - } - -+static int asus_rog_2023_ally_fix(struct cs35l41_hda *cs35l41, struct device *physdev, int id, -+ const char *hid) -+{ -+ const char *rog_ally_bios_ver = dmi_get_system_info(DMI_BIOS_VERSION); -+ const char *rog_ally_bios_num = rog_ally_bios_ver + 6; // Dropping the RC71L. part before the number -+ int rog_ally_bios_int; -+ kstrtoint(rog_ally_bios_num, 10, &rog_ally_bios_int); -+ if(rog_ally_bios_int >= 330){ -+ printk(KERN_INFO "DSD properties exist in the %d BIOS\n", rog_ally_bios_int); -+ return -ENOENT; //Patch not applicable. Exiting... -+ } -+ -+ struct cs35l41_hw_cfg *hw_cfg = &cs35l41->hw_cfg; -+ -+ dev_info(cs35l41->dev, "Adding DSD properties for %s\n", cs35l41->acpi_subsystem_id); -+ -+ cs35l41->index = id == 0x40 ? 0 : 1; -+ cs35l41->channel_index = 0; -+ cs35l41->reset_gpio = gpiod_get_index(physdev, NULL, 0, GPIOD_OUT_HIGH); -+ cs35l41->speaker_id = cs35l41_get_speaker_id(physdev, 0, 0, 2); -+ hw_cfg->spk_pos = cs35l41->index; -+ hw_cfg->gpio1.func = CS35L41_NOT_USED; -+ hw_cfg->gpio1.valid = true; -+ hw_cfg->gpio2.func = CS35L41_INTERRUPT; -+ hw_cfg->gpio2.valid = true; -+ hw_cfg->bst_type = CS35L41_INT_BOOST; -+ hw_cfg->bst_ind = 1000; /* 1,000nH Inductance value */ -+ hw_cfg->bst_ipk = 4500; /* 4,500mA peak current */ -+ hw_cfg->bst_cap = 24; /* 24 microFarad cap value */ -+ hw_cfg->valid = true; -+ -+ return 0; -+} -+ - struct cs35l41_prop_model { - const char *hid; - const char *ssid; -@@ -94,7 +130,7 @@ const struct cs35l41_prop_model cs35l41_prop_model_table[] = { - { "CSC3551", "10431483", asus_rog_2023_spkr_id2 }, // ASUS GU603V - spi, reset gpio 1 - { "CSC3551", "10431493", asus_rog_2023_spkr_id2 }, // ASUS GV601V - spi, reset gpio 1 - { "CSC3551", "10431573", asus_rog_2023_spkr_id2 }, // ASUS GZ301V - spi, reset gpio 0 -- { "CSC3551", "104317F3", asus_rog_2023_spkr_id2 }, // ASUS ROG ALLY - i2c -+ { "CSC3551", "104317F3", asus_rog_2023_ally_fix }, // ASUS ROG ALLY - i2c - { "CSC3551", "10431B93", asus_rog_2023_spkr_id2 }, // ASUS G614J - spi, reset gpio 0 - { "CSC3551", "10431CAF", asus_rog_2023_spkr_id2 }, // ASUS G634J - spi, reset gpio 0 - { "CSC3551", "10431C9F", asus_rog_2023_spkr_id2 }, // ASUS G614JI -spi, reset gpio 0 - --- -2.41.0 - diff --git a/patches/nobara/rog-ally-side-keys-fix.patch b/patches/nobara/rog-ally-side-keys-fix.patch deleted file mode 100644 index 4559566..0000000 --- a/patches/nobara/rog-ally-side-keys-fix.patch +++ /dev/null @@ -1,51 +0,0 @@ -From df561bb39dc0ae974bbd1d0f88b8a75cc8c20b7c Mon Sep 17 00:00:00 2001 -From: GloriousEggroll -Date: Wed, 30 Aug 2023 20:25:07 -0600 -Subject: [PATCH 09/18] rog-ally-side-keys - ---- - drivers/hid/hid-asus.c | 8 ++++++++ - drivers/hid/hid-ids.h | 1 + - 2 files changed, 9 insertions(+) - -diff --git a/drivers/hid/hid-asus.c b/drivers/hid/hid-asus.c -index fd61dba88..596196c04 100644 ---- a/drivers/hid/hid-asus.c -+++ b/drivers/hid/hid-asus.c -@@ -899,6 +899,11 @@ static int asus_input_mapping(struct hid_device *hdev, - case 0x4b: asus_map_key_clear(KEY_F14); break; /* Arrows/Pg-Up/Dn toggle */ - - -+ case 0xa5: asus_map_key_clear(KEY_F15); break; /* ROG Ally left back */ -+ case 0xa6: asus_map_key_clear(KEY_F16); break; /* ROG Ally QAM button */ -+ case 0xa7: asus_map_key_clear(KEY_F17); break; /* ROG Ally ROG long-press */ -+ case 0xa8: asus_map_key_clear(KEY_F18); break; /* ROG Ally ROG longer-press */ -+ - default: - /* ASUS lazily declares 256 usages, ignore the rest, - * as some make the keyboard appear as a pointer device. */ -@@ -1252,6 +1257,9 @@ static const struct hid_device_id asus_devices[] = { - { HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK, - USB_DEVICE_ID_ASUSTEK_ROG_NKEY_KEYBOARD), - QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD }, -+ { HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK, -+ USB_DEVICE_ID_ASUSTEK_ROG_NKEY_ALLY), -+ QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD }, - { HID_USB_DEVICE(USB_VENDOR_ID_ASUSTEK, - USB_DEVICE_ID_ASUSTEK_ROG_NKEY_KEYBOARD2), - QUIRK_USE_KBD_BACKLIGHT | QUIRK_ROG_NKEY_KEYBOARD }, -diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h -index 8a310f8ff..46f5262d7 100644 ---- a/drivers/hid/hid-ids.h -+++ b/drivers/hid/hid-ids.h -@@ -208,6 +208,7 @@ - #define USB_DEVICE_ID_ASUSTEK_ROG_NKEY_KEYBOARD 0x1866 - #define USB_DEVICE_ID_ASUSTEK_ROG_NKEY_KEYBOARD2 0x19b6 - #define USB_DEVICE_ID_ASUSTEK_ROG_NKEY_KEYBOARD3 0x1a30 -+#define USB_DEVICE_ID_ASUSTEK_ROG_NKEY_ALLY 0x1abe - #define USB_DEVICE_ID_ASUSTEK_ROG_CLAYMORE_II_KEYBOARD 0x196b - #define USB_DEVICE_ID_ASUSTEK_FX503VD_KEYBOARD 0x1869 - --- -2.41.0 - diff --git a/patches/nobara/steam-deck.patch b/patches/nobara/steam-deck.patch new file mode 100644 index 0000000..9eba750 --- /dev/null +++ b/patches/nobara/steam-deck.patch @@ -0,0 +1,2497 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Andrey Smirnov +Date: Sat, 19 Feb 2022 16:08:36 -0800 +Subject: [PATCH] mfd: Add MFD core driver for Steam Deck + +Add MFD core driver for Steam Deck. Doesn't really do much so far +besides instantiating a number of MFD cells that implement all the +interesting functionality. + +(cherry picked from commit 5f534c2d6ebdefccb9c024eb0f013bc1c0c622d9) +Signed-off-by: Cristian Ciocaltea +Signed-off-by: Jan200101 +--- + drivers/mfd/Kconfig | 11 ++++ + drivers/mfd/Makefile | 2 + + drivers/mfd/steamdeck.c | 127 ++++++++++++++++++++++++++++++++++++++++ + 3 files changed, 140 insertions(+) + create mode 100644 drivers/mfd/steamdeck.c + +diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig +index 8b93856de432..af335d9150e9 100644 +--- a/drivers/mfd/Kconfig ++++ b/drivers/mfd/Kconfig +@@ -2260,5 +2260,16 @@ config MFD_RSMU_SPI + Additional drivers must be enabled in order to use the functionality + of the device. + ++config MFD_STEAMDECK ++ tristate "Valve Steam Deck" ++ select MFD_CORE ++ depends on ACPI ++ depends on X86_64 || COMPILE_TEST ++ help ++ This driver registers various MFD cells that expose aspects ++ of Steam Deck specific ACPI functionality. ++ ++ Say N here, unless you are running on Steam Deck hardware. ++ + endmenu + endif +diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile +index 7ed3ef4a698c..d01254ef0106 100644 +--- a/drivers/mfd/Makefile ++++ b/drivers/mfd/Makefile +@@ -280,3 +280,5 @@ rsmu-i2c-objs := rsmu_core.o rsmu_i2c.o + rsmu-spi-objs := rsmu_core.o rsmu_spi.o + obj-$(CONFIG_MFD_RSMU_I2C) += rsmu-i2c.o + obj-$(CONFIG_MFD_RSMU_SPI) += rsmu-spi.o ++ ++obj-$(CONFIG_MFD_STEAMDECK) += steamdeck.o +diff --git a/drivers/mfd/steamdeck.c b/drivers/mfd/steamdeck.c +new file mode 100644 +index 000000000000..0e504b3c2796 +--- /dev/null ++++ b/drivers/mfd/steamdeck.c +@@ -0,0 +1,127 @@ ++// SPDX-License-Identifier: GPL-2.0+ ++ ++/* ++ * Steam Deck EC MFD core driver ++ * ++ * Copyright (C) 2021-2022 Valve Corporation ++ * ++ */ ++ ++#include ++#include ++#include ++ ++#define STEAMDECK_STA_OK \ ++ (ACPI_STA_DEVICE_ENABLED | \ ++ ACPI_STA_DEVICE_PRESENT | \ ++ ACPI_STA_DEVICE_FUNCTIONING) ++ ++struct steamdeck { ++ struct acpi_device *adev; ++ struct device *dev; ++}; ++ ++#define STEAMDECK_ATTR_RO(_name, _method) \ ++ static ssize_t _name##_show(struct device *dev, \ ++ struct device_attribute *attr, \ ++ char *buf) \ ++ { \ ++ struct steamdeck *sd = dev_get_drvdata(dev); \ ++ unsigned long long val; \ ++ \ ++ if (ACPI_FAILURE(acpi_evaluate_integer( \ ++ sd->adev->handle, \ ++ _method, NULL, &val))) \ ++ return -EIO; \ ++ \ ++ return sysfs_emit(buf, "%llu\n", val); \ ++ } \ ++ static DEVICE_ATTR_RO(_name) ++ ++STEAMDECK_ATTR_RO(firmware_version, "PDFW"); ++STEAMDECK_ATTR_RO(board_id, "BOID"); ++ ++static struct attribute *steamdeck_attrs[] = { ++ &dev_attr_firmware_version.attr, ++ &dev_attr_board_id.attr, ++ NULL ++}; ++ ++ATTRIBUTE_GROUPS(steamdeck); ++ ++static const struct mfd_cell steamdeck_cells[] = { ++ { .name = "steamdeck-hwmon" }, ++ { .name = "steamdeck-leds" }, ++ { .name = "steamdeck-extcon" }, ++}; ++ ++static void steamdeck_remove_sysfs_groups(void *data) ++{ ++ struct steamdeck *sd = data; ++ ++ sysfs_remove_groups(&sd->dev->kobj, steamdeck_groups); ++} ++ ++static int steamdeck_probe(struct platform_device *pdev) ++{ ++ struct device *dev = &pdev->dev; ++ unsigned long long sta; ++ struct steamdeck *sd; ++ acpi_status status; ++ int ret; ++ ++ sd = devm_kzalloc(dev, sizeof(*sd), GFP_KERNEL); ++ if (!sd) ++ return -ENOMEM; ++ sd->adev = ACPI_COMPANION(dev); ++ sd->dev = dev; ++ platform_set_drvdata(pdev, sd); ++ ++ status = acpi_evaluate_integer(sd->adev->handle, "_STA", ++ NULL, &sta); ++ if (ACPI_FAILURE(status)) { ++ dev_err(dev, "Status check failed (0x%x)\n", status); ++ return -EINVAL; ++ } ++ ++ if ((sta & STEAMDECK_STA_OK) != STEAMDECK_STA_OK) { ++ dev_err(dev, "Device is not ready\n"); ++ return -EINVAL; ++ } ++ ++ ret = sysfs_create_groups(&dev->kobj, steamdeck_groups); ++ if (ret) { ++ dev_err(dev, "Failed to create sysfs group\n"); ++ return ret; ++ } ++ ++ ret = devm_add_action_or_reset(dev, steamdeck_remove_sysfs_groups, ++ sd); ++ if (ret) { ++ dev_err(dev, "Failed to register devres action\n"); ++ return ret; ++ } ++ ++ return devm_mfd_add_devices(dev, PLATFORM_DEVID_NONE, ++ steamdeck_cells, ARRAY_SIZE(steamdeck_cells), ++ NULL, 0, NULL); ++} ++ ++static const struct acpi_device_id steamdeck_device_ids[] = { ++ { "VLV0100", 0 }, ++ { "", 0 }, ++}; ++MODULE_DEVICE_TABLE(acpi, steamdeck_device_ids); ++ ++static struct platform_driver steamdeck_driver = { ++ .probe = steamdeck_probe, ++ .driver = { ++ .name = "steamdeck", ++ .acpi_match_table = steamdeck_device_ids, ++ }, ++}; ++module_platform_driver(steamdeck_driver); ++ ++MODULE_AUTHOR("Andrey Smirnov "); ++MODULE_DESCRIPTION("Steam Deck EC MFD core driver"); ++MODULE_LICENSE("GPL"); + +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Andrey Smirnov +Date: Sat, 19 Feb 2022 16:09:45 -0800 +Subject: [PATCH] hwmon: Add driver for Steam Deck's EC sensors + +Add driver for sensors exposed by EC firmware on Steam Deck hardware. + +(cherry picked from commit 6917aac77bee6185ae3920b936cdbe7876118c0b) +Signed-off-by: Cristian Ciocaltea +Signed-off-by: Jan200101 +--- + drivers/hwmon/Kconfig | 11 ++ + drivers/hwmon/Makefile | 1 + + drivers/hwmon/steamdeck-hwmon.c | 224 ++++++++++++++++++++++++++++++++ + 3 files changed, 236 insertions(+) + create mode 100644 drivers/hwmon/steamdeck-hwmon.c + +diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig +index 7ac3daaf59ce..d784c78417cf 100644 +--- a/drivers/hwmon/Kconfig ++++ b/drivers/hwmon/Kconfig +@@ -1900,6 +1900,17 @@ config SENSORS_SCH5636 + This driver can also be built as a module. If so, the module + will be called sch5636. + ++config SENSORS_STEAMDECK ++ tristate "Steam Deck EC sensors" ++ depends on MFD_STEAMDECK ++ help ++ If you say yes here you get support for the hardware ++ monitoring features exposed by EC firmware on Steam Deck ++ devices ++ ++ This driver can also be built as a module. If so, the module ++ will be called steamdeck-hwmon. ++ + config SENSORS_STTS751 + tristate "ST Microelectronics STTS751" + depends on I2C +diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile +index 11d076cad8a2..d03c1e1d339f 100644 +--- a/drivers/hwmon/Makefile ++++ b/drivers/hwmon/Makefile +@@ -191,6 +191,7 @@ obj-$(CONFIG_SENSORS_SMSC47B397)+= smsc47b397.o + obj-$(CONFIG_SENSORS_SMSC47M1) += smsc47m1.o + obj-$(CONFIG_SENSORS_SMSC47M192)+= smsc47m192.o + obj-$(CONFIG_SENSORS_SPARX5) += sparx5-temp.o ++obj-$(CONFIG_SENSORS_STEAMDECK) += steamdeck-hwmon.o + obj-$(CONFIG_SENSORS_STTS751) += stts751.o + obj-$(CONFIG_SENSORS_SY7636A) += sy7636a-hwmon.o + obj-$(CONFIG_SENSORS_AMC6821) += amc6821.o +diff --git a/drivers/hwmon/steamdeck-hwmon.c b/drivers/hwmon/steamdeck-hwmon.c +new file mode 100644 +index 000000000000..fab9e9460bd4 +--- /dev/null ++++ b/drivers/hwmon/steamdeck-hwmon.c +@@ -0,0 +1,224 @@ ++// SPDX-License-Identifier: GPL-2.0+ ++/* ++ * Steam Deck EC sensors driver ++ * ++ * Copyright (C) 2021-2022 Valve Corporation ++ */ ++ ++#include ++#include ++#include ++ ++#define STEAMDECK_HWMON_NAME "steamdeck-hwmon" ++ ++struct steamdeck_hwmon { ++ struct acpi_device *adev; ++}; ++ ++static long ++steamdeck_hwmon_get(struct steamdeck_hwmon *sd, const char *method) ++{ ++ unsigned long long val; ++ if (ACPI_FAILURE(acpi_evaluate_integer(sd->adev->handle, ++ (char *)method, NULL, &val))) ++ return -EIO; ++ ++ return val; ++} ++ ++static int ++steamdeck_hwmon_read(struct device *dev, enum hwmon_sensor_types type, ++ u32 attr, int channel, long *out) ++{ ++ struct steamdeck_hwmon *sd = dev_get_drvdata(dev); ++ ++ switch (type) { ++ case hwmon_curr: ++ if (attr != hwmon_curr_input) ++ return -EOPNOTSUPP; ++ ++ *out = steamdeck_hwmon_get(sd, "PDAM"); ++ if (*out < 0) ++ return *out; ++ break; ++ case hwmon_in: ++ if (attr != hwmon_in_input) ++ return -EOPNOTSUPP; ++ ++ *out = steamdeck_hwmon_get(sd, "PDVL"); ++ if (*out < 0) ++ return *out; ++ break; ++ case hwmon_temp: ++ if (attr != hwmon_temp_input) ++ return -EOPNOTSUPP; ++ ++ *out = steamdeck_hwmon_get(sd, "BATT"); ++ if (*out < 0) ++ return *out; ++ /* ++ * Assuming BATT returns deg C we need to mutiply it ++ * by 1000 to convert to mC ++ */ ++ *out *= 1000; ++ break; ++ case hwmon_fan: ++ switch (attr) { ++ case hwmon_fan_input: ++ *out = steamdeck_hwmon_get(sd, "FANR"); ++ if (*out < 0) ++ return *out; ++ break; ++ case hwmon_fan_target: ++ *out = steamdeck_hwmon_get(sd, "FSSR"); ++ if (*out < 0) ++ return *out; ++ break; ++ case hwmon_fan_fault: ++ *out = steamdeck_hwmon_get(sd, "FANC"); ++ if (*out < 0) ++ return *out; ++ /* ++ * FANC (Fan check): ++ * 0: Abnormal ++ * 1: Normal ++ */ ++ *out = !*out; ++ break; ++ default: ++ return -EOPNOTSUPP; ++ } ++ break; ++ default: ++ return -EOPNOTSUPP; ++ } ++ ++ return 0; ++} ++ ++static int ++steamdeck_hwmon_read_string(struct device *dev, enum hwmon_sensor_types type, ++ u32 attr, int channel, const char **str) ++{ ++ switch (type) { ++ /* ++ * These two aren't, strictly speaking, measured. EC ++ * firmware just reports what PD negotiation resulted ++ * in. ++ */ ++ case hwmon_curr: ++ *str = "PD Contract Current"; ++ break; ++ case hwmon_in: ++ *str = "PD Contract Voltage"; ++ break; ++ case hwmon_temp: ++ *str = "Battery Temp"; ++ break; ++ case hwmon_fan: ++ *str = "System Fan"; ++ break; ++ default: ++ return -EOPNOTSUPP; ++ } ++ ++ return 0; ++} ++ ++static int ++steamdeck_hwmon_write(struct device *dev, enum hwmon_sensor_types type, ++ u32 attr, int channel, long val) ++{ ++ struct steamdeck_hwmon *sd = dev_get_drvdata(dev); ++ ++ if (type != hwmon_fan || ++ attr != hwmon_fan_target) ++ return -EOPNOTSUPP; ++ ++ val = clamp_val(val, 0, 7300); ++ ++ if (ACPI_FAILURE(acpi_execute_simple_method(sd->adev->handle, ++ "FANS", val))) ++ return -EIO; ++ ++ return 0; ++} ++ ++static umode_t ++steamdeck_hwmon_is_visible(const void *data, enum hwmon_sensor_types type, ++ u32 attr, int channel) ++{ ++ if (type == hwmon_fan && ++ attr == hwmon_fan_target) ++ return 0644; ++ ++ return 0444; ++} ++ ++static const struct hwmon_channel_info *steamdeck_hwmon_info[] = { ++ HWMON_CHANNEL_INFO(in, ++ HWMON_I_INPUT | HWMON_I_LABEL), ++ HWMON_CHANNEL_INFO(curr, ++ HWMON_C_INPUT | HWMON_C_LABEL), ++ HWMON_CHANNEL_INFO(temp, ++ HWMON_T_INPUT | HWMON_T_LABEL), ++ HWMON_CHANNEL_INFO(fan, ++ HWMON_F_INPUT | HWMON_F_LABEL | ++ HWMON_F_TARGET | HWMON_F_FAULT), ++ NULL ++}; ++ ++static const struct hwmon_ops steamdeck_hwmon_ops = { ++ .is_visible = steamdeck_hwmon_is_visible, ++ .read = steamdeck_hwmon_read, ++ .read_string = steamdeck_hwmon_read_string, ++ .write = steamdeck_hwmon_write, ++}; ++ ++static const struct hwmon_chip_info steamdeck_hwmon_chip_info = { ++ .ops = &steamdeck_hwmon_ops, ++ .info = steamdeck_hwmon_info, ++}; ++ ++static int steamdeck_hwmon_probe(struct platform_device *pdev) ++{ ++ struct device *dev = &pdev->dev; ++ struct steamdeck_hwmon *sd; ++ struct device *hwmon; ++ ++ sd = devm_kzalloc(dev, sizeof(*sd), GFP_KERNEL); ++ if (!sd) ++ return -ENOMEM; ++ ++ sd->adev = ACPI_COMPANION(dev->parent); ++ hwmon = devm_hwmon_device_register_with_info(dev, ++ "steamdeck_hwmon", ++ sd, ++ &steamdeck_hwmon_chip_info, ++ NULL); ++ if (IS_ERR(hwmon)) { ++ dev_err(dev, "Failed to register HWMON device"); ++ return PTR_ERR(hwmon); ++ } ++ ++ return 0; ++} ++ ++static const struct platform_device_id steamdeck_hwmon_id_table[] = { ++ { .name = STEAMDECK_HWMON_NAME }, ++ {} ++}; ++MODULE_DEVICE_TABLE(platform, steamdeck_hwmon_id_table); ++ ++static struct platform_driver steamdeck_hwmon_driver = { ++ .probe = steamdeck_hwmon_probe, ++ .driver = { ++ .name = STEAMDECK_HWMON_NAME, ++ }, ++ .id_table = steamdeck_hwmon_id_table, ++}; ++module_platform_driver(steamdeck_hwmon_driver); ++ ++MODULE_AUTHOR("Andrey Smirnov "); ++MODULE_DESCRIPTION("Steam Deck EC sensors driver"); ++MODULE_LICENSE("GPL"); + +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Andrey Smirnov +Date: Sun, 27 Feb 2022 12:58:05 -0800 +Subject: [PATCH] leds: steamdeck: Add support for Steam Deck LED + +(cherry picked from commit 85a86d19aa7022ff0555023d53aef78323a42d0c) +Signed-off-by: Cristian Ciocaltea +Signed-off-by: Jan200101 +--- + drivers/leds/Kconfig | 7 ++++ + drivers/leds/Makefile | 1 + + drivers/leds/leds-steamdeck.c | 74 +++++++++++++++++++++++++++++++++++ + 3 files changed, 82 insertions(+) + create mode 100644 drivers/leds/leds-steamdeck.c + +diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig +index 499d0f215a8b..d1d761695cd6 100644 +--- a/drivers/leds/Kconfig ++++ b/drivers/leds/Kconfig +@@ -864,6 +864,13 @@ config LEDS_ACER_A500 + This option enables support for the Power Button LED of + Acer Iconia Tab A500. + ++config LEDS_STEAMDECK ++ tristate "LED support for Steam Deck" ++ depends on LEDS_CLASS && MFD_STEAMDECK ++ help ++ This option enabled support for the status LED (next to the ++ power button) on Steam Deck ++ + source "drivers/leds/blink/Kconfig" + + comment "Flash and Torch LED drivers" +diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile +index 4fd2f92cd198..130a1c175dde 100644 +--- a/drivers/leds/Makefile ++++ b/drivers/leds/Makefile +@@ -75,6 +75,7 @@ + obj-$(CONFIG_LEDS_PWM) += leds-pwm.o + obj-$(CONFIG_LEDS_REGULATOR) += leds-regulator.o + obj-$(CONFIG_LEDS_SC27XX_BLTC) += leds-sc27xx-bltc.o ++obj-$(CONFIG_LEDS_STEAMDECK) += leds-steamdeck.o + obj-$(CONFIG_LEDS_SUNFIRE) += leds-sunfire.o + obj-$(CONFIG_LEDS_SYSCON) += leds-syscon.o + obj-$(CONFIG_LEDS_TCA6507) += leds-tca6507.o +diff --git a/drivers/leds/leds-steamdeck.c b/drivers/leds/leds-steamdeck.c +new file mode 100644 +index 000000000000..686500b8de73 +--- /dev/null ++++ b/drivers/leds/leds-steamdeck.c +@@ -0,0 +1,74 @@ ++// SPDX-License-Identifier: GPL-2.0+ ++ ++/* ++ * Steam Deck EC MFD LED cell driver ++ * ++ * Copyright (C) 2021-2022 Valve Corporation ++ * ++ */ ++ ++#include ++#include ++#include ++ ++struct steamdeck_led { ++ struct acpi_device *adev; ++ struct led_classdev cdev; ++}; ++ ++static int steamdeck_leds_brightness_set(struct led_classdev *cdev, ++ enum led_brightness value) ++{ ++ struct steamdeck_led *sd = container_of(cdev, struct steamdeck_led, ++ cdev); ++ ++ if (ACPI_FAILURE(acpi_execute_simple_method(sd->adev->handle, ++ "CHBV", value))) ++ return -EIO; ++ ++ return 0; ++} ++ ++static int steamdeck_leds_probe(struct platform_device *pdev) ++{ ++ struct device *dev = &pdev->dev; ++ struct steamdeck_led *sd; ++ int ret; ++ ++ sd = devm_kzalloc(dev, sizeof(*sd), GFP_KERNEL); ++ if (!sd) ++ return -ENOMEM; ++ ++ sd->adev = ACPI_COMPANION(dev->parent); ++ ++ sd->cdev.name = "status:white"; ++ sd->cdev.brightness_set_blocking = steamdeck_leds_brightness_set; ++ sd->cdev.max_brightness = 100; ++ ++ ret = devm_led_classdev_register(dev, &sd->cdev); ++ if (ret) { ++ dev_err(dev, "Failed to register LEDs device: %d\n", ret); ++ return ret; ++ } ++ ++ return 0; ++} ++ ++static const struct platform_device_id steamdeck_leds_id_table[] = { ++ { .name = "steamdeck-leds" }, ++ {} ++}; ++MODULE_DEVICE_TABLE(platform, steamdeck_leds_id_table); ++ ++static struct platform_driver steamdeck_leds_driver = { ++ .probe = steamdeck_leds_probe, ++ .driver = { ++ .name = "steamdeck-leds", ++ }, ++ .id_table = steamdeck_leds_id_table, ++}; ++module_platform_driver(steamdeck_leds_driver); ++ ++MODULE_AUTHOR("Andrey Smirnov "); ++MODULE_DESCRIPTION("Steam Deck LEDs driver"); ++MODULE_LICENSE("GPL"); + +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Andrey Smirnov +Date: Sun, 27 Feb 2022 14:46:08 -0800 +Subject: [PATCH] extcon: Add driver for Steam Deck + +(cherry picked from commit f9f2eddae582ae39d5f89c1218448fc259b90aa8) +Signed-off-by: Cristian Ciocaltea +Signed-off-by: Jan200101 +--- + drivers/extcon/Kconfig | 7 ++ + drivers/extcon/Makefile | 1 + + drivers/extcon/extcon-steamdeck.c | 180 ++++++++++++++++++++++++++++++ + 3 files changed, 188 insertions(+) + create mode 100644 drivers/extcon/extcon-steamdeck.c + +diff --git a/drivers/extcon/Kconfig b/drivers/extcon/Kconfig +index 290186e44e6b..4d444a9e2c1f 100644 +--- a/drivers/extcon/Kconfig ++++ b/drivers/extcon/Kconfig +@@ -189,4 +189,11 @@ config EXTCON_USBC_TUSB320 + Say Y here to enable support for USB Type C cable detection extcon + support using a TUSB320. + ++config EXTCON_STEAMDECK ++ tristate "Steam Deck extcon support" ++ depends on MFD_STEAMDECK ++ help ++ Say Y here to enable support of USB Type C cable detection extcon ++ support on Steam Deck devices ++ + endif +diff --git a/drivers/extcon/Makefile b/drivers/extcon/Makefile +index 1b390d934ca9..1c7e217f29e4 100644 +--- a/drivers/extcon/Makefile ++++ b/drivers/extcon/Makefile +@@ -25,3 +25,4 @@ obj-$(CONFIG_EXTCON_SM5502) += extcon-sm5502.o + obj-$(CONFIG_EXTCON_USB_GPIO) += extcon-usb-gpio.o + obj-$(CONFIG_EXTCON_USBC_CROS_EC) += extcon-usbc-cros-ec.o + obj-$(CONFIG_EXTCON_USBC_TUSB320) += extcon-usbc-tusb320.o ++obj-$(CONFIG_EXTCON_STEAMDECK) += extcon-steamdeck.o +diff --git a/drivers/extcon/extcon-steamdeck.c b/drivers/extcon/extcon-steamdeck.c +new file mode 100644 +index 000000000000..74f190adc8ea +--- /dev/null ++++ b/drivers/extcon/extcon-steamdeck.c +@@ -0,0 +1,180 @@ ++ ++#include ++#include ++#include ++ ++#define ACPI_STEAMDECK_NOTIFY_STATUS 0x80 ++ ++/* 0 - port connected, 1 -port disconnected */ ++#define ACPI_STEAMDECK_PORT_CONNECT BIT(0) ++/* 0 - Upstream Facing Port, 1 - Downdstream Facing Port */ ++#define ACPI_STEAMDECK_CUR_DATA_ROLE BIT(3) ++/* ++ * Debouncing delay to allow negotiation process to settle. 2s value ++ * was arrived at via trial and error. ++ */ ++#define STEAMDECK_ROLE_SWITCH_DELAY (msecs_to_jiffies(2000)) ++ ++struct steamdeck_extcon { ++ struct acpi_device *adev; ++ struct delayed_work role_work; ++ struct extcon_dev *edev; ++ struct device *dev; ++}; ++ ++static int steamdeck_read_pdcs(struct steamdeck_extcon *sd, unsigned long long *pdcs) ++{ ++ acpi_status status; ++ ++ status = acpi_evaluate_integer(sd->adev->handle, "PDCS", NULL, pdcs); ++ if (ACPI_FAILURE(status)) { ++ dev_err(sd->dev, "PDCS evaluation failed: %s\n", ++ acpi_format_exception(status)); ++ return -EIO; ++ } ++ ++ return 0; ++} ++ ++static void steamdeck_usb_role_work(struct work_struct *work) ++{ ++ struct steamdeck_extcon *sd = ++ container_of(work, struct steamdeck_extcon, role_work.work); ++ unsigned long long pdcs; ++ bool usb_host; ++ ++ if (steamdeck_read_pdcs(sd, &pdcs)) ++ return; ++ ++ /* ++ * We only care about these two ++ */ ++ pdcs &= ACPI_STEAMDECK_PORT_CONNECT | ACPI_STEAMDECK_CUR_DATA_ROLE; ++ ++ /* ++ * For "connect" events our role is determined by a bit in ++ * PDCS, for "disconnect" we switch to being a gadget ++ * unconditionally. The thinking for the latter is we don't ++ * want to start acting as a USB host until we get ++ * confirmation from the firmware that we are a USB host ++ */ ++ usb_host = (pdcs & ACPI_STEAMDECK_PORT_CONNECT) ? ++ pdcs & ACPI_STEAMDECK_CUR_DATA_ROLE : false; ++ ++ dev_dbg(sd->dev, "USB role is %s\n", usb_host ? "host" : "device"); ++ WARN_ON(extcon_set_state_sync(sd->edev, EXTCON_USB_HOST, ++ usb_host)); ++ ++} ++ ++static void steamdeck_notify(acpi_handle handle, u32 event, void *context) ++{ ++ struct device *dev = context; ++ struct steamdeck_extcon *sd = dev_get_drvdata(dev); ++ unsigned long long pdcs; ++ unsigned long delay; ++ ++ switch (event) { ++ case ACPI_STEAMDECK_NOTIFY_STATUS: ++ if (steamdeck_read_pdcs(sd, &pdcs)) ++ return; ++ /* ++ * We process "disconnect" events immediately and ++ * "connect" events with a delay to give the HW time ++ * to settle. For example attaching USB hub (at least ++ * for HW used for testing) will generate intermediary ++ * event with "host" bit not set, followed by the one ++ * that does have it set. ++ */ ++ delay = (pdcs & ACPI_STEAMDECK_PORT_CONNECT) ? ++ STEAMDECK_ROLE_SWITCH_DELAY : 0; ++ ++ queue_delayed_work(system_long_wq, &sd->role_work, delay); ++ break; ++ default: ++ dev_warn(dev, "Unsupported event [0x%x]\n", event); ++ } ++} ++ ++static void steamdeck_remove_notify_handler(void *data) ++{ ++ struct steamdeck_extcon *sd = data; ++ ++ acpi_remove_notify_handler(sd->adev->handle, ACPI_DEVICE_NOTIFY, ++ steamdeck_notify); ++ cancel_delayed_work_sync(&sd->role_work); ++} ++ ++static const unsigned int steamdeck_extcon_cable[] = { ++ EXTCON_USB, ++ EXTCON_USB_HOST, ++ EXTCON_CHG_USB_SDP, ++ EXTCON_CHG_USB_CDP, ++ EXTCON_CHG_USB_DCP, ++ EXTCON_CHG_USB_ACA, ++ EXTCON_NONE, ++}; ++ ++static int steamdeck_extcon_probe(struct platform_device *pdev) ++{ ++ struct device *dev = &pdev->dev; ++ struct steamdeck_extcon *sd; ++ acpi_status status; ++ int ret; ++ ++ sd = devm_kzalloc(dev, sizeof(*sd), GFP_KERNEL); ++ if (!sd) ++ return -ENOMEM; ++ ++ INIT_DELAYED_WORK(&sd->role_work, steamdeck_usb_role_work); ++ platform_set_drvdata(pdev, sd); ++ sd->adev = ACPI_COMPANION(dev->parent); ++ sd->dev = dev; ++ sd->edev = devm_extcon_dev_allocate(dev, steamdeck_extcon_cable); ++ if (IS_ERR(sd->edev)) ++ return PTR_ERR(sd->edev); ++ ++ ret = devm_extcon_dev_register(dev, sd->edev); ++ if (ret < 0) { ++ dev_err(dev, "Failed to register extcon device: %d\n", ret); ++ return ret; ++ } ++ ++ /* ++ * Set initial role value ++ */ ++ queue_delayed_work(system_long_wq, &sd->role_work, 0); ++ flush_delayed_work(&sd->role_work); ++ ++ status = acpi_install_notify_handler(sd->adev->handle, ++ ACPI_DEVICE_NOTIFY, ++ steamdeck_notify, ++ dev); ++ if (ACPI_FAILURE(status)) { ++ dev_err(dev, "Error installing ACPI notify handler\n"); ++ return -EIO; ++ } ++ ++ ret = devm_add_action_or_reset(dev, steamdeck_remove_notify_handler, ++ sd); ++ return ret; ++} ++ ++static const struct platform_device_id steamdeck_extcon_id_table[] = { ++ { .name = "steamdeck-extcon" }, ++ {} ++}; ++MODULE_DEVICE_TABLE(platform, steamdeck_extcon_id_table); ++ ++static struct platform_driver steamdeck_extcon_driver = { ++ .probe = steamdeck_extcon_probe, ++ .driver = { ++ .name = "steamdeck-extcon", ++ }, ++ .id_table = steamdeck_extcon_id_table, ++}; ++module_platform_driver(steamdeck_extcon_driver); ++ ++MODULE_AUTHOR("Andrey Smirnov "); ++MODULE_DESCRIPTION("Steam Deck extcon driver"); ++MODULE_LICENSE("GPL"); + +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Andrey Smirnov +Date: Sat, 15 Jul 2023 12:58:54 -0700 +Subject: [PATCH] hwmon: steamdeck-hwmon: Add support for max battery + level/rate + +Add support for max battery level/charge rate attributes. + +Signed-off-by: Andrey Smirnov +(cherry picked from commit 50af83e8fd75dc52221edd3fb6fd7a7f70c4d8a4) +Signed-off-by: Cristian Ciocaltea +Signed-off-by: Jan200101 +--- + drivers/hwmon/steamdeck-hwmon.c | 72 ++++++++++++++++++++++++++++++++- + 1 file changed, 71 insertions(+), 1 deletion(-) + +diff --git a/drivers/hwmon/steamdeck-hwmon.c b/drivers/hwmon/steamdeck-hwmon.c +index fab9e9460bd4..9d0a5471b181 100644 +--- a/drivers/hwmon/steamdeck-hwmon.c ++++ b/drivers/hwmon/steamdeck-hwmon.c +@@ -180,6 +180,76 @@ static const struct hwmon_chip_info steamdeck_hwmon_chip_info = { + .info = steamdeck_hwmon_info, + }; + ++ ++static ssize_t ++steamdeck_hwmon_simple_store(struct device *dev, const char *buf, size_t count, ++ const char *method, ++ unsigned long upper_limit) ++{ ++ struct steamdeck_hwmon *sd = dev_get_drvdata(dev); ++ unsigned long value; ++ ++ if (kstrtoul(buf, 10, &value) || value >= upper_limit) ++ return -EINVAL; ++ ++ if (ACPI_FAILURE(acpi_execute_simple_method(sd->adev->handle, ++ (char *)method, value))) ++ return -EIO; ++ ++ return count; ++} ++ ++static ssize_t ++steamdeck_hwmon_simple_show(struct device *dev, char *buf, ++ const char *method) ++{ ++ struct steamdeck_hwmon *sd = dev_get_drvdata(dev); ++ unsigned long value; ++ ++ value = steamdeck_hwmon_get(sd, method); ++ if (value < 0) ++ return value; ++ ++ return sprintf(buf, "%ld\n", value); ++} ++ ++#define STEAMDECK_HWMON_ATTR_RW(_name, _set_method, _get_method, \ ++ _upper_limit) \ ++ static ssize_t _name##_show(struct device *dev, \ ++ struct device_attribute *attr, \ ++ char *buf) \ ++ { \ ++ return steamdeck_hwmon_simple_show(dev, buf, \ ++ _get_method); \ ++ } \ ++ static ssize_t _name##_store(struct device *dev, \ ++ struct device_attribute *attr, \ ++ const char *buf, size_t count) \ ++ { \ ++ return steamdeck_hwmon_simple_store(dev, buf, count, \ ++ _set_method, \ ++ _upper_limit); \ ++ } \ ++ static DEVICE_ATTR_RW(_name) ++ ++STEAMDECK_HWMON_ATTR_RW(max_battery_charge_level, "FCBL", "SFBL", 101); ++STEAMDECK_HWMON_ATTR_RW(max_battery_charge_rate, "CHGR", "GCHR", 101); ++ ++static struct attribute *steamdeck_hwmon_attributes[] = { ++ &dev_attr_max_battery_charge_level.attr, ++ &dev_attr_max_battery_charge_rate.attr, ++ NULL ++}; ++ ++static const struct attribute_group steamdeck_hwmon_group = { ++ .attrs = steamdeck_hwmon_attributes, ++}; ++ ++static const struct attribute_group *steamdeck_hwmon_groups[] = { ++ &steamdeck_hwmon_group, ++ NULL ++}; ++ + static int steamdeck_hwmon_probe(struct platform_device *pdev) + { + struct device *dev = &pdev->dev; +@@ -195,7 +265,7 @@ static int steamdeck_hwmon_probe(struct platform_device *pdev) + "steamdeck_hwmon", + sd, + &steamdeck_hwmon_chip_info, +- NULL); ++ steamdeck_hwmon_groups); + if (IS_ERR(hwmon)) { + dev_err(dev, "Failed to register HWMON device"); + return PTR_ERR(hwmon); + +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Andrey Smirnov +Date: Sun, 24 Sep 2023 15:02:33 -0700 +Subject: [PATCH] mfd: steamdeck: Expose controller board power in sysfs + +As of version 118 Deck's BIOS implements "SCBP" method that allows +gating power of the controller board (VBUS). Add a basic WO method to +our root MFD device to allow toggling that. + +Signed-off-by: Andrey Smirnov +(cherry picked from commit f97f32718acc10cbb51fef925842392e80904d74) +Signed-off-by: Cristian Ciocaltea +Signed-off-by: Jan200101 +--- + drivers/mfd/steamdeck.c | 20 ++++++++++++++++++++ + 1 file changed, 20 insertions(+) + +diff --git a/drivers/mfd/steamdeck.c b/drivers/mfd/steamdeck.c +index 0e504b3c2796..a60fa7db9141 100644 +--- a/drivers/mfd/steamdeck.c ++++ b/drivers/mfd/steamdeck.c +@@ -41,9 +41,29 @@ struct steamdeck { + STEAMDECK_ATTR_RO(firmware_version, "PDFW"); + STEAMDECK_ATTR_RO(board_id, "BOID"); + ++static ssize_t controller_board_power_store(struct device *dev, ++ struct device_attribute *attr, ++ const char *buf, size_t count) ++{ ++ struct steamdeck *sd = dev_get_drvdata(dev); ++ bool enabled; ++ ssize_t ret = kstrtobool(buf, &enabled); ++ ++ if (ret) ++ return ret; ++ ++ if (ACPI_FAILURE(acpi_execute_simple_method(sd->adev->handle, ++ "SCBP", enabled))) ++ return -EIO; ++ ++ return count; ++} ++static DEVICE_ATTR_WO(controller_board_power); ++ + static struct attribute *steamdeck_attrs[] = { + &dev_attr_firmware_version.attr, + &dev_attr_board_id.attr, ++ &dev_attr_controller_board_power.attr, + NULL + }; + +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Vicki Pfau +Date: Thu, 30 Jun 2022 18:42:10 -0700 +Subject: [PATCH 01/10] USB: gadget: f_hid: Add Get-Feature report + +While the HID gadget implementation has been sufficient for devices that only +use INTERRUPT transfers, the USB HID standard includes provisions for Set- and +Get-Feature report CONTROL transfers that go over endpoint 0. These were +previously impossible with the existing implementation, and would either send +an empty reply, or stall out. + +As the feature is a standard part of USB HID, it stands to reason that devices +would use it, and that the HID gadget should support it. This patch adds +support for (polled) device-to-host Get-Feature reports through a new ioctl +interface to the hidg class dev nodes. + +Signed-off-by: Vicki Pfau +(cherry picked from commit 8437fa3861c7198a3e286f393c8637c4fc08d2bc) +Signed-off-by: Cristian Ciocaltea +--- + drivers/usb/gadget/function/f_hid.c | 121 ++++++++++++++++++++++++++-- + include/uapi/linux/usb/g_hid.h | 38 +++++++++ + include/uapi/linux/usb/gadgetfs.h | 2 +- + 3 files changed, 154 insertions(+), 7 deletions(-) + create mode 100644 include/uapi/linux/usb/g_hid.h + +diff --git a/drivers/usb/gadget/function/f_hid.c b/drivers/usb/gadget/function/f_hid.c +index ea85e2c701a15..6fec92b5a0bd9 100644 +--- a/drivers/usb/gadget/function/f_hid.c ++++ b/drivers/usb/gadget/function/f_hid.c +@@ -16,6 +16,7 @@ + #include + #include + #include ++#include + + #include "u_f.h" + #include "u_hid.h" +@@ -75,6 +76,13 @@ struct f_hidg { + wait_queue_head_t write_queue; + struct usb_request *req; + ++ /* get report */ ++ struct usb_request *get_req; ++ struct usb_hidg_report get_report; ++ spinlock_t get_spinlock; ++ bool get_pending; ++ wait_queue_head_t get_queue; ++ + struct device dev; + struct cdev cdev; + struct usb_function func; +@@ -523,6 +531,64 @@ static ssize_t f_hidg_write(struct file *file, const char __user *buffer, + return status; + } + ++ ++static int f_hidg_get_report(struct file *file, struct usb_hidg_report __user *buffer) ++{ ++ struct f_hidg *hidg = file->private_data; ++ struct usb_composite_dev *cdev = hidg->func.config->cdev; ++ ++ int status = 0; ++ unsigned long flags; ++ ++ spin_lock_irqsave(&hidg->get_spinlock, flags); ++ ++#define GET_REPORT_COND (!hidg->get_pending) ++ ++ while (!GET_REPORT_COND) { ++ spin_unlock_irqrestore(&hidg->get_spinlock, flags); ++ ++ if (file->f_flags & O_NONBLOCK) ++ return -EAGAIN; ++ ++ if (wait_event_interruptible_exclusive(hidg->get_queue, ++ GET_REPORT_COND)) ++ return -ERESTARTSYS; ++ ++ spin_lock_irqsave(&hidg->get_spinlock, flags); ++ if (!hidg->get_pending) { ++ spin_unlock_irqrestore(&hidg->get_spinlock, flags); ++ return -EINVAL; ++ } ++ } ++ ++ hidg->get_pending = true; ++ spin_unlock_irqrestore(&hidg->get_spinlock, flags); ++ ++ status = copy_from_user(&hidg->get_report, buffer, ++ sizeof(struct usb_hidg_report)); ++ if (status != 0) { ++ ERROR(cdev, "copy_from_user error\n"); ++ status = -EINVAL; ++ } ++ ++ spin_lock_irqsave(&hidg->get_spinlock, flags); ++ hidg->get_pending = false; ++ spin_unlock_irqrestore(&hidg->get_spinlock, flags); ++ ++ wake_up(&hidg->get_queue); ++ return status; ++} ++ ++static long f_hidg_ioctl(struct file *file, unsigned int code, unsigned long arg) ++{ ++ switch (code) { ++ case GADGET_HID_WRITE_GET_REPORT: ++ return f_hidg_get_report(file, (struct usb_hidg_report __user *)arg); ++ default: ++ return -ENOTTY; ++ } ++} ++ + static __poll_t f_hidg_poll(struct file *file, poll_table *wait) + { + struct f_hidg *hidg = file->private_data; +@@ -548,6 +614,7 @@ static __poll_t f_hidg_poll(struct file *file, poll_table *wait) + #undef WRITE_COND + #undef READ_COND_SSREPORT + #undef READ_COND_INTOUT ++#undef GET_REPORT_COND + + static int f_hidg_release(struct inode *inode, struct file *fd) + { +@@ -640,6 +707,10 @@ static void hidg_ssreport_complete(struct usb_ep *ep, struct usb_request *req) + wake_up(&hidg->read_queue); + } + ++static void hidg_get_report_complete(struct usb_ep *ep, struct usb_request *req) ++{ ++} ++ + static int hidg_setup(struct usb_function *f, + const struct usb_ctrlrequest *ctrl) + { +@@ -647,6 +718,8 @@ static int hidg_setup(struct usb_function *f, + struct usb_composite_dev *cdev = f->config->cdev; + struct usb_request *req = cdev->req; + int status = 0; ++ unsigned long flags; ++ bool do_wake = false; + __u16 value, length; + + value = __le16_to_cpu(ctrl->wValue); +@@ -659,14 +732,29 @@ static int hidg_setup(struct usb_function *f, + switch ((ctrl->bRequestType << 8) | ctrl->bRequest) { + case ((USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8 + | HID_REQ_GET_REPORT): +- VDBG(cdev, "get_report\n"); ++ VDBG(cdev, "get_report | wLength=%d\n", ctrl->wLength); + +- /* send an empty report */ +- length = min_t(unsigned, length, hidg->report_length); +- memset(req->buf, 0x0, length); ++ req = hidg->get_req; ++ req->zero = 0; ++ req->length = min_t(unsigned, length, hidg->report_length); ++ status = usb_ep_queue(cdev->gadget->ep0, req, GFP_ATOMIC); ++ if (status < 0) { ++ ERROR(cdev, "usb_ep_queue error on get_report %d\n", ++ status); + +- goto respond; +- break; ++ spin_lock_irqsave(&hidg->get_spinlock, flags); ++ if (hidg->get_pending) { ++ hidg->get_pending = false; ++ do_wake = true; ++ } ++ spin_unlock_irqrestore(&hidg->get_spinlock, flags); ++ ++ if (do_wake) { ++ wake_up(&hidg->get_queue); ++ } ++ } ++ ++ return status; + + case ((USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8 + | HID_REQ_GET_PROTOCOL): +@@ -800,6 +888,14 @@ static void hidg_disable(struct usb_function *f) + + hidg->req = NULL; + spin_unlock_irqrestore(&hidg->write_spinlock, flags); ++ ++ spin_lock_irqsave(&hidg->get_spinlock, flags); ++ if (!hidg->get_pending) { ++ usb_ep_free_request(f->config->cdev->gadget->ep0, hidg->get_req); ++ hidg->get_pending = true; ++ } ++ hidg->get_req = NULL; ++ spin_unlock_irqrestore(&hidg->get_spinlock, flags); + } + + static int hidg_set_alt(struct usb_function *f, unsigned intf, unsigned alt) +@@ -908,6 +1004,7 @@ static const struct file_operations f_hidg_fops = { + .write = f_hidg_write, + .read = f_hidg_read, + .poll = f_hidg_poll, ++ .unlocked_ioctl = f_hidg_ioctl, + .llseek = noop_llseek, + }; + +@@ -918,6 +1015,14 @@ static int hidg_bind(struct usb_configuration *c, struct usb_function *f) + struct usb_string *us; + int status; + ++ hidg->get_req = usb_ep_alloc_request(c->cdev->gadget->ep0, GFP_ATOMIC); ++ if (!hidg->get_req) ++ return -ENOMEM; ++ hidg->get_req->buf = hidg->get_report.data; ++ hidg->get_req->zero = 0; ++ hidg->get_req->complete = hidg_get_report_complete; ++ hidg->get_req->context = hidg; ++ + /* maybe allocate device-global string IDs, and patch descriptors */ + us = usb_gstrings_attach(c->cdev, ct_func_strings, + ARRAY_SIZE(ct_func_string_defs)); +@@ -1003,8 +1108,10 @@ static int hidg_bind(struct usb_configuration *c, struct usb_function *f) + hidg->write_pending = 1; + hidg->req = NULL; + spin_lock_init(&hidg->read_spinlock); ++ spin_lock_init(&hidg->get_spinlock); + init_waitqueue_head(&hidg->write_queue); + init_waitqueue_head(&hidg->read_queue); ++ init_waitqueue_head(&hidg->get_queue); + INIT_LIST_HEAD(&hidg->completed_out_req); + + /* create char device */ +@@ -1021,6 +1128,8 @@ static int hidg_bind(struct usb_configuration *c, struct usb_function *f) + if (hidg->req != NULL) + free_ep_req(hidg->in_ep, hidg->req); + ++ usb_ep_free_request(c->cdev->gadget->ep0, hidg->get_req); ++ + return status; + } + +diff --git a/include/uapi/linux/usb/g_hid.h b/include/uapi/linux/usb/g_hid.h +new file mode 100644 +index 0000000000000..c6068b4863543 +--- /dev/null ++++ b/include/uapi/linux/usb/g_hid.h +@@ -0,0 +1,38 @@ ++/* SPDX-License-Identifier: GPL-2.0+ WITH Linux-syscall-note */ ++/* ++ * g_hid.h -- Header file for USB HID gadget driver ++ * ++ * Copyright (C) 2022 Valve Software ++ * ++ * This program is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation; either version 2 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program; if not, write to the Free Software ++ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA ++ */ ++ ++#ifndef __UAPI_LINUX_USB_G_HID_H ++#define __UAPI_LINUX_USB_G_HID_H ++ ++#include ++ ++struct usb_hidg_report { ++ __u16 length; ++ __u8 data[512]; ++}; ++ ++/* The 'g' code is also used by gadgetfs and hid gadget ioctl requests. ++ * Don't add any colliding codes to either driver, and keep ++ * them in unique ranges (size 0x20 for now). ++ */ ++#define GADGET_HID_WRITE_GET_REPORT _IOW('g', 0x42, struct usb_hidg_report) ++ ++#endif /* __UAPI_LINUX_USB_G_HID_H */ +diff --git a/include/uapi/linux/usb/gadgetfs.h b/include/uapi/linux/usb/gadgetfs.h +index 835473910a498..9754822b2a409 100644 +--- a/include/uapi/linux/usb/gadgetfs.h ++++ b/include/uapi/linux/usb/gadgetfs.h +@@ -62,7 +62,7 @@ struct usb_gadgetfs_event { + }; + + +-/* The 'g' code is also used by printer gadget ioctl requests. ++/* The 'g' code is also used by printer and hid gadget ioctl requests. + * Don't add any colliding codes to either driver, and keep + * them in unique ranges (size 0x20 for now). + */ +-- +2.41.0 + + +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Vicki Pfau +Date: Thu, 30 Jun 2022 18:43:10 -0700 +Subject: [PATCH 02/10] USB: gadget: f_hid: Add Set-Feature report + +While the HID gadget implementation has been sufficient for devices that only +use INTERRUPT transfers, the USB HID standard includes provisions for Set- and +Get-Feature report CONTROL transfers that go over endpoint 0. These were +previously impossible with the existing implementation, and would either send +an empty reply, or stall out. + +As the feature is a standard part of USB HID, it stands to reason that devices +would use it, and that the HID gadget should support it. This patch adds +support for host-to-device Set-Feature reports through a new ioctl +interface to the hidg class dev nodes. + +Signed-off-by: Vicki Pfau +(cherry picked from commit 3d82be0ec3aa3b947d9c927d7b06c433de15be8b) +Signed-off-by: Cristian Ciocaltea +--- + drivers/usb/gadget/function/f_hid.c | 110 ++++++++++++++++++++++++++-- + include/uapi/linux/usb/g_hid.h | 24 +----- + 2 files changed, 106 insertions(+), 28 deletions(-) + +diff --git a/drivers/usb/gadget/function/f_hid.c b/drivers/usb/gadget/function/f_hid.c +index 6fec92b5a0bd9..172cba91aded1 100644 +--- a/drivers/usb/gadget/function/f_hid.c ++++ b/drivers/usb/gadget/function/f_hid.c +@@ -76,6 +76,11 @@ struct f_hidg { + wait_queue_head_t write_queue; + struct usb_request *req; + ++ /* set report */ ++ struct list_head completed_set_req; ++ spinlock_t set_spinlock; ++ wait_queue_head_t set_queue; ++ + /* get report */ + struct usb_request *get_req; + struct usb_hidg_report get_report; +@@ -531,6 +536,54 @@ static ssize_t f_hidg_write(struct file *file, const char __user *buffer, + return status; + } + ++static int f_hidg_set_report(struct file *file, struct usb_hidg_report __user *buffer) ++{ ++ struct f_hidg *hidg = file->private_data; ++ struct f_hidg_req_list *list; ++ struct usb_request *req; ++ unsigned long flags; ++ unsigned short length; ++ int status; ++ ++ spin_lock_irqsave(&hidg->set_spinlock, flags); ++ ++#define SET_REPORT_COND (!list_empty(&hidg->completed_set_req)) ++ ++ /* wait for at least one buffer to complete */ ++ while (!SET_REPORT_COND) { ++ spin_unlock_irqrestore(&hidg->set_spinlock, flags); ++ if (file->f_flags & O_NONBLOCK) ++ return -EAGAIN; ++ ++ if (wait_event_interruptible(hidg->set_queue, SET_REPORT_COND)) ++ return -ERESTARTSYS; ++ ++ spin_lock_irqsave(&hidg->set_spinlock, flags); ++ } ++ ++ /* pick the first one */ ++ list = list_first_entry(&hidg->completed_set_req, ++ struct f_hidg_req_list, list); ++ ++ /* ++ * Remove this from list to protect it from being free() ++ * while host disables our function ++ */ ++ list_del(&list->list); ++ ++ req = list->req; ++ spin_unlock_irqrestore(&hidg->set_spinlock, flags); ++ ++ /* copy to user outside spinlock */ ++ length = min_t(unsigned short, sizeof(buffer->data), req->actual); ++ status = copy_to_user(&buffer->length, &length, sizeof(buffer->length)); ++ if (!status) { ++ status = copy_to_user(&buffer->data, req->buf, length); ++ } ++ kfree(list); ++ free_ep_req(hidg->func.config->cdev->gadget->ep0, req); ++ return status; ++} + + static int f_hidg_get_report(struct file *file, struct usb_hidg_report __user *buffer) + { +@@ -582,6 +635,8 @@ static int f_hidg_get_report(struct file *file, struct usb_hidg_report __user *b + static long f_hidg_ioctl(struct file *file, unsigned int code, unsigned long arg) + { + switch (code) { ++ case GADGET_HID_READ_SET_REPORT: ++ return f_hidg_set_report(file, (struct usb_hidg_report __user *)arg); + case GADGET_HID_WRITE_GET_REPORT: + return f_hidg_get_report(file, (struct usb_hidg_report __user *)arg); + default: +@@ -596,6 +651,7 @@ static __poll_t f_hidg_poll(struct file *file, poll_table *wait) + + poll_wait(file, &hidg->read_queue, wait); + poll_wait(file, &hidg->write_queue, wait); ++ poll_wait(file, &hidg->set_queue, wait); + + if (WRITE_COND) + ret |= EPOLLOUT | EPOLLWRNORM; +@@ -608,12 +664,16 @@ static __poll_t f_hidg_poll(struct file *file, poll_table *wait) + ret |= EPOLLIN | EPOLLRDNORM; + } + ++ if (SET_REPORT_COND) ++ ret |= EPOLLPRI; ++ + return ret; + } + + #undef WRITE_COND + #undef READ_COND_SSREPORT + #undef READ_COND_INTOUT ++#undef SET_REPORT_COND + #undef GET_REPORT_COND + + static int f_hidg_release(struct inode *inode, struct file *fd) +@@ -658,11 +718,19 @@ static void hidg_intout_complete(struct usb_ep *ep, struct usb_request *req) + + req_list->req = req; + +- spin_lock_irqsave(&hidg->read_spinlock, flags); +- list_add_tail(&req_list->list, &hidg->completed_out_req); +- spin_unlock_irqrestore(&hidg->read_spinlock, flags); ++ if (ep == cdev->gadget->ep0) { ++ spin_lock_irqsave(&hidg->set_spinlock, flags); ++ list_add_tail(&req_list->list, &hidg->completed_set_req); ++ spin_unlock_irqrestore(&hidg->set_spinlock, flags); + +- wake_up(&hidg->read_queue); ++ wake_up(&hidg->set_queue); ++ } else { ++ spin_lock_irqsave(&hidg->read_spinlock, flags); ++ list_add_tail(&req_list->list, &hidg->completed_out_req); ++ spin_unlock_irqrestore(&hidg->read_spinlock, flags); ++ ++ wake_up(&hidg->read_queue); ++ } + break; + default: + ERROR(cdev, "Set report failed %d\n", req->status); +@@ -775,12 +843,27 @@ static int hidg_setup(struct usb_function *f, + case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8 + | HID_REQ_SET_REPORT): + VDBG(cdev, "set_report | wLength=%d\n", ctrl->wLength); +- if (hidg->use_out_ep) ++ if (!hidg->use_out_ep) { ++ req->complete = hidg_ssreport_complete; ++ req->context = hidg; ++ goto respond; ++ } ++ if (!length) + goto stall; +- req->complete = hidg_ssreport_complete; ++ req = alloc_ep_req(cdev->gadget->ep0, GFP_ATOMIC); ++ if (!req) ++ return -ENOMEM; ++ req->complete = hidg_intout_complete; + req->context = hidg; +- goto respond; +- break; ++ req->zero = 0; ++ req->length = length; ++ status = usb_ep_queue(cdev->gadget->ep0, req, GFP_ATOMIC); ++ if (status < 0) { ++ ERROR(cdev, "usb_ep_queue error on set_report %d\n", status); ++ free_ep_req(cdev->gadget->ep0, req); ++ } ++ ++ return status; + + case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8 + | HID_REQ_SET_PROTOCOL): +@@ -880,6 +963,14 @@ static void hidg_disable(struct usb_function *f) + spin_unlock_irqrestore(&hidg->read_spinlock, flags); + } + ++ spin_lock_irqsave(&hidg->set_spinlock, flags); ++ list_for_each_entry_safe(list, next, &hidg->completed_set_req, list) { ++ free_ep_req(f->config->cdev->gadget->ep0, list->req); ++ list_del(&list->list); ++ kfree(list); ++ } ++ spin_unlock_irqrestore(&hidg->set_spinlock, flags); ++ + spin_lock_irqsave(&hidg->write_spinlock, flags); + if (!hidg->write_pending) { + free_ep_req(hidg->in_ep, hidg->req); +@@ -1108,11 +1199,14 @@ static int hidg_bind(struct usb_configuration *c, struct usb_function *f) + hidg->write_pending = 1; + hidg->req = NULL; + spin_lock_init(&hidg->read_spinlock); ++ spin_lock_init(&hidg->set_spinlock); + spin_lock_init(&hidg->get_spinlock); + init_waitqueue_head(&hidg->write_queue); + init_waitqueue_head(&hidg->read_queue); ++ init_waitqueue_head(&hidg->set_queue); + init_waitqueue_head(&hidg->get_queue); + INIT_LIST_HEAD(&hidg->completed_out_req); ++ INIT_LIST_HEAD(&hidg->completed_set_req); + + /* create char device */ + cdev_init(&hidg->cdev, &f_hidg_fops); +diff --git a/include/uapi/linux/usb/g_hid.h b/include/uapi/linux/usb/g_hid.h +index c6068b4863543..54814c2c68d60 100644 +--- a/include/uapi/linux/usb/g_hid.h ++++ b/include/uapi/linux/usb/g_hid.h +@@ -1,38 +1,22 @@ + /* SPDX-License-Identifier: GPL-2.0+ WITH Linux-syscall-note */ +-/* +- * g_hid.h -- Header file for USB HID gadget driver +- * +- * Copyright (C) 2022 Valve Software +- * +- * This program is free software; you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation; either version 2 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +- * GNU General Public License for more details. +- * +- * You should have received a copy of the GNU General Public License +- * along with this program; if not, write to the Free Software +- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +- */ + + #ifndef __UAPI_LINUX_USB_G_HID_H + #define __UAPI_LINUX_USB_G_HID_H + + #include + ++#define HIDG_REPORT_SIZE_MAX 64 ++ + struct usb_hidg_report { + __u16 length; +- __u8 data[512]; ++ __u8 data[HIDG_REPORT_SIZE_MAX]; + }; + + /* The 'g' code is also used by gadgetfs and hid gadget ioctl requests. + * Don't add any colliding codes to either driver, and keep + * them in unique ranges (size 0x20 for now). + */ ++#define GADGET_HID_READ_SET_REPORT _IOR('g', 0x41, struct usb_hidg_report) + #define GADGET_HID_WRITE_GET_REPORT _IOW('g', 0x42, struct usb_hidg_report) + + #endif /* __UAPI_LINUX_USB_G_HID_H */ +-- +2.41.0 + + +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Vicki Pfau +Date: Tue, 29 Nov 2022 18:32:58 -0800 +Subject: [PATCH 03/10] HID: hid-steam: Update list of identifiers from SDL + +SDL includes a list of settings (registers), reports (cmds), and various other +identifiers that were provided by Valve. This commit imports a significant +chunk of that list as well as updating the guessed names and replacing a +handful of magic constants. It also replaces bitmask definitions that used hex +with the BIT macro. + +Signed-off-by: Vicki Pfau +--- + drivers/hid/hid-steam.c | 156 +++++++++++++++++++++++++++++++--------- + 1 file changed, 121 insertions(+), 35 deletions(-) + +diff --git a/drivers/hid/hid-steam.c b/drivers/hid/hid-steam.c +index b110818fc9458..39a9bf3b7f77d 100644 +--- a/drivers/hid/hid-steam.c ++++ b/drivers/hid/hid-steam.c +@@ -71,7 +71,7 @@ static LIST_HEAD(steam_devices); + + /* + * Commands that can be sent in a feature report. +- * Thanks to Valve for some valuable hints. ++ * Thanks to Valve and SDL for some valuable hints. + */ + #define STEAM_CMD_SET_MAPPINGS 0x80 + #define STEAM_CMD_CLEAR_MAPPINGS 0x81 +@@ -80,27 +80,98 @@ static LIST_HEAD(steam_devices); + #define STEAM_CMD_GET_ATTRIB_LABEL 0x84 + #define STEAM_CMD_DEFAULT_MAPPINGS 0x85 + #define STEAM_CMD_FACTORY_RESET 0x86 +-#define STEAM_CMD_WRITE_REGISTER 0x87 ++#define STEAM_CMD_SET_REGISTER 0x87 + #define STEAM_CMD_CLEAR_REGISTER 0x88 +-#define STEAM_CMD_READ_REGISTER 0x89 ++#define STEAM_CMD_GET_REGISTER 0x89 + #define STEAM_CMD_GET_REGISTER_LABEL 0x8a + #define STEAM_CMD_GET_REGISTER_MAX 0x8b + #define STEAM_CMD_GET_REGISTER_DEFAULT 0x8c + #define STEAM_CMD_SET_MODE 0x8d +-#define STEAM_CMD_DEFAULT_MOUSE 0x8e +-#define STEAM_CMD_FORCEFEEDBAK 0x8f +-#define STEAM_CMD_REQUEST_COMM_STATUS 0xb4 +-#define STEAM_CMD_GET_SERIAL 0xae ++#define STEAM_CMD_DEFAULT_REGISTER 0x8e ++#define STEAM_CMD_HAPTIC_PULSE 0x8f ++#define STEAM_CMD_TURN_OFF_CONTROLLER 0x9f ++#define STEAM_CMD_GET_DEVICE_IFNO 0xa1 ++#define STEAM_CMD_CALIBRATE_TRACKPADS 0xa7 ++#define STEAM_CMD_SET_SERIAL 0xa9 ++#define STEAM_CMD_GET_TRACKPAD_CALIB 0xaa ++#define STEAM_CMD_GET_TRACKPAD_FACTORY_CALIB 0xab ++#define STEAM_CMD_GET_TRACKPAD_RAW_DATA 0xac ++#define STEAM_CMD_ENABLE_PAIRING 0xad ++#define STEAM_CMD_GET_STRING_ATTRIB 0xae ++#define STEAM_CMD_RADIO_ERASE_RECORDS 0xaf ++#define STEAM_CMD_RADIO_WRITE_RECORD 0xb0 ++#define STEAM_CMD_SET_DONGLE_SETTING 0xb1 ++#define STEAM_CMD_DONGLE_DISCONNECT_DEV 0xb2 ++#define STEAM_CMD_DONGLE_COMMIT_DEV 0xb3 ++#define STEAM_CMD_DONGLE_GET_STATE 0xb4 ++#define STEAM_CMD_CALIBRATE_GYRO 0xb5 ++#define STEAM_CMD_PLAY_AUDIO 0xb6 ++#define STEAM_CMD_AUDIO_UPDATE_START 0xb7 ++#define STEAM_CMD_AUDIO_UPDATE_DATA 0xb8 ++#define STEAM_CMD_AUDIO_UPDATE_COMPLETE 0xb9 ++#define STEAM_CMD_GET_CHIPID 0xba ++#define STEAM_CMD_CALIBRATE_JOYSTICK 0xbf ++#define STEAM_CMD_CALIBRATE_TRIGGERS 0xc0 ++#define STEAM_CMD_SET_AUDIO_MAPPING 0xc1 ++#define STEAM_CMD_CHECK_GYRO_FW_LOAD 0xc2 ++#define STEAM_CMD_CALIBRATE_ANALOG 0xc3 ++#define STEAM_CMD_DONGLE_GET_CONN_SLOTS 0xc4 ++#define STEAM_CMD_HAPTIC_CMD 0xea + #define STEAM_CMD_HAPTIC_RUMBLE 0xeb + + /* Some useful register ids */ +-#define STEAM_REG_LPAD_MODE 0x07 +-#define STEAM_REG_RPAD_MODE 0x08 +-#define STEAM_REG_RPAD_MARGIN 0x18 +-#define STEAM_REG_LED 0x2d +-#define STEAM_REG_GYRO_MODE 0x30 +-#define STEAM_REG_LPAD_CLICK_PRESSURE 0x34 +-#define STEAM_REG_RPAD_CLICK_PRESSURE 0x35 ++#define STEAM_REG_MOUSE_SENSITIVITY 0x00 ++#define STEAM_REG_MOUSE_ACCELERATION 0x01 ++#define STEAM_REG_TRACKBALL_ROTATION_ANGLE 0x02 ++#define STEAM_REG_HAPTIC_INTENSITY 0x03 ++#define STEAM_REG_LEFT_GAMEPAD_STICK_ENABLED 0x04 ++#define STEAM_REG_RIGHT_GAMEPAD_STICK_ENABLED 0x05 ++#define STEAM_REG_USB_DEBUG_MODE 0x06 ++#define STEAM_REG_LEFT_TRACKPAD_MODE 0x07 ++#define STEAM_REG_RIGHT_TRACKPAD_MODE 0x08 ++#define STEAM_REG_MOUSE_POINTER_ENABLED 0x09 ++#define STEAM_REG_DPAD_DEADZONE 0x0a ++#define STEAM_REG_MINIMUM_MOMENTUM_VEL 0x0b ++#define STEAM_REG_MOMENTUM_DECAY_AMOUNT 0x0c ++#define STEAM_REG_PAD_REL_MODE_TICKS_PER_PIXEL 0x0d ++#define STEAM_REG_HAPTIC_INCREMENT 0x0e ++#define STEAM_REG_DPAD_ANGLE_SIN 0x0f ++#define STEAM_REG_DPAD_ANGLE_COS 0x10 ++#define STEAM_REG_MOMENTUM_VERTICAL_DIVISOR 0x11 ++#define STEAM_REG_MOMENTUM_MAXIMUM_VELOCITY 0x12 ++#define STEAM_REG_TRACKPAD_Z_ON 0x13 ++#define STEAM_REG_TRACKPAD_Z_OFF 0x14 ++#define STEAM_REG_SENSITIVY_SCALE_AMOUNT 0x15 ++#define STEAM_REG_LEFT_TRACKPAD_SECONDARY_MODE 0x16 ++#define STEAM_REG_RIGHT_TRACKPAD_SECONDARY_MODE 0x17 ++#define STEAM_REG_SMOOTH_ABSOLUTE_MOUSE 0x18 ++#define STEAM_REG_STEAMBUTTON_POWEROFF_TIME 0x19 ++#define STEAM_REG_TRACKPAD_OUTER_RADIUS 0x1b ++#define STEAM_REG_TRACKPAD_Z_ON_LEFT 0x1c ++#define STEAM_REG_TRACKPAD_Z_OFF_LEFT 0x1d ++#define STEAM_REG_TRACKPAD_OUTER_SPIN_VEL 0x1e ++#define STEAM_REG_TRACKPAD_OUTER_SPIN_RADIUS 0x1f ++#define STEAM_REG_TRACKPAD_OUTER_SPIN_HORIZONTAL_ONLY 0x20 ++#define STEAM_REG_TRACKPAD_RELATIVE_MODE_DEADZONE 0x21 ++#define STEAM_REG_TRACKPAD_RELATIVE_MODE_MAX_VEL 0x22 ++#define STEAM_REG_TRACKPAD_RELATIVE_MODE_INVERT_Y 0x23 ++#define STEAM_REG_TRACKPAD_DOUBLE_TAP_BEEP_ENABLED 0x24 ++#define STEAM_REG_TRACKPAD_DOUBLE_TAP_BEEP_PERIOD 0x25 ++#define STEAM_REG_TRACKPAD_DOUBLE_TAP_BEEP_COUNT 0x26 ++#define STEAM_REG_TRACKPAD_OUTER_RADIUS_RELEASE_ON_TRANSITION 0x27 ++#define STEAM_REG_RADIAL_MODE_ANGLE 0x28 ++#define STEAM_REG_HAPTIC_INTENSITY_MOUSE_MODE 0x29 ++#define STEAM_REG_LEFT_DPAD_REQUIRES_CLICK 0x2a ++#define STEAM_REG_RIGHT_DPAD_REQUIRES_CLICK 0x2b ++#define STEAM_REG_LED_BASELINE_BRIGHTNESS 0x2c ++#define STEAM_REG_LED_USER_BRIGHTNESS 0x2d ++#define STEAM_REG_ENABLE_RAW_JOYSTICK 0x2e ++#define STEAM_REG_ENABLE_FAST_SCAN 0x2f ++#define STEAM_REG_GYRO_MODE 0x30 ++#define STEAM_REG_WIRELESS_PACKET_VERSION 0x31 ++#define STEAM_REG_SLEEP_INACTIVITY_TIMEOUT 0x32 ++#define STEAM_REG_LEFT_TRACKPAD_CLICK_PRESSURE 0x34 ++#define STEAM_REG_RIGHT_TRACKPAD_CLICK_PRESSURE 0x35 + + /* Raw event identifiers */ + #define STEAM_EV_INPUT_DATA 0x01 +@@ -108,13 +179,28 @@ static LIST_HEAD(steam_devices); + #define STEAM_EV_BATTERY 0x04 + #define STEAM_EV_DECK_INPUT_DATA 0x09 + ++/* String attribute idenitifiers */ ++#define STEAM_ATTRIB_STR_BOARD_SERIAL 0x00 ++#define STEAM_ATTRIB_STR_UNIT_SERIAL 0x01 ++ + /* Values for GYRO_MODE (bitmask) */ +-#define STEAM_GYRO_MODE_OFF 0x0000 +-#define STEAM_GYRO_MODE_STEERING 0x0001 +-#define STEAM_GYRO_MODE_TILT 0x0002 +-#define STEAM_GYRO_MODE_SEND_ORIENTATION 0x0004 +-#define STEAM_GYRO_MODE_SEND_RAW_ACCEL 0x0008 +-#define STEAM_GYRO_MODE_SEND_RAW_GYRO 0x0010 ++#define STEAM_GYRO_MODE_OFF 0 ++#define STEAM_GYRO_MODE_STEERING BIT(0) ++#define STEAM_GYRO_MODE_TILT BIT(1) ++#define STEAM_GYRO_MODE_SEND_ORIENTATION BIT(2) ++#define STEAM_GYRO_MODE_SEND_RAW_ACCEL BIT(3) ++#define STEAM_GYRO_MODE_SEND_RAW_GYRO BIT(4) ++ ++/* Trackpad modes */ ++#define STEAM_TRACKPAD_ABSOLUTE_MOUSE 0x00 ++#define STEAM_TRACKPAD_RELATIVE_MOUSE 0x01 ++#define STEAM_TRACKPAD_DPAD_FOUR_WAY_DISCRETE 0x02 ++#define STEAM_TRACKPAD_DPAD_FOUR_WAY_OVERLAP 0x03 ++#define STEAM_TRACKPAD_DPAD_EIGHT_WAY 0x04 ++#define STEAM_TRACKPAD_RADIAL_MODE 0x05 ++#define STEAM_TRACKPAD_ABSOLUTE_DPAD 0x06 ++#define STEAM_TRACKPAD_NONE 0x07 ++#define STEAM_TRACKPAD_GESTURE_KEYBOARD 0x08 + + /* Other random constants */ + #define STEAM_SERIAL_LEN 10 +@@ -232,7 +318,7 @@ static int steam_write_registers(struct steam_device *steam, + /* Send: 0x87 len (reg valLo valHi)* */ + u8 reg; + u16 val; +- u8 cmd[64] = {STEAM_CMD_WRITE_REGISTER, 0x00}; ++ u8 cmd[64] = {STEAM_CMD_SET_REGISTER, 0x00}; + int ret; + va_list args; + +@@ -268,7 +354,7 @@ static int steam_get_serial(struct steam_device *steam) + * Recv: 0xae 0x15 0x01 serialnumber (10 chars) + */ + int ret; +- u8 cmd[] = {STEAM_CMD_GET_SERIAL, 0x15, 0x01}; ++ u8 cmd[] = {STEAM_CMD_GET_STRING_ATTRIB, 0x15, STEAM_ATTRIB_STR_UNIT_SERIAL}; + u8 reply[3 + STEAM_SERIAL_LEN + 1]; + + ret = steam_send_report(steam, cmd, sizeof(cmd)); +@@ -277,7 +363,7 @@ static int steam_get_serial(struct steam_device *steam) + ret = steam_recv_report(steam, reply, sizeof(reply)); + if (ret < 0) + return ret; +- if (reply[0] != 0xae || reply[1] != 0x15 || reply[2] != 0x01) ++ if (reply[0] != 0xae || reply[1] != 0x15 || reply[2] != STEAM_ATTRIB_STR_UNIT_SERIAL) + return -EIO; + reply[3 + STEAM_SERIAL_LEN] = 0; + strscpy(steam->serial_no, reply + 3, sizeof(steam->serial_no)); +@@ -291,7 +377,7 @@ static int steam_get_serial(struct steam_device *steam) + */ + static inline int steam_request_conn_status(struct steam_device *steam) + { +- return steam_send_report_byte(steam, STEAM_CMD_REQUEST_COMM_STATUS); ++ return steam_send_report_byte(steam, STEAM_CMD_DONGLE_GET_STATE); + } + + static inline int steam_haptic_rumble(struct steam_device *steam, +@@ -339,9 +425,9 @@ static void steam_set_lizard_mode(struct steam_device *steam, bool enable) + /* enable esc, enter, cursors */ + steam_send_report_byte(steam, STEAM_CMD_DEFAULT_MAPPINGS); + /* enable mouse */ +- steam_send_report_byte(steam, STEAM_CMD_DEFAULT_MOUSE); ++ steam_send_report_byte(steam, STEAM_CMD_DEFAULT_REGISTER); + steam_write_registers(steam, +- STEAM_REG_RPAD_MARGIN, 0x01, /* enable margin */ ++ STEAM_REG_SMOOTH_ABSOLUTE_MOUSE, 0x01, /* enable smooth */ + 0); + + cancel_delayed_work_sync(&steam->heartbeat); +@@ -351,11 +437,11 @@ static void steam_set_lizard_mode(struct steam_device *steam, bool enable) + + if (steam->quirks & STEAM_QUIRK_DECK) { + steam_write_registers(steam, +- STEAM_REG_RPAD_MARGIN, 0x00, /* disable margin */ +- STEAM_REG_LPAD_MODE, 0x07, /* disable mouse */ +- STEAM_REG_RPAD_MODE, 0x07, /* disable mouse */ +- STEAM_REG_LPAD_CLICK_PRESSURE, 0xFFFF, /* disable clicky pad */ +- STEAM_REG_RPAD_CLICK_PRESSURE, 0xFFFF, /* disable clicky pad */ ++ STEAM_REG_SMOOTH_ABSOLUTE_MOUSE, 0x00, /* disable smooth */ ++ STEAM_REG_LEFT_TRACKPAD_MODE, STEAM_TRACKPAD_NONE, /* disable mouse */ ++ STEAM_REG_RIGHT_TRACKPAD_MODE, STEAM_TRACKPAD_NONE, /* disable mouse */ ++ STEAM_REG_LEFT_TRACKPAD_CLICK_PRESSURE, 0xFFFF, /* disable clicky pad */ ++ STEAM_REG_RIGHT_TRACKPAD_CLICK_PRESSURE, 0xFFFF, /* disable clicky pad */ + 0); + /* + * The Steam Deck has a watchdog that automatically enables +@@ -365,9 +451,9 @@ static void steam_set_lizard_mode(struct steam_device *steam, bool enable) + schedule_delayed_work(&steam->heartbeat, 5 * HZ); + } else { + steam_write_registers(steam, +- STEAM_REG_RPAD_MARGIN, 0x00, /* disable margin */ +- STEAM_REG_LPAD_MODE, 0x07, /* disable mouse */ +- STEAM_REG_RPAD_MODE, 0x07, /* disable mouse */ ++ STEAM_REG_SMOOTH_ABSOLUTE_MOUSE, 0x00, /* disable smooth */ ++ STEAM_REG_LEFT_TRACKPAD_MODE, STEAM_TRACKPAD_NONE, /* disable mouse */ ++ STEAM_REG_RIGHT_TRACKPAD_MODE, STEAM_TRACKPAD_NONE, /* disable mouse */ + 0); + } + } +@@ -747,7 +833,7 @@ static void steam_lizard_mode_heartbeat(struct work_struct *work) + if (!steam->client_opened && steam->client_hdev) { + steam_send_report_byte(steam, STEAM_CMD_CLEAR_MAPPINGS); + steam_write_registers(steam, +- STEAM_REG_RPAD_MODE, 0x07, /* disable mouse */ ++ STEAM_REG_RIGHT_TRACKPAD_MODE, STEAM_TRACKPAD_NONE, /* disable mouse */ + 0); + schedule_delayed_work(&steam->heartbeat, 5 * HZ); + } +-- +2.41.0 + + +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Vicki Pfau +Date: Wed, 16 Nov 2022 19:54:26 -0800 +Subject: [PATCH 04/10] HID: hid-steam: Add gamepad-only mode switched to by + holding options + +Signed-off-by: Vicki Pfau +--- + drivers/hid/hid-steam.c | 72 +++++++++++++++++++++++++++++++++++++++++ + 1 file changed, 72 insertions(+) + +diff --git a/drivers/hid/hid-steam.c b/drivers/hid/hid-steam.c +index 39a9bf3b7f77d..0620046b142ef 100644 +--- a/drivers/hid/hid-steam.c ++++ b/drivers/hid/hid-steam.c +@@ -202,6 +202,11 @@ static LIST_HEAD(steam_devices); + #define STEAM_TRACKPAD_NONE 0x07 + #define STEAM_TRACKPAD_GESTURE_KEYBOARD 0x08 + ++/* Pad identifiers for the deck */ ++#define STEAM_PAD_LEFT 0 ++#define STEAM_PAD_RIGHT 1 ++#define STEAM_PAD_BOTH 2 ++ + /* Other random constants */ + #define STEAM_SERIAL_LEN 10 + +@@ -221,6 +226,9 @@ struct steam_device { + u8 battery_charge; + u16 voltage; + struct delayed_work heartbeat; ++ struct delayed_work mode_switch; ++ bool did_mode_switch; ++ bool gamepad_mode; + struct work_struct rumble_work; + u16 rumble_left; + u16 rumble_right; +@@ -380,6 +388,33 @@ static inline int steam_request_conn_status(struct steam_device *steam) + return steam_send_report_byte(steam, STEAM_CMD_DONGLE_GET_STATE); + } + ++/* ++ * Send a haptic pulse to the trackpads ++ * Duration and interval are measured in microseconds, count is the number ++ * of pulses to send for duration time with interval microseconds between them ++ * and gain is measured in decibels, ranging from -24 to +6 ++ */ ++static inline int steam_haptic_pulse(struct steam_device *steam, u8 pad, ++ u16 duration, u16 interval, u16 count, u8 gain) ++{ ++ u8 report[10] = {STEAM_CMD_HAPTIC_PULSE, 8}; ++ ++ /* Left and right are swapped on this report for legacy reasons */ ++ if (pad < STEAM_PAD_BOTH) ++ pad ^= 1; ++ ++ report[2] = pad; ++ report[3] = duration & 0xFF; ++ report[4] = duration >> 8; ++ report[5] = interval & 0xFF; ++ report[6] = interval >> 8; ++ report[7] = count & 0xFF; ++ report[8] = count >> 8; ++ report[9] = gain; ++ ++ return steam_send_report(steam, report, sizeof(report)); ++} ++ + static inline int steam_haptic_rumble(struct steam_device *steam, + u16 intensity, u16 left_speed, u16 right_speed, + u8 left_gain, u8 right_gain) +@@ -421,6 +456,9 @@ static int steam_play_effect(struct input_dev *dev, void *data, + + static void steam_set_lizard_mode(struct steam_device *steam, bool enable) + { ++ if (steam->gamepad_mode) ++ enable = false; ++ + if (enable) { + /* enable esc, enter, cursors */ + steam_send_report_byte(steam, STEAM_CMD_DEFAULT_MAPPINGS); +@@ -805,6 +843,29 @@ static void steam_work_connect_cb(struct work_struct *work) + } + } + ++static void steam_mode_switch_cb(struct work_struct *work) ++{ ++ struct steam_device *steam = container_of(to_delayed_work(work), ++ struct steam_device, mode_switch); ++ steam->gamepad_mode = !steam->gamepad_mode; ++ if (!lizard_mode) ++ return; ++ ++ mutex_lock(&steam->mutex); ++ if (steam->gamepad_mode) ++ steam_set_lizard_mode(steam, false); ++ else if (!steam->client_opened) ++ steam_set_lizard_mode(steam, lizard_mode); ++ mutex_unlock(&steam->mutex); ++ ++ steam_haptic_pulse(steam, STEAM_PAD_RIGHT, 0x190, 0, 1, 0); ++ if (steam->gamepad_mode) { ++ steam_haptic_pulse(steam, STEAM_PAD_LEFT, 0x14D, 0x14D, 0x2D, 0); ++ } else { ++ steam_haptic_pulse(steam, STEAM_PAD_LEFT, 0x1F4, 0x1F4, 0x1E, 0); ++ } ++} ++ + static bool steam_is_valve_interface(struct hid_device *hdev) + { + struct hid_report_enum *rep_enum; +@@ -977,6 +1038,7 @@ static int steam_probe(struct hid_device *hdev, + mutex_init(&steam->mutex); + steam->quirks = id->driver_data; + INIT_WORK(&steam->work_connect, steam_work_connect_cb); ++ INIT_DELAYED_WORK(&steam->mode_switch, steam_mode_switch_cb); + INIT_LIST_HEAD(&steam->list); + INIT_DEFERRABLE_WORK(&steam->heartbeat, steam_lizard_mode_heartbeat); + INIT_WORK(&steam->rumble_work, steam_haptic_rumble_cb); +@@ -1036,6 +1098,7 @@ static int steam_probe(struct hid_device *hdev, + client_hdev_fail: + cancel_work_sync(&steam->work_connect); + cancel_delayed_work_sync(&steam->heartbeat); ++ cancel_delayed_work_sync(&steam->mode_switch); + cancel_work_sync(&steam->rumble_work); + steam_alloc_fail: + hid_err(hdev, "%s: failed with error %d\n", +@@ -1059,6 +1122,7 @@ static void steam_remove(struct hid_device *hdev) + cancel_delayed_work_sync(&steam->heartbeat); + mutex_unlock(&steam->mutex); + cancel_work_sync(&steam->work_connect); ++ cancel_delayed_work_sync(&steam->mode_switch); + if (steam->quirks & STEAM_QUIRK_WIRELESS) { + hid_info(hdev, "Steam wireless receiver disconnected"); + } +@@ -1393,6 +1457,14 @@ static void steam_do_deck_input_event(struct steam_device *steam, + input_event(input, EV_KEY, BTN_BASE, !!(b14 & BIT(2))); + + input_sync(input); ++ ++ if (!(b9 & BIT(6)) && steam->did_mode_switch) { ++ steam->did_mode_switch = false; ++ cancel_delayed_work_sync(&steam->mode_switch); ++ } else if (!steam->client_opened && (b9 & BIT(6)) && !steam->did_mode_switch) { ++ steam->did_mode_switch = true; ++ schedule_delayed_work(&steam->mode_switch, 45 * HZ / 100); ++ } + } + + /* +-- +2.41.0 + + +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Vicki Pfau +Date: Mon, 8 May 2023 20:24:56 -0700 +Subject: [PATCH 05/10] HID: hid-steam: Clean up locking + +This cleans up the locking logic so that the spinlock is consistently used for +access to a small handful of struct variables, and the mutex is exclusively and +consistently used for ensuring that mutliple threads aren't trying to +send/receive reports at the same time. Previously, only some report +transactions were guarded by this mutex, potentially breaking atomicity. The +mutex has been renamed to reflect this usage. + +Signed-off-by: Vicki Pfau +--- + drivers/hid/hid-steam.c | 148 ++++++++++++++++++++++++---------------- + 1 file changed, 90 insertions(+), 58 deletions(-) + +diff --git a/drivers/hid/hid-steam.c b/drivers/hid/hid-steam.c +index 0620046b142ef..845ca71b8bd3a 100644 +--- a/drivers/hid/hid-steam.c ++++ b/drivers/hid/hid-steam.c +@@ -214,7 +214,7 @@ struct steam_device { + struct list_head list; + spinlock_t lock; + struct hid_device *hdev, *client_hdev; +- struct mutex mutex; ++ struct mutex report_mutex; + bool client_opened; + struct input_dev __rcu *input; + unsigned long quirks; +@@ -361,21 +361,26 @@ static int steam_get_serial(struct steam_device *steam) + * Send: 0xae 0x15 0x01 + * Recv: 0xae 0x15 0x01 serialnumber (10 chars) + */ +- int ret; ++ int ret = 0; + u8 cmd[] = {STEAM_CMD_GET_STRING_ATTRIB, 0x15, STEAM_ATTRIB_STR_UNIT_SERIAL}; + u8 reply[3 + STEAM_SERIAL_LEN + 1]; + ++ mutex_lock(&steam->report_mutex); + ret = steam_send_report(steam, cmd, sizeof(cmd)); + if (ret < 0) +- return ret; ++ goto out; + ret = steam_recv_report(steam, reply, sizeof(reply)); + if (ret < 0) +- return ret; +- if (reply[0] != 0xae || reply[1] != 0x15 || reply[2] != STEAM_ATTRIB_STR_UNIT_SERIAL) +- return -EIO; ++ goto out; ++ if (reply[0] != 0xae || reply[1] != 0x15 || reply[2] != STEAM_ATTRIB_STR_UNIT_SERIAL) { ++ ret = -EIO; ++ goto out; ++ } + reply[3 + STEAM_SERIAL_LEN] = 0; + strscpy(steam->serial_no, reply + 3, sizeof(steam->serial_no)); +- return 0; ++out: ++ mutex_unlock(&steam->report_mutex); ++ return ret; + } + + /* +@@ -385,7 +390,11 @@ static int steam_get_serial(struct steam_device *steam) + */ + static inline int steam_request_conn_status(struct steam_device *steam) + { +- return steam_send_report_byte(steam, STEAM_CMD_DONGLE_GET_STATE); ++ int ret; ++ mutex_lock(&steam->report_mutex); ++ ret = steam_send_report_byte(steam, STEAM_CMD_DONGLE_GET_STATE); ++ mutex_unlock(&steam->report_mutex); ++ return ret; + } + + /* +@@ -397,6 +406,7 @@ static inline int steam_request_conn_status(struct steam_device *steam) + static inline int steam_haptic_pulse(struct steam_device *steam, u8 pad, + u16 duration, u16 interval, u16 count, u8 gain) + { ++ int ret; + u8 report[10] = {STEAM_CMD_HAPTIC_PULSE, 8}; + + /* Left and right are swapped on this report for legacy reasons */ +@@ -412,13 +422,17 @@ static inline int steam_haptic_pulse(struct steam_device *steam, u8 pad, + report[8] = count >> 8; + report[9] = gain; + +- return steam_send_report(steam, report, sizeof(report)); ++ mutex_lock(&steam->report_mutex); ++ ret = steam_send_report(steam, report, sizeof(report)); ++ mutex_unlock(&steam->report_mutex); ++ return ret; + } + + static inline int steam_haptic_rumble(struct steam_device *steam, + u16 intensity, u16 left_speed, u16 right_speed, + u8 left_gain, u8 right_gain) + { ++ int ret; + u8 report[11] = {STEAM_CMD_HAPTIC_RUMBLE, 9}; + + report[3] = intensity & 0xFF; +@@ -430,7 +444,10 @@ static inline int steam_haptic_rumble(struct steam_device *steam, + report[9] = left_gain; + report[10] = right_gain; + +- return steam_send_report(steam, report, sizeof(report)); ++ mutex_lock(&steam->report_mutex); ++ ret = steam_send_report(steam, report, sizeof(report)); ++ mutex_unlock(&steam->report_mutex); ++ return ret; + } + + static void steam_haptic_rumble_cb(struct work_struct *work) +@@ -460,6 +477,7 @@ static void steam_set_lizard_mode(struct steam_device *steam, bool enable) + enable = false; + + if (enable) { ++ mutex_lock(&steam->report_mutex); + /* enable esc, enter, cursors */ + steam_send_report_byte(steam, STEAM_CMD_DEFAULT_MAPPINGS); + /* enable mouse */ +@@ -467,9 +485,11 @@ static void steam_set_lizard_mode(struct steam_device *steam, bool enable) + steam_write_registers(steam, + STEAM_REG_SMOOTH_ABSOLUTE_MOUSE, 0x01, /* enable smooth */ + 0); ++ mutex_unlock(&steam->report_mutex); + + cancel_delayed_work_sync(&steam->heartbeat); + } else { ++ mutex_lock(&steam->report_mutex); + /* disable esc, enter, cursor */ + steam_send_report_byte(steam, STEAM_CMD_CLEAR_MAPPINGS); + +@@ -481,18 +501,19 @@ static void steam_set_lizard_mode(struct steam_device *steam, bool enable) + STEAM_REG_LEFT_TRACKPAD_CLICK_PRESSURE, 0xFFFF, /* disable clicky pad */ + STEAM_REG_RIGHT_TRACKPAD_CLICK_PRESSURE, 0xFFFF, /* disable clicky pad */ + 0); ++ mutex_unlock(&steam->report_mutex); + /* + * The Steam Deck has a watchdog that automatically enables + * lizard mode if it doesn't see any traffic for too long + */ +- if (!work_busy(&steam->heartbeat.work)) +- schedule_delayed_work(&steam->heartbeat, 5 * HZ); ++ schedule_delayed_work(&steam->heartbeat, 5 * HZ); + } else { + steam_write_registers(steam, + STEAM_REG_SMOOTH_ABSOLUTE_MOUSE, 0x00, /* disable smooth */ + STEAM_REG_LEFT_TRACKPAD_MODE, STEAM_TRACKPAD_NONE, /* disable mouse */ + STEAM_REG_RIGHT_TRACKPAD_MODE, STEAM_TRACKPAD_NONE, /* disable mouse */ + 0); ++ mutex_unlock(&steam->report_mutex); + } + } + } +@@ -500,22 +521,29 @@ static void steam_set_lizard_mode(struct steam_device *steam, bool enable) + static int steam_input_open(struct input_dev *dev) + { + struct steam_device *steam = input_get_drvdata(dev); ++ unsigned long flags; ++ bool set_lizard_mode; + +- mutex_lock(&steam->mutex); +- if (!steam->client_opened && lizard_mode) ++ spin_lock_irqsave(&steam->lock, flags); ++ set_lizard_mode = !steam->client_opened && lizard_mode; ++ spin_unlock_irqrestore(&steam->lock, flags); ++ if (set_lizard_mode) + steam_set_lizard_mode(steam, false); +- mutex_unlock(&steam->mutex); ++ + return 0; + } + + static void steam_input_close(struct input_dev *dev) + { + struct steam_device *steam = input_get_drvdata(dev); ++ unsigned long flags; ++ bool set_lizard_mode; + +- mutex_lock(&steam->mutex); +- if (!steam->client_opened && lizard_mode) ++ spin_lock_irqsave(&steam->lock, flags); ++ set_lizard_mode = !steam->client_opened && lizard_mode; ++ spin_unlock_irqrestore(&steam->lock, flags); ++ if (set_lizard_mode) + steam_set_lizard_mode(steam, true); +- mutex_unlock(&steam->mutex); + } + + static enum power_supply_property steam_battery_props[] = { +@@ -760,6 +788,7 @@ static int steam_register(struct steam_device *steam) + { + int ret; + bool client_opened; ++ unsigned long flags; + + /* + * This function can be called several times in a row with the +@@ -772,11 +801,9 @@ static int steam_register(struct steam_device *steam) + * Unlikely, but getting the serial could fail, and it is not so + * important, so make up a serial number and go on. + */ +- mutex_lock(&steam->mutex); + if (steam_get_serial(steam) < 0) + strscpy(steam->serial_no, "XXXXXXXXXX", + sizeof(steam->serial_no)); +- mutex_unlock(&steam->mutex); + + hid_info(steam->hdev, "Steam Controller '%s' connected", + steam->serial_no); +@@ -791,11 +818,11 @@ static int steam_register(struct steam_device *steam) + mutex_unlock(&steam_devices_lock); + } + +- mutex_lock(&steam->mutex); ++ spin_lock_irqsave(&steam->lock, flags); + client_opened = steam->client_opened; ++ spin_unlock_irqrestore(&steam->lock, flags); + if (!client_opened) + steam_set_lizard_mode(steam, lizard_mode); +- mutex_unlock(&steam->mutex); + + if (!client_opened) + ret = steam_input_register(steam); +@@ -847,16 +874,21 @@ static void steam_mode_switch_cb(struct work_struct *work) + { + struct steam_device *steam = container_of(to_delayed_work(work), + struct steam_device, mode_switch); ++ unsigned long flags; ++ bool client_opened; + steam->gamepad_mode = !steam->gamepad_mode; + if (!lizard_mode) + return; + +- mutex_lock(&steam->mutex); + if (steam->gamepad_mode) + steam_set_lizard_mode(steam, false); +- else if (!steam->client_opened) +- steam_set_lizard_mode(steam, lizard_mode); +- mutex_unlock(&steam->mutex); ++ else { ++ spin_lock_irqsave(&steam->lock, flags); ++ client_opened = steam->client_opened; ++ spin_unlock_irqrestore(&steam->lock, flags); ++ if (!client_opened) ++ steam_set_lizard_mode(steam, lizard_mode); ++ } + + steam_haptic_pulse(steam, STEAM_PAD_RIGHT, 0x190, 0, 1, 0); + if (steam->gamepad_mode) { +@@ -889,16 +921,21 @@ static void steam_lizard_mode_heartbeat(struct work_struct *work) + { + struct steam_device *steam = container_of(work, struct steam_device, + heartbeat.work); ++ bool client_opened; ++ unsigned long flags; + +- mutex_lock(&steam->mutex); +- if (!steam->client_opened && steam->client_hdev) { ++ spin_lock_irqsave(&steam->lock, flags); ++ client_opened = steam->client_opened; ++ spin_unlock_irqrestore(&steam->lock, flags); ++ if (!client_opened) { ++ mutex_lock(&steam->report_mutex); + steam_send_report_byte(steam, STEAM_CMD_CLEAR_MAPPINGS); + steam_write_registers(steam, + STEAM_REG_RIGHT_TRACKPAD_MODE, STEAM_TRACKPAD_NONE, /* disable mouse */ + 0); ++ mutex_unlock(&steam->report_mutex); + schedule_delayed_work(&steam->heartbeat, 5 * HZ); + } +- mutex_unlock(&steam->mutex); + } + + static int steam_client_ll_parse(struct hid_device *hdev) +@@ -921,10 +958,11 @@ static void steam_client_ll_stop(struct hid_device *hdev) + static int steam_client_ll_open(struct hid_device *hdev) + { + struct steam_device *steam = hdev->driver_data; ++ unsigned long flags; + +- mutex_lock(&steam->mutex); ++ spin_lock_irqsave(&steam->lock, flags); + steam->client_opened = true; +- mutex_unlock(&steam->mutex); ++ spin_unlock_irqrestore(&steam->lock, flags); + + steam_input_unregister(steam); + +@@ -939,14 +977,12 @@ static void steam_client_ll_close(struct hid_device *hdev) + bool connected; + + spin_lock_irqsave(&steam->lock, flags); +- connected = steam->connected; ++ steam->client_opened = false; ++ connected = steam->connected && !steam->client_opened; + spin_unlock_irqrestore(&steam->lock, flags); + +- mutex_lock(&steam->mutex); +- steam->client_opened = false; + if (connected) + steam_set_lizard_mode(steam, lizard_mode); +- mutex_unlock(&steam->mutex); + + if (connected) + steam_input_register(steam); +@@ -1035,7 +1071,7 @@ static int steam_probe(struct hid_device *hdev, + steam->hdev = hdev; + hid_set_drvdata(hdev, steam); + spin_lock_init(&steam->lock); +- mutex_init(&steam->mutex); ++ mutex_init(&steam->report_mutex); + steam->quirks = id->driver_data; + INIT_WORK(&steam->work_connect, steam_work_connect_cb); + INIT_DELAYED_WORK(&steam->mode_switch, steam_mode_switch_cb); +@@ -1043,13 +1079,6 @@ static int steam_probe(struct hid_device *hdev, + INIT_DEFERRABLE_WORK(&steam->heartbeat, steam_lizard_mode_heartbeat); + INIT_WORK(&steam->rumble_work, steam_haptic_rumble_cb); + +- steam->client_hdev = steam_create_client_hid(hdev); +- if (IS_ERR(steam->client_hdev)) { +- ret = PTR_ERR(steam->client_hdev); +- goto client_hdev_fail; +- } +- steam->client_hdev->driver_data = steam; +- + /* + * With the real steam controller interface, do not connect hidraw. + * Instead, create the client_hid and connect that. +@@ -1058,10 +1087,6 @@ static int steam_probe(struct hid_device *hdev, + if (ret) + goto hid_hw_start_fail; + +- ret = hid_add_device(steam->client_hdev); +- if (ret) +- goto client_hdev_add_fail; +- + ret = hid_hw_open(hdev); + if (ret) { + hid_err(hdev, +@@ -1087,15 +1112,26 @@ static int steam_probe(struct hid_device *hdev, + } + } + ++ steam->client_hdev = steam_create_client_hid(hdev); ++ if (IS_ERR(steam->client_hdev)) { ++ ret = PTR_ERR(steam->client_hdev); ++ goto client_hdev_fail; ++ } ++ steam->client_hdev->driver_data = steam; ++ ++ ret = hid_add_device(steam->client_hdev); ++ if (ret) ++ goto client_hdev_add_fail; ++ + return 0; + +-input_register_fail: +-hid_hw_open_fail: + client_hdev_add_fail: + hid_hw_stop(hdev); +-hid_hw_start_fail: +- hid_destroy_device(steam->client_hdev); + client_hdev_fail: ++ hid_destroy_device(steam->client_hdev); ++input_register_fail: ++hid_hw_open_fail: ++hid_hw_start_fail: + cancel_work_sync(&steam->work_connect); + cancel_delayed_work_sync(&steam->heartbeat); + cancel_delayed_work_sync(&steam->mode_switch); +@@ -1115,14 +1151,12 @@ static void steam_remove(struct hid_device *hdev) + return; + } + ++ cancel_delayed_work_sync(&steam->heartbeat); ++ cancel_delayed_work_sync(&steam->mode_switch); ++ cancel_work_sync(&steam->work_connect); + hid_destroy_device(steam->client_hdev); +- mutex_lock(&steam->mutex); + steam->client_hdev = NULL; + steam->client_opened = false; +- cancel_delayed_work_sync(&steam->heartbeat); +- mutex_unlock(&steam->mutex); +- cancel_work_sync(&steam->work_connect); +- cancel_delayed_work_sync(&steam->mode_switch); + if (steam->quirks & STEAM_QUIRK_WIRELESS) { + hid_info(hdev, "Steam wireless receiver disconnected"); + } +@@ -1597,10 +1631,8 @@ static int steam_param_set_lizard_mode(const char *val, + + mutex_lock(&steam_devices_lock); + list_for_each_entry(steam, &steam_devices, list) { +- mutex_lock(&steam->mutex); + if (!steam->client_opened) + steam_set_lizard_mode(steam, lizard_mode); +- mutex_unlock(&steam->mutex); + } + mutex_unlock(&steam_devices_lock); + return 0; +-- +2.41.0 + + +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Vicki Pfau +Date: Wed, 10 May 2023 17:27:12 -0700 +Subject: [PATCH 06/10] HID: hid-steam: Make client_opened a counter + +The client_opened variable was used to track if the hidraw was opened by any +clients to silence keyboard/mouse events while opened. However, there was no +counting of how many clients were opened, so opening two at the same time and +then closing one would fool the driver into thinking it had no remaining opened +clients. + +Signed-off-by: Vicki Pfau +--- + drivers/hid/hid-steam.c | 10 +++++----- + 1 file changed, 5 insertions(+), 5 deletions(-) + +diff --git a/drivers/hid/hid-steam.c b/drivers/hid/hid-steam.c +index 845ca71b8bd3a..0c2fe51b29bc1 100644 +--- a/drivers/hid/hid-steam.c ++++ b/drivers/hid/hid-steam.c +@@ -215,7 +215,7 @@ struct steam_device { + spinlock_t lock; + struct hid_device *hdev, *client_hdev; + struct mutex report_mutex; +- bool client_opened; ++ unsigned long client_opened; + struct input_dev __rcu *input; + unsigned long quirks; + struct work_struct work_connect; +@@ -787,7 +787,7 @@ static void steam_battery_unregister(struct steam_device *steam) + static int steam_register(struct steam_device *steam) + { + int ret; +- bool client_opened; ++ unsigned long client_opened; + unsigned long flags; + + /* +@@ -961,7 +961,7 @@ static int steam_client_ll_open(struct hid_device *hdev) + unsigned long flags; + + spin_lock_irqsave(&steam->lock, flags); +- steam->client_opened = true; ++ steam->client_opened++; + spin_unlock_irqrestore(&steam->lock, flags); + + steam_input_unregister(steam); +@@ -977,7 +977,7 @@ static void steam_client_ll_close(struct hid_device *hdev) + bool connected; + + spin_lock_irqsave(&steam->lock, flags); +- steam->client_opened = false; ++ steam->client_opened--; + connected = steam->connected && !steam->client_opened; + spin_unlock_irqrestore(&steam->lock, flags); + +@@ -1156,7 +1156,7 @@ static void steam_remove(struct hid_device *hdev) + cancel_work_sync(&steam->work_connect); + hid_destroy_device(steam->client_hdev); + steam->client_hdev = NULL; +- steam->client_opened = false; ++ steam->client_opened = 0; + if (steam->quirks & STEAM_QUIRK_WIRELESS) { + hid_info(hdev, "Steam wireless receiver disconnected"); + } +-- +2.41.0 + + +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Vicki Pfau +Date: Thu, 18 May 2023 18:00:35 -0700 +Subject: [PATCH 07/10] HID: hid-steam: Better handling of serial number length + +The second byte of the GET_STRING_ATTRIB report is a length, so we should set +the size of the buffer to be the size we're actually requesting, and only +reject the reply if the length out is nonsensical. + +Signed-off-by: Vicki Pfau +--- + drivers/hid/hid-steam.c | 10 +++++----- + 1 file changed, 5 insertions(+), 5 deletions(-) + +diff --git a/drivers/hid/hid-steam.c b/drivers/hid/hid-steam.c +index 0c2fe51b29bc1..92e3e1052fa42 100644 +--- a/drivers/hid/hid-steam.c ++++ b/drivers/hid/hid-steam.c +@@ -208,7 +208,7 @@ static LIST_HEAD(steam_devices); + #define STEAM_PAD_BOTH 2 + + /* Other random constants */ +-#define STEAM_SERIAL_LEN 10 ++#define STEAM_SERIAL_LEN 0x15 + + struct steam_device { + struct list_head list; +@@ -359,10 +359,10 @@ static int steam_get_serial(struct steam_device *steam) + { + /* + * Send: 0xae 0x15 0x01 +- * Recv: 0xae 0x15 0x01 serialnumber (10 chars) ++ * Recv: 0xae 0x15 0x01 serialnumber + */ + int ret = 0; +- u8 cmd[] = {STEAM_CMD_GET_STRING_ATTRIB, 0x15, STEAM_ATTRIB_STR_UNIT_SERIAL}; ++ u8 cmd[] = {STEAM_CMD_GET_STRING_ATTRIB, sizeof(steam->serial_no), STEAM_ATTRIB_STR_UNIT_SERIAL}; + u8 reply[3 + STEAM_SERIAL_LEN + 1]; + + mutex_lock(&steam->report_mutex); +@@ -372,12 +372,12 @@ static int steam_get_serial(struct steam_device *steam) + ret = steam_recv_report(steam, reply, sizeof(reply)); + if (ret < 0) + goto out; +- if (reply[0] != 0xae || reply[1] != 0x15 || reply[2] != STEAM_ATTRIB_STR_UNIT_SERIAL) { ++ if (reply[0] != 0xae || reply[1] < 1 || reply[1] > sizeof(steam->serial_no) || reply[2] != STEAM_ATTRIB_STR_UNIT_SERIAL) { + ret = -EIO; + goto out; + } + reply[3 + STEAM_SERIAL_LEN] = 0; +- strscpy(steam->serial_no, reply + 3, sizeof(steam->serial_no)); ++ strscpy(steam->serial_no, reply + 3, reply[1]); + out: + mutex_unlock(&steam->report_mutex); + return ret; +-- +2.41.0 diff --git a/patches/nobara/uinput.patch b/patches/nobara/uinput.patch new file mode 100644 index 0000000..c5666a8 --- /dev/null +++ b/patches/nobara/uinput.patch @@ -0,0 +1,133 @@ +--- + drivers/input/misc/uinput.c | 48 +++++++++++++++++++++++++------------ + include/uapi/linux/uinput.h | 5 ++++ + 2 files changed, 38 insertions(+), 15 deletions(-) + + +diff --git a/drivers/input/misc/uinput.c b/drivers/input/misc/uinput.c +index 84051f20b18a..2c3180370a02 100644 +--- a/drivers/input/misc/uinput.c ++++ b/drivers/input/misc/uinput.c +@@ -20,6 +20,7 @@ + */ + #include + #include ++#include + #include + #include + #include +@@ -280,7 +281,7 @@ static int uinput_dev_flush(struct input_dev *dev, struct file *file) + + static void uinput_destroy_device(struct uinput_device *udev) + { +- const char *name, *phys; ++ const char *name, *phys, *uniq; + struct input_dev *dev = udev->dev; + enum uinput_state old_state = udev->state; + +@@ -289,6 +290,7 @@ static void uinput_destroy_device(struct uinput_device *udev) + if (dev) { + name = dev->name; + phys = dev->phys; ++ uniq = dev->uniq; + if (old_state == UIST_CREATED) { + uinput_flush_requests(udev); + input_unregister_device(dev); +@@ -297,6 +299,7 @@ static void uinput_destroy_device(struct uinput_device *udev) + } + kfree(name); + kfree(phys); ++ kfree(uniq); + udev->dev = NULL; + } + } +@@ -831,6 +834,24 @@ static int uinput_str_to_user(void __user *dest, const char *str, + return ret ? -EFAULT : len; + } + ++static int uinput_get_user_str(struct uinput_device *udev, const char **kptr, ++ const char *uptr, unsigned int size) ++{ ++ char *tmp; ++ ++ if (udev->state == UIST_CREATED) ++ return -EINVAL; ++ ++ tmp = strndup_user(uptr, size); ++ if (IS_ERR(tmp)) ++ return PTR_ERR(tmp); ++ ++ kfree(*kptr); ++ *kptr = tmp; ++ ++ return 0; ++} ++ + static long uinput_ioctl_handler(struct file *file, unsigned int cmd, + unsigned long arg, void __user *p) + { +@@ -839,7 +860,6 @@ static long uinput_ioctl_handler(struct file *file, unsigned int cmd, + struct uinput_ff_upload ff_up; + struct uinput_ff_erase ff_erase; + struct uinput_request *req; +- char *phys; + const char *name; + unsigned int size; + +@@ -916,19 +936,8 @@ static long uinput_ioctl_handler(struct file *file, unsigned int cmd, + goto out; + + case UI_SET_PHYS: +- if (udev->state == UIST_CREATED) { +- retval = -EINVAL; +- goto out; +- } +- +- phys = strndup_user(p, 1024); +- if (IS_ERR(phys)) { +- retval = PTR_ERR(phys); +- goto out; +- } +- +- kfree(udev->dev->phys); +- udev->dev->phys = phys; ++ pr_warn_once("uinput: UI_SET_PHYS is deprecated. Use UI_SET_PHYS_STR"); ++ retval = uinput_get_user_str(udev, &udev->dev->phys, p, 1024); + goto out; + + case UI_BEGIN_FF_UPLOAD: +@@ -1023,6 +1032,15 @@ static long uinput_ioctl_handler(struct file *file, unsigned int cmd, + case UI_ABS_SETUP & ~IOCSIZE_MASK: + retval = uinput_abs_setup(udev, p, size); + goto out; ++ ++ case UI_SET_PHYS_STR(0): ++ retval = uinput_get_user_str(udev, &udev->dev->phys, p, size); ++ goto out; ++ ++ case UI_SET_UNIQ_STR(0): ++ retval = uinput_get_user_str(udev, &udev->dev->uniq, p, size); ++ goto out; ++ + } + + retval = -EINVAL; +diff --git a/include/uapi/linux/uinput.h b/include/uapi/linux/uinput.h +index c9e677e3af1d..84d4fa142830 100644 +--- a/include/uapi/linux/uinput.h ++++ b/include/uapi/linux/uinput.h +@@ -142,9 +142,14 @@ struct uinput_abs_setup { + #define UI_SET_LEDBIT _IOW(UINPUT_IOCTL_BASE, 105, int) + #define UI_SET_SNDBIT _IOW(UINPUT_IOCTL_BASE, 106, int) + #define UI_SET_FFBIT _IOW(UINPUT_IOCTL_BASE, 107, int) ++ ++/* DEPRECATED: Data size is ambiguous. Use UI_SET_PHYS_STR instead. */ + #define UI_SET_PHYS _IOW(UINPUT_IOCTL_BASE, 108, char*) ++ + #define UI_SET_SWBIT _IOW(UINPUT_IOCTL_BASE, 109, int) + #define UI_SET_PROPBIT _IOW(UINPUT_IOCTL_BASE, 110, int) ++#define UI_SET_PHYS_STR(len) _IOC(_IOC_WRITE, UINPUT_IOCTL_BASE, 111, len) ++#define UI_SET_UNIQ_STR(len) _IOC(_IOC_WRITE, UINPUT_IOCTL_BASE, 112, len) + + #define UI_BEGIN_FF_UPLOAD _IOWR(UINPUT_IOCTL_BASE, 200, struct uinput_ff_upload) + #define UI_END_FF_UPLOAD _IOW(UINPUT_IOCTL_BASE, 201, struct uinput_ff_upload) diff --git a/patches/nobara/winesync.patch b/patches/nobara/winesync.patch new file mode 100644 index 0000000..3c72546 --- /dev/null +++ b/patches/nobara/winesync.patch @@ -0,0 +1,5071 @@ +From 153c94d81f583dfbd9e4e81eefc6a9b8e83ff06d Mon Sep 17 00:00:00 2001 +From: Zebediah Figura +Date: Fri, 5 Mar 2021 10:50:45 -0600 +Subject: [PATCH 01/34] winesync: Introduce the winesync driver and character + device. + +--- + drivers/misc/Kconfig | 11 +++++++ + drivers/misc/Makefile | 1 + + drivers/misc/winesync.c | 64 +++++++++++++++++++++++++++++++++++++++++ + 3 files changed, 76 insertions(+) + create mode 100644 drivers/misc/winesync.c + +diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig +index 94e9fb4cdd76..4f9e3d80a6e8 100644 +--- a/drivers/misc/Kconfig ++++ b/drivers/misc/Kconfig +@@ -562,6 +562,17 @@ config VCPU_STALL_DETECTOR + This driver can also be built as a module. If so, the module + will be called tps6594-pfsm. + ++config WINESYNC ++ tristate "Synchronization primitives for Wine" ++ help ++ This module provides kernel support for synchronization primitives ++ used by Wine. It is not a hardware driver. ++ ++ To compile this driver as a module, choose M here: the ++ module will be called winesync. ++ ++ If unsure, say N. ++ + source "drivers/misc/c2port/Kconfig" + source "drivers/misc/eeprom/Kconfig" + source "drivers/misc/cb710/Kconfig" +diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile +index 2be8542616dd..d061fe45407b 100644 +--- a/drivers/misc/Makefile ++++ b/drivers/misc/Makefile +@@ -59,6 +59,7 @@ obj-$(CONFIG_HABANA_AI) += habanalabs/ + obj-$(CONFIG_UACCE) += uacce/ + obj-$(CONFIG_XILINX_SDFEC) += xilinx_sdfec.o + obj-$(CONFIG_HISI_HIKEY_USB) += hisi_hikey_usb.o ++obj-$(CONFIG_WINESYNC) += winesync.o + obj-$(CONFIG_HI6421V600_IRQ) += hi6421v600-irq.o + obj-$(CONFIG_OPEN_DICE) += open-dice.o + obj-$(CONFIG_GP_PCI1XXXX) += mchp_pci1xxxx/ +diff --git a/drivers/misc/winesync.c b/drivers/misc/winesync.c +new file mode 100644 +index 000000000000..111f33c5676e +--- /dev/null ++++ b/drivers/misc/winesync.c +@@ -0,0 +1,64 @@ ++// SPDX-License-Identifier: GPL-2.0-only ++/* ++ * winesync.c - Kernel driver for Wine synchronization primitives ++ * ++ * Copyright (C) 2021 Zebediah Figura ++ */ ++ ++#include ++#include ++#include ++ ++#define WINESYNC_NAME "winesync" ++ ++static int winesync_char_open(struct inode *inode, struct file *file) ++{ ++ return nonseekable_open(inode, file); ++} ++ ++static int winesync_char_release(struct inode *inode, struct file *file) ++{ ++ return 0; ++} ++ ++static long winesync_char_ioctl(struct file *file, unsigned int cmd, ++ unsigned long parm) ++{ ++ switch (cmd) { ++ default: ++ return -ENOSYS; ++ } ++} ++ ++static const struct file_operations winesync_fops = { ++ .owner = THIS_MODULE, ++ .open = winesync_char_open, ++ .release = winesync_char_release, ++ .unlocked_ioctl = winesync_char_ioctl, ++ .compat_ioctl = winesync_char_ioctl, ++ .llseek = no_llseek, ++}; ++ ++static struct miscdevice winesync_misc = { ++ .minor = MISC_DYNAMIC_MINOR, ++ .name = WINESYNC_NAME, ++ .fops = &winesync_fops, ++}; ++ ++static int __init winesync_init(void) ++{ ++ return misc_register(&winesync_misc); ++} ++ ++static void __exit winesync_exit(void) ++{ ++ misc_deregister(&winesync_misc); ++} ++ ++module_init(winesync_init); ++module_exit(winesync_exit); ++ ++MODULE_AUTHOR("Zebediah Figura"); ++MODULE_DESCRIPTION("Kernel driver for Wine synchronization primitives"); ++MODULE_LICENSE("GPL"); ++MODULE_ALIAS("devname:" WINESYNC_NAME); +-- +2.37.3 + +From 1f142d40cb7537bd936a68cadaf0f2a0d94abd62 Mon Sep 17 00:00:00 2001 +From: Zebediah Figura +Date: Fri, 5 Mar 2021 10:57:06 -0600 +Subject: [PATCH 02/34] winesync: Reserve a minor device number and ioctl + range. + +--- + Documentation/admin-guide/devices.txt | 3 ++- + Documentation/userspace-api/ioctl/ioctl-number.rst | 2 ++ + drivers/misc/winesync.c | 3 ++- + include/linux/miscdevice.h | 1 + + 4 files changed, 7 insertions(+), 2 deletions(-) + +diff --git a/Documentation/admin-guide/devices.txt b/Documentation/admin-guide/devices.txt +index c07dc0ee860e..4e5abe508426 100644 +--- a/Documentation/admin-guide/devices.txt ++++ b/Documentation/admin-guide/devices.txt +@@ -376,8 +376,9 @@ + 240 = /dev/userio Serio driver testing device + 241 = /dev/vhost-vsock Host kernel driver for virtio vsock + 242 = /dev/rfkill Turning off radio transmissions (rfkill) ++ 243 = /dev/winesync Wine synchronization primitive device + +- 243-254 Reserved for local use ++ 244-254 Reserved for local use + 255 Reserved for MISC_DYNAMIC_MINOR + + 11 char Raw keyboard device (Linux/SPARC only) +diff --git a/Documentation/userspace-api/ioctl/ioctl-number.rst b/Documentation/userspace-api/ioctl/ioctl-number.rst +index 3b985b19f39d..3f313fd4338c 100644 +--- a/Documentation/userspace-api/ioctl/ioctl-number.rst ++++ b/Documentation/userspace-api/ioctl/ioctl-number.rst +@@ -375,6 +375,8 @@ Code Seq# Include File Comments + + 0xF6 all LTTng Linux Trace Toolkit Next Generation + ++0xF7 00-0F uapi/linux/winesync.h Wine synchronization primitives ++ + 0xF8 all arch/x86/include/uapi/asm/amd_hsmp.h AMD HSMP EPYC system management interface driver + + 0xFD all linux/dm-ioctl.h +diff --git a/drivers/misc/winesync.c b/drivers/misc/winesync.c +index 111f33c5676e..85cb6ccaa077 100644 +--- a/drivers/misc/winesync.c ++++ b/drivers/misc/winesync.c +@@ -40,7 +40,7 @@ static const struct file_operations winesync_fops = { + }; + + static struct miscdevice winesync_misc = { +- .minor = MISC_DYNAMIC_MINOR, ++ .minor = WINESYNC_MINOR, + .name = WINESYNC_NAME, + .fops = &winesync_fops, + }; +@@ -62,3 +62,4 @@ MODULE_AUTHOR("Zebediah Figura"); + MODULE_DESCRIPTION("Kernel driver for Wine synchronization primitives"); + MODULE_LICENSE("GPL"); + MODULE_ALIAS("devname:" WINESYNC_NAME); ++MODULE_ALIAS_MISCDEV(WINESYNC_MINOR); +diff --git a/include/linux/miscdevice.h b/include/linux/miscdevice.h +index 0676f18093f9..350aecfcfb29 100644 +--- a/include/linux/miscdevice.h ++++ b/include/linux/miscdevice.h +@@ -71,6 +71,7 @@ + #define USERIO_MINOR 240 + #define VHOST_VSOCK_MINOR 241 + #define RFKILL_MINOR 242 ++#define WINESYNC_MINOR 243 + #define MISC_DYNAMIC_MINOR 255 + + struct device; +-- +2.36.0 + +From 8ad26f39cb5442d9e17f22ed0cda8d3669bb11b5 Mon Sep 17 00:00:00 2001 +From: Zebediah Figura +Date: Fri, 5 Mar 2021 11:15:39 -0600 +Subject: [PATCH 03/34] winesync: Introduce WINESYNC_IOC_CREATE_SEM and + WINESYNC_IOC_DELETE. + +--- + drivers/misc/winesync.c | 117 ++++++++++++++++++++++++++++++++++ + include/uapi/linux/winesync.h | 25 ++++++++ + 2 files changed, 142 insertions(+) + create mode 100644 include/uapi/linux/winesync.h + +diff --git a/drivers/misc/winesync.c b/drivers/misc/winesync.c +index 85cb6ccaa077..36e31bbe0390 100644 +--- a/drivers/misc/winesync.c ++++ b/drivers/misc/winesync.c +@@ -8,23 +8,140 @@ + #include + #include + #include ++#include ++#include ++#include + + #define WINESYNC_NAME "winesync" + ++enum winesync_type { ++ WINESYNC_TYPE_SEM, ++}; ++ ++struct winesync_obj { ++ struct rcu_head rhead; ++ struct kref refcount; ++ ++ enum winesync_type type; ++ ++ union { ++ struct { ++ __u32 count; ++ __u32 max; ++ } sem; ++ } u; ++}; ++ ++struct winesync_device { ++ struct xarray objects; ++}; ++ ++static void destroy_obj(struct kref *ref) ++{ ++ struct winesync_obj *obj = container_of(ref, struct winesync_obj, refcount); ++ ++ kfree_rcu(obj, rhead); ++} ++ ++static void put_obj(struct winesync_obj *obj) ++{ ++ kref_put(&obj->refcount, destroy_obj); ++} ++ + static int winesync_char_open(struct inode *inode, struct file *file) + { ++ struct winesync_device *dev; ++ ++ dev = kzalloc(sizeof(*dev), GFP_KERNEL); ++ if (!dev) ++ return -ENOMEM; ++ ++ xa_init_flags(&dev->objects, XA_FLAGS_ALLOC); ++ ++ file->private_data = dev; + return nonseekable_open(inode, file); + } + + static int winesync_char_release(struct inode *inode, struct file *file) + { ++ struct winesync_device *dev = file->private_data; ++ struct winesync_obj *obj; ++ unsigned long id; ++ ++ xa_for_each(&dev->objects, id, obj) ++ put_obj(obj); ++ ++ xa_destroy(&dev->objects); ++ ++ kfree(dev); ++ ++ return 0; ++} ++ ++static void init_obj(struct winesync_obj *obj) ++{ ++ kref_init(&obj->refcount); ++} ++ ++static int winesync_create_sem(struct winesync_device *dev, void __user *argp) ++{ ++ struct winesync_sem_args __user *user_args = argp; ++ struct winesync_sem_args args; ++ struct winesync_obj *sem; ++ __u32 id; ++ int ret; ++ ++ if (copy_from_user(&args, argp, sizeof(args))) ++ return -EFAULT; ++ ++ if (args.count > args.max) ++ return -EINVAL; ++ ++ sem = kzalloc(sizeof(*sem), GFP_KERNEL); ++ if (!sem) ++ return -ENOMEM; ++ ++ init_obj(sem); ++ sem->type = WINESYNC_TYPE_SEM; ++ sem->u.sem.count = args.count; ++ sem->u.sem.max = args.max; ++ ++ ret = xa_alloc(&dev->objects, &id, sem, xa_limit_32b, GFP_KERNEL); ++ if (ret < 0) { ++ kfree(sem); ++ return ret; ++ } ++ ++ return put_user(id, &user_args->sem); ++} ++ ++static int winesync_delete(struct winesync_device *dev, void __user *argp) ++{ ++ struct winesync_obj *obj; ++ __u32 id; ++ ++ if (get_user(id, (__u32 __user *)argp)) ++ return -EFAULT; ++ ++ obj = xa_erase(&dev->objects, id); ++ if (!obj) ++ return -EINVAL; ++ ++ put_obj(obj); + return 0; + } + + static long winesync_char_ioctl(struct file *file, unsigned int cmd, + unsigned long parm) + { ++ struct winesync_device *dev = file->private_data; ++ void __user *argp = (void __user *)parm; ++ + switch (cmd) { ++ case WINESYNC_IOC_CREATE_SEM: ++ return winesync_create_sem(dev, argp); ++ case WINESYNC_IOC_DELETE: ++ return winesync_delete(dev, argp); + default: + return -ENOSYS; + } +diff --git a/include/uapi/linux/winesync.h b/include/uapi/linux/winesync.h +new file mode 100644 +index 000000000000..aabb491f39d2 +--- /dev/null ++++ b/include/uapi/linux/winesync.h +@@ -0,0 +1,25 @@ ++/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ ++/* ++ * Kernel support for Wine synchronization primitives ++ * ++ * Copyright (C) 2021 Zebediah Figura ++ */ ++ ++#ifndef __LINUX_WINESYNC_H ++#define __LINUX_WINESYNC_H ++ ++#include ++ ++struct winesync_sem_args { ++ __u32 sem; ++ __u32 count; ++ __u32 max; ++}; ++ ++#define WINESYNC_IOC_BASE 0xf7 ++ ++#define WINESYNC_IOC_CREATE_SEM _IOWR(WINESYNC_IOC_BASE, 0, \ ++ struct winesync_sem_args) ++#define WINESYNC_IOC_DELETE _IOW (WINESYNC_IOC_BASE, 1, __u32) ++ ++#endif +-- +2.36.0 + +From 144e223bfd7c5e733a9e7e50a3a8d37dbbedc0b7 Mon Sep 17 00:00:00 2001 +From: Zebediah Figura +Date: Fri, 5 Mar 2021 11:22:42 -0600 +Subject: [PATCH 04/34] winesync: Introduce WINESYNC_IOC_PUT_SEM. + +--- + drivers/misc/winesync.c | 76 +++++++++++++++++++++++++++++++++++ + include/uapi/linux/winesync.h | 2 + + 2 files changed, 78 insertions(+) + +diff --git a/drivers/misc/winesync.c b/drivers/misc/winesync.c +index 36e31bbe0390..84b5a5c9e0ce 100644 +--- a/drivers/misc/winesync.c ++++ b/drivers/misc/winesync.c +@@ -21,9 +21,11 @@ enum winesync_type { + struct winesync_obj { + struct rcu_head rhead; + struct kref refcount; ++ spinlock_t lock; + + enum winesync_type type; + ++ /* The following fields are protected by the object lock. */ + union { + struct { + __u32 count; +@@ -36,6 +38,19 @@ struct winesync_device { + struct xarray objects; + }; + ++static struct winesync_obj *get_obj(struct winesync_device *dev, __u32 id) ++{ ++ struct winesync_obj *obj; ++ ++ rcu_read_lock(); ++ obj = xa_load(&dev->objects, id); ++ if (obj && !kref_get_unless_zero(&obj->refcount)) ++ obj = NULL; ++ rcu_read_unlock(); ++ ++ return obj; ++} ++ + static void destroy_obj(struct kref *ref) + { + struct winesync_obj *obj = container_of(ref, struct winesync_obj, refcount); +@@ -48,6 +63,18 @@ static void put_obj(struct winesync_obj *obj) + kref_put(&obj->refcount, destroy_obj); + } + ++static struct winesync_obj *get_obj_typed(struct winesync_device *dev, __u32 id, ++ enum winesync_type type) ++{ ++ struct winesync_obj *obj = get_obj(dev, id); ++ ++ if (obj && obj->type != type) { ++ put_obj(obj); ++ return NULL; ++ } ++ return obj; ++} ++ + static int winesync_char_open(struct inode *inode, struct file *file) + { + struct winesync_device *dev; +@@ -81,6 +108,7 @@ static int winesync_char_release(struct inode *inode, struct file *file) + static void init_obj(struct winesync_obj *obj) + { + kref_init(&obj->refcount); ++ spin_lock_init(&obj->lock); + } + + static int winesync_create_sem(struct winesync_device *dev, void __user *argp) +@@ -131,6 +159,52 @@ static int winesync_delete(struct winesync_device *dev, void __user *argp) + return 0; + } + ++/* ++ * Actually change the semaphore state, returning -EOVERFLOW if it is made ++ * invalid. ++ */ ++static int put_sem_state(struct winesync_obj *sem, __u32 count) ++{ ++ lockdep_assert_held(&sem->lock); ++ ++ if (sem->u.sem.count + count < sem->u.sem.count || ++ sem->u.sem.count + count > sem->u.sem.max) ++ return -EOVERFLOW; ++ ++ sem->u.sem.count += count; ++ return 0; ++} ++ ++static int winesync_put_sem(struct winesync_device *dev, void __user *argp) ++{ ++ struct winesync_sem_args __user *user_args = argp; ++ struct winesync_sem_args args; ++ struct winesync_obj *sem; ++ __u32 prev_count; ++ int ret; ++ ++ if (copy_from_user(&args, argp, sizeof(args))) ++ return -EFAULT; ++ ++ sem = get_obj_typed(dev, args.sem, WINESYNC_TYPE_SEM); ++ if (!sem) ++ return -EINVAL; ++ ++ spin_lock(&sem->lock); ++ ++ prev_count = sem->u.sem.count; ++ ret = put_sem_state(sem, args.count); ++ ++ spin_unlock(&sem->lock); ++ ++ put_obj(sem); ++ ++ if (!ret && put_user(prev_count, &user_args->count)) ++ ret = -EFAULT; ++ ++ return ret; ++} ++ + static long winesync_char_ioctl(struct file *file, unsigned int cmd, + unsigned long parm) + { +@@ -142,6 +216,8 @@ static long winesync_char_ioctl(struct file *file, unsigned int cmd, + return winesync_create_sem(dev, argp); + case WINESYNC_IOC_DELETE: + return winesync_delete(dev, argp); ++ case WINESYNC_IOC_PUT_SEM: ++ return winesync_put_sem(dev, argp); + default: + return -ENOSYS; + } +diff --git a/include/uapi/linux/winesync.h b/include/uapi/linux/winesync.h +index aabb491f39d2..7681a168eb92 100644 +--- a/include/uapi/linux/winesync.h ++++ b/include/uapi/linux/winesync.h +@@ -21,5 +21,7 @@ struct winesync_sem_args { + #define WINESYNC_IOC_CREATE_SEM _IOWR(WINESYNC_IOC_BASE, 0, \ + struct winesync_sem_args) + #define WINESYNC_IOC_DELETE _IOW (WINESYNC_IOC_BASE, 1, __u32) ++#define WINESYNC_IOC_PUT_SEM _IOWR(WINESYNC_IOC_BASE, 2, \ ++ struct winesync_sem_args) + + #endif +-- +2.36.0 + +From 207daf2aa77f9d197b205a88322d5359f432bc67 Mon Sep 17 00:00:00 2001 +From: Zebediah Figura +Date: Fri, 5 Mar 2021 11:31:44 -0600 +Subject: [PATCH 05/34] winesync: Introduce WINESYNC_IOC_WAIT_ANY. + +--- + drivers/misc/winesync.c | 226 ++++++++++++++++++++++++++++++++++ + include/uapi/linux/winesync.h | 11 ++ + 2 files changed, 237 insertions(+) + +diff --git a/drivers/misc/winesync.c b/drivers/misc/winesync.c +index 84b5a5c9e0ce..d9b5ab159520 100644 +--- a/drivers/misc/winesync.c ++++ b/drivers/misc/winesync.c +@@ -23,6 +23,8 @@ struct winesync_obj { + struct kref refcount; + spinlock_t lock; + ++ struct list_head any_waiters; ++ + enum winesync_type type; + + /* The following fields are protected by the object lock. */ +@@ -34,6 +36,28 @@ struct winesync_obj { + } u; + }; + ++struct winesync_q_entry { ++ struct list_head node; ++ struct winesync_q *q; ++ struct winesync_obj *obj; ++ __u32 index; ++}; ++ ++struct winesync_q { ++ struct task_struct *task; ++ __u32 owner; ++ ++ /* ++ * Protected via atomic_cmpxchg(). Only the thread that wins the ++ * compare-and-swap may actually change object states and wake this ++ * task. ++ */ ++ atomic_t signaled; ++ ++ __u32 count; ++ struct winesync_q_entry entries[]; ++}; ++ + struct winesync_device { + struct xarray objects; + }; +@@ -109,6 +133,26 @@ static void init_obj(struct winesync_obj *obj) + { + kref_init(&obj->refcount); + spin_lock_init(&obj->lock); ++ INIT_LIST_HEAD(&obj->any_waiters); ++} ++ ++static void try_wake_any_sem(struct winesync_obj *sem) ++{ ++ struct winesync_q_entry *entry; ++ ++ lockdep_assert_held(&sem->lock); ++ ++ list_for_each_entry(entry, &sem->any_waiters, node) { ++ struct winesync_q *q = entry->q; ++ ++ if (!sem->u.sem.count) ++ break; ++ ++ if (atomic_cmpxchg(&q->signaled, -1, entry->index) == -1) { ++ sem->u.sem.count--; ++ wake_up_process(q->task); ++ } ++ } + } + + static int winesync_create_sem(struct winesync_device *dev, void __user *argp) +@@ -194,6 +238,8 @@ static int winesync_put_sem(struct winesync_device *dev, void __user *argp) + + prev_count = sem->u.sem.count; + ret = put_sem_state(sem, args.count); ++ if (!ret) ++ try_wake_any_sem(sem); + + spin_unlock(&sem->lock); + +@@ -205,6 +251,184 @@ static int winesync_put_sem(struct winesync_device *dev, void __user *argp) + return ret; + } + ++static int winesync_schedule(const struct winesync_q *q, ktime_t *timeout) ++{ ++ int ret = 0; ++ ++ do { ++ if (signal_pending(current)) { ++ ret = -ERESTARTSYS; ++ break; ++ } ++ ++ set_current_state(TASK_INTERRUPTIBLE); ++ if (atomic_read(&q->signaled) != -1) { ++ ret = 0; ++ break; ++ } ++ ret = schedule_hrtimeout(timeout, HRTIMER_MODE_ABS); ++ } while (ret < 0); ++ __set_current_state(TASK_RUNNING); ++ ++ return ret; ++} ++ ++/* ++ * Allocate and initialize the winesync_q structure, but do not queue us yet. ++ * Also, calculate the relative timeout. ++ */ ++static int setup_wait(struct winesync_device *dev, ++ const struct winesync_wait_args *args, ++ ktime_t *ret_timeout, struct winesync_q **ret_q) ++{ ++ const __u32 count = args->count; ++ struct winesync_q *q; ++ ktime_t timeout = 0; ++ __u32 *ids; ++ __u32 i, j; ++ ++ if (!args->owner || args->pad) ++ return -EINVAL; ++ ++ if (args->timeout) { ++ struct timespec64 to; ++ ++ if (get_timespec64(&to, u64_to_user_ptr(args->timeout))) ++ return -EFAULT; ++ if (!timespec64_valid(&to)) ++ return -EINVAL; ++ ++ timeout = timespec64_to_ns(&to); ++ } ++ ++ ids = kmalloc_array(count, sizeof(*ids), GFP_KERNEL); ++ if (!ids) ++ return -ENOMEM; ++ if (copy_from_user(ids, u64_to_user_ptr(args->objs), ++ array_size(count, sizeof(*ids)))) { ++ kfree(ids); ++ return -EFAULT; ++ } ++ ++ q = kmalloc(struct_size(q, entries, count), GFP_KERNEL); ++ if (!q) { ++ kfree(ids); ++ return -ENOMEM; ++ } ++ q->task = current; ++ q->owner = args->owner; ++ atomic_set(&q->signaled, -1); ++ q->count = count; ++ ++ for (i = 0; i < count; i++) { ++ struct winesync_q_entry *entry = &q->entries[i]; ++ struct winesync_obj *obj = get_obj(dev, ids[i]); ++ ++ if (!obj) ++ goto err; ++ ++ entry->obj = obj; ++ entry->q = q; ++ entry->index = i; ++ } ++ ++ kfree(ids); ++ ++ *ret_q = q; ++ *ret_timeout = timeout; ++ return 0; ++ ++err: ++ for (j = 0; j < i; j++) ++ put_obj(q->entries[j].obj); ++ kfree(ids); ++ kfree(q); ++ return -EINVAL; ++} ++ ++static void try_wake_any_obj(struct winesync_obj *obj) ++{ ++ switch (obj->type) { ++ case WINESYNC_TYPE_SEM: ++ try_wake_any_sem(obj); ++ break; ++ } ++} ++ ++static int winesync_wait_any(struct winesync_device *dev, void __user *argp) ++{ ++ struct winesync_wait_args args; ++ struct winesync_q *q; ++ ktime_t timeout; ++ int signaled; ++ __u32 i; ++ int ret; ++ ++ if (copy_from_user(&args, argp, sizeof(args))) ++ return -EFAULT; ++ ++ ret = setup_wait(dev, &args, &timeout, &q); ++ if (ret < 0) ++ return ret; ++ ++ /* queue ourselves */ ++ ++ for (i = 0; i < args.count; i++) { ++ struct winesync_q_entry *entry = &q->entries[i]; ++ struct winesync_obj *obj = entry->obj; ++ ++ spin_lock(&obj->lock); ++ list_add_tail(&entry->node, &obj->any_waiters); ++ spin_unlock(&obj->lock); ++ } ++ ++ /* check if we are already signaled */ ++ ++ for (i = 0; i < args.count; i++) { ++ struct winesync_obj *obj = q->entries[i].obj; ++ ++ if (atomic_read(&q->signaled) != -1) ++ break; ++ ++ spin_lock(&obj->lock); ++ try_wake_any_obj(obj); ++ spin_unlock(&obj->lock); ++ } ++ ++ /* sleep */ ++ ++ ret = winesync_schedule(q, args.timeout ? &timeout : NULL); ++ ++ /* and finally, unqueue */ ++ ++ for (i = 0; i < args.count; i++) { ++ struct winesync_q_entry *entry = &q->entries[i]; ++ struct winesync_obj *obj = entry->obj; ++ ++ spin_lock(&obj->lock); ++ list_del(&entry->node); ++ spin_unlock(&obj->lock); ++ ++ put_obj(obj); ++ } ++ ++ signaled = atomic_read(&q->signaled); ++ if (signaled != -1) { ++ struct winesync_wait_args __user *user_args = argp; ++ ++ /* even if we caught a signal, we need to communicate success */ ++ ret = 0; ++ ++ if (put_user(signaled, &user_args->index)) ++ ret = -EFAULT; ++ } else if (!ret) { ++ ret = -ETIMEDOUT; ++ } ++ ++ kfree(q); ++ return ret; ++} ++ + static long winesync_char_ioctl(struct file *file, unsigned int cmd, + unsigned long parm) + { +@@ -218,6 +442,8 @@ static long winesync_char_ioctl(struct file *file, unsigned int cmd, + return winesync_delete(dev, argp); + case WINESYNC_IOC_PUT_SEM: + return winesync_put_sem(dev, argp); ++ case WINESYNC_IOC_WAIT_ANY: ++ return winesync_wait_any(dev, argp); + default: + return -ENOSYS; + } +diff --git a/include/uapi/linux/winesync.h b/include/uapi/linux/winesync.h +index 7681a168eb92..f57ebfbe1dd9 100644 +--- a/include/uapi/linux/winesync.h ++++ b/include/uapi/linux/winesync.h +@@ -16,6 +16,15 @@ struct winesync_sem_args { + __u32 max; + }; + ++struct winesync_wait_args { ++ __u64 timeout; ++ __u64 objs; ++ __u32 count; ++ __u32 owner; ++ __u32 index; ++ __u32 pad; ++}; ++ + #define WINESYNC_IOC_BASE 0xf7 + + #define WINESYNC_IOC_CREATE_SEM _IOWR(WINESYNC_IOC_BASE, 0, \ +@@ -23,5 +32,7 @@ struct winesync_sem_args { + #define WINESYNC_IOC_DELETE _IOW (WINESYNC_IOC_BASE, 1, __u32) + #define WINESYNC_IOC_PUT_SEM _IOWR(WINESYNC_IOC_BASE, 2, \ + struct winesync_sem_args) ++#define WINESYNC_IOC_WAIT_ANY _IOWR(WINESYNC_IOC_BASE, 3, \ ++ struct winesync_wait_args) + + #endif +-- +2.36.0 + +From 3d68ffb91767194d5a1a07aa6c57849343530a15 Mon Sep 17 00:00:00 2001 +From: Zebediah Figura +Date: Fri, 5 Mar 2021 11:36:09 -0600 +Subject: [PATCH 06/34] winesync: Introduce WINESYNC_IOC_WAIT_ALL. + +--- + drivers/misc/winesync.c | 242 ++++++++++++++++++++++++++++++++-- + include/uapi/linux/winesync.h | 2 + + 2 files changed, 236 insertions(+), 8 deletions(-) + +diff --git a/drivers/misc/winesync.c b/drivers/misc/winesync.c +index d9b5ab159520..2b708c5b88a6 100644 +--- a/drivers/misc/winesync.c ++++ b/drivers/misc/winesync.c +@@ -23,7 +23,34 @@ struct winesync_obj { + struct kref refcount; + spinlock_t lock; + ++ /* ++ * any_waiters is protected by the object lock, but all_waiters is ++ * protected by the device wait_all_lock. ++ */ + struct list_head any_waiters; ++ struct list_head all_waiters; ++ ++ /* ++ * Hint describing how many tasks are queued on this object in a ++ * wait-all operation. ++ * ++ * Any time we do a wake, we may need to wake "all" waiters as well as ++ * "any" waiters. In order to atomically wake "all" waiters, we must ++ * lock all of the objects, and that means grabbing the wait_all_lock ++ * below (and, due to lock ordering rules, before locking this object). ++ * However, wait-all is a rare operation, and grabbing the wait-all ++ * lock for every wake would create unnecessary contention. Therefore we ++ * first check whether all_hint is zero, and, if it is, we skip trying ++ * to wake "all" waiters. ++ * ++ * This hint isn't protected by any lock. It might change during the ++ * course of a wake, but there's no meaningful race there; it's only a ++ * hint. ++ * ++ * Since wait requests must originate from user-space threads, we're ++ * limited here by PID_MAX_LIMIT, so there's no risk of saturation. ++ */ ++ atomic_t all_hint; + + enum winesync_type type; + +@@ -54,11 +81,25 @@ struct winesync_q { + */ + atomic_t signaled; + ++ bool all; + __u32 count; + struct winesync_q_entry entries[]; + }; + + struct winesync_device { ++ /* ++ * Wait-all operations must atomically grab all objects, and be totally ++ * ordered with respect to each other and wait-any operations. If one ++ * thread is trying to acquire several objects, another thread cannot ++ * touch the object at the same time. ++ * ++ * We achieve this by grabbing multiple object locks at the same time. ++ * However, this creates a lock ordering problem. To solve that problem, ++ * wait_all_lock is taken first whenever multiple objects must be locked ++ * at the same time. ++ */ ++ spinlock_t wait_all_lock; ++ + struct xarray objects; + }; + +@@ -107,6 +148,8 @@ static int winesync_char_open(struct inode *inode, struct file *file) + if (!dev) + return -ENOMEM; + ++ spin_lock_init(&dev->wait_all_lock); ++ + xa_init_flags(&dev->objects, XA_FLAGS_ALLOC); + + file->private_data = dev; +@@ -132,8 +175,82 @@ static int winesync_char_release(struct inode *inode, struct file *file) + static void init_obj(struct winesync_obj *obj) + { + kref_init(&obj->refcount); ++ atomic_set(&obj->all_hint, 0); + spin_lock_init(&obj->lock); + INIT_LIST_HEAD(&obj->any_waiters); ++ INIT_LIST_HEAD(&obj->all_waiters); ++} ++ ++static bool is_signaled(struct winesync_obj *obj, __u32 owner) ++{ ++ lockdep_assert_held(&obj->lock); ++ ++ switch (obj->type) { ++ case WINESYNC_TYPE_SEM: ++ return !!obj->u.sem.count; ++ } ++ ++ WARN(1, "bad object type %#x\n", obj->type); ++ return false; ++} ++ ++/* ++ * "locked_obj" is an optional pointer to an object which is already locked and ++ * should not be locked again. This is necessary so that changing an object's ++ * state and waking it can be a single atomic operation. ++ */ ++static void try_wake_all(struct winesync_device *dev, struct winesync_q *q, ++ struct winesync_obj *locked_obj) ++{ ++ __u32 count = q->count; ++ bool can_wake = true; ++ __u32 i; ++ ++ lockdep_assert_held(&dev->wait_all_lock); ++ if (locked_obj) ++ lockdep_assert_held(&locked_obj->lock); ++ ++ for (i = 0; i < count; i++) { ++ if (q->entries[i].obj != locked_obj) ++ spin_lock(&q->entries[i].obj->lock); ++ } ++ ++ for (i = 0; i < count; i++) { ++ if (!is_signaled(q->entries[i].obj, q->owner)) { ++ can_wake = false; ++ break; ++ } ++ } ++ ++ if (can_wake && atomic_cmpxchg(&q->signaled, -1, 0) == -1) { ++ for (i = 0; i < count; i++) { ++ struct winesync_obj *obj = q->entries[i].obj; ++ ++ switch (obj->type) { ++ case WINESYNC_TYPE_SEM: ++ obj->u.sem.count--; ++ break; ++ } ++ } ++ wake_up_process(q->task); ++ } ++ ++ for (i = 0; i < count; i++) { ++ if (q->entries[i].obj != locked_obj) ++ spin_unlock(&q->entries[i].obj->lock); ++ } ++} ++ ++static void try_wake_all_obj(struct winesync_device *dev, ++ struct winesync_obj *obj) ++{ ++ struct winesync_q_entry *entry; ++ ++ lockdep_assert_held(&dev->wait_all_lock); ++ lockdep_assert_held(&obj->lock); ++ ++ list_for_each_entry(entry, &obj->all_waiters, node) ++ try_wake_all(dev, entry->q, obj); + } + + static void try_wake_any_sem(struct winesync_obj *sem) +@@ -234,14 +351,29 @@ static int winesync_put_sem(struct winesync_device *dev, void __user *argp) + if (!sem) + return -EINVAL; + +- spin_lock(&sem->lock); ++ if (atomic_read(&sem->all_hint) > 0) { ++ spin_lock(&dev->wait_all_lock); ++ spin_lock(&sem->lock); ++ ++ prev_count = sem->u.sem.count; ++ ret = put_sem_state(sem, args.count); ++ if (!ret) { ++ try_wake_all_obj(dev, sem); ++ try_wake_any_sem(sem); ++ } + +- prev_count = sem->u.sem.count; +- ret = put_sem_state(sem, args.count); +- if (!ret) +- try_wake_any_sem(sem); ++ spin_unlock(&sem->lock); ++ spin_unlock(&dev->wait_all_lock); ++ } else { ++ spin_lock(&sem->lock); + +- spin_unlock(&sem->lock); ++ prev_count = sem->u.sem.count; ++ ret = put_sem_state(sem, args.count); ++ if (!ret) ++ try_wake_any_sem(sem); ++ ++ spin_unlock(&sem->lock); ++ } + + put_obj(sem); + +@@ -278,7 +410,7 @@ static int winesync_schedule(const struct winesync_q *q, ktime_t *timeout) + * Also, calculate the relative timeout. + */ + static int setup_wait(struct winesync_device *dev, +- const struct winesync_wait_args *args, ++ const struct winesync_wait_args *args, bool all, + ktime_t *ret_timeout, struct winesync_q **ret_q) + { + const __u32 count = args->count; +@@ -318,6 +450,7 @@ static int setup_wait(struct winesync_device *dev, + q->task = current; + q->owner = args->owner; + atomic_set(&q->signaled, -1); ++ q->all = all; + q->count = count; + + for (i = 0; i < count; i++) { +@@ -327,6 +460,16 @@ static int setup_wait(struct winesync_device *dev, + if (!obj) + goto err; + ++ if (all) { ++ /* Check that the objects are all distinct. */ ++ for (j = 0; j < i; j++) { ++ if (obj == q->entries[j].obj) { ++ put_obj(obj); ++ goto err; ++ } ++ } ++ } ++ + entry->obj = obj; + entry->q = q; + entry->index = i; +@@ -367,7 +510,7 @@ static int winesync_wait_any(struct winesync_device *dev, void __user *argp) + if (copy_from_user(&args, argp, sizeof(args))) + return -EFAULT; + +- ret = setup_wait(dev, &args, &timeout, &q); ++ ret = setup_wait(dev, &args, false, &timeout, &q); + if (ret < 0) + return ret; + +@@ -429,6 +572,87 @@ static int winesync_wait_any(struct winesync_device *dev, void __user *argp) + return ret; + } + ++static int winesync_wait_all(struct winesync_device *dev, void __user *argp) ++{ ++ struct winesync_wait_args args; ++ struct winesync_q *q; ++ ktime_t timeout; ++ int signaled; ++ __u32 i; ++ int ret; ++ ++ if (copy_from_user(&args, argp, sizeof(args))) ++ return -EFAULT; ++ ++ ret = setup_wait(dev, &args, true, &timeout, &q); ++ if (ret < 0) ++ return ret; ++ ++ /* queue ourselves */ ++ ++ spin_lock(&dev->wait_all_lock); ++ ++ for (i = 0; i < args.count; i++) { ++ struct winesync_q_entry *entry = &q->entries[i]; ++ struct winesync_obj *obj = entry->obj; ++ ++ atomic_inc(&obj->all_hint); ++ ++ /* ++ * obj->all_waiters is protected by dev->wait_all_lock rather ++ * than obj->lock, so there is no need to acquire it here. ++ */ ++ list_add_tail(&entry->node, &obj->all_waiters); ++ } ++ ++ /* check if we are already signaled */ ++ ++ try_wake_all(dev, q, NULL); ++ ++ spin_unlock(&dev->wait_all_lock); ++ ++ /* sleep */ ++ ++ ret = winesync_schedule(q, args.timeout ? &timeout : NULL); ++ ++ /* and finally, unqueue */ ++ ++ spin_lock(&dev->wait_all_lock); ++ ++ for (i = 0; i < args.count; i++) { ++ struct winesync_q_entry *entry = &q->entries[i]; ++ struct winesync_obj *obj = entry->obj; ++ ++ /* ++ * obj->all_waiters is protected by dev->wait_all_lock rather ++ * than obj->lock, so there is no need to acquire it here. ++ */ ++ list_del(&entry->node); ++ ++ atomic_dec(&obj->all_hint); ++ ++ put_obj(obj); ++ } ++ ++ spin_unlock(&dev->wait_all_lock); ++ ++ signaled = atomic_read(&q->signaled); ++ if (signaled != -1) { ++ struct winesync_wait_args __user *user_args = argp; ++ ++ /* even if we caught a signal, we need to communicate success */ ++ ret = 0; ++ ++ if (put_user(signaled, &user_args->index)) ++ ret = -EFAULT; ++ } else if (!ret) { ++ ret = -ETIMEDOUT; ++ } ++ ++ kfree(q); ++ return ret; ++} ++ + static long winesync_char_ioctl(struct file *file, unsigned int cmd, + unsigned long parm) + { +@@ -442,6 +666,8 @@ static long winesync_char_ioctl(struct file *file, unsigned int cmd, + return winesync_delete(dev, argp); + case WINESYNC_IOC_PUT_SEM: + return winesync_put_sem(dev, argp); ++ case WINESYNC_IOC_WAIT_ALL: ++ return winesync_wait_all(dev, argp); + case WINESYNC_IOC_WAIT_ANY: + return winesync_wait_any(dev, argp); + default: +diff --git a/include/uapi/linux/winesync.h b/include/uapi/linux/winesync.h +index f57ebfbe1dd9..44025a510cb9 100644 +--- a/include/uapi/linux/winesync.h ++++ b/include/uapi/linux/winesync.h +@@ -34,5 +34,7 @@ struct winesync_wait_args { + struct winesync_sem_args) + #define WINESYNC_IOC_WAIT_ANY _IOWR(WINESYNC_IOC_BASE, 3, \ + struct winesync_wait_args) ++#define WINESYNC_IOC_WAIT_ALL _IOWR(WINESYNC_IOC_BASE, 4, \ ++ struct winesync_wait_args) + + #endif +-- +2.36.0 + +From 2838a60302cd26a2ab92a143749e455edebe7b7c Mon Sep 17 00:00:00 2001 +From: Zebediah Figura +Date: Fri, 5 Mar 2021 11:41:10 -0600 +Subject: [PATCH 07/34] winesync: Introduce WINESYNC_IOC_CREATE_MUTEX. + +--- + drivers/misc/winesync.c | 72 +++++++++++++++++++++++++++++++++++ + include/uapi/linux/winesync.h | 8 ++++ + 2 files changed, 80 insertions(+) + +diff --git a/drivers/misc/winesync.c b/drivers/misc/winesync.c +index 2b708c5b88a6..18eb05975907 100644 +--- a/drivers/misc/winesync.c ++++ b/drivers/misc/winesync.c +@@ -16,6 +16,7 @@ + + enum winesync_type { + WINESYNC_TYPE_SEM, ++ WINESYNC_TYPE_MUTEX, + }; + + struct winesync_obj { +@@ -60,6 +61,10 @@ struct winesync_obj { + __u32 count; + __u32 max; + } sem; ++ struct { ++ __u32 count; ++ __u32 owner; ++ } mutex; + } u; + }; + +@@ -188,6 +193,10 @@ static bool is_signaled(struct winesync_obj *obj, __u32 owner) + switch (obj->type) { + case WINESYNC_TYPE_SEM: + return !!obj->u.sem.count; ++ case WINESYNC_TYPE_MUTEX: ++ if (obj->u.mutex.owner && obj->u.mutex.owner != owner) ++ return false; ++ return obj->u.mutex.count < UINT_MAX; + } + + WARN(1, "bad object type %#x\n", obj->type); +@@ -230,6 +239,10 @@ static void try_wake_all(struct winesync_device *dev, struct winesync_q *q, + case WINESYNC_TYPE_SEM: + obj->u.sem.count--; + break; ++ case WINESYNC_TYPE_MUTEX: ++ obj->u.mutex.count++; ++ obj->u.mutex.owner = q->owner; ++ break; + } + } + wake_up_process(q->task); +@@ -272,6 +285,28 @@ static void try_wake_any_sem(struct winesync_obj *sem) + } + } + ++static void try_wake_any_mutex(struct winesync_obj *mutex) ++{ ++ struct winesync_q_entry *entry; ++ ++ lockdep_assert_held(&mutex->lock); ++ ++ list_for_each_entry(entry, &mutex->any_waiters, node) { ++ struct winesync_q *q = entry->q; ++ ++ if (mutex->u.mutex.count == UINT_MAX) ++ break; ++ if (mutex->u.mutex.owner && mutex->u.mutex.owner != q->owner) ++ continue; ++ ++ if (atomic_cmpxchg(&q->signaled, -1, entry->index) == -1) { ++ mutex->u.mutex.count++; ++ mutex->u.mutex.owner = q->owner; ++ wake_up_process(q->task); ++ } ++ } ++} ++ + static int winesync_create_sem(struct winesync_device *dev, void __user *argp) + { + struct winesync_sem_args __user *user_args = argp; +@@ -304,6 +339,38 @@ static int winesync_create_sem(struct winesync_device *dev, void __user *argp) + return put_user(id, &user_args->sem); + } + ++static int winesync_create_mutex(struct winesync_device *dev, void __user *argp) ++{ ++ struct winesync_mutex_args __user *user_args = argp; ++ struct winesync_mutex_args args; ++ struct winesync_obj *mutex; ++ __u32 id; ++ int ret; ++ ++ if (copy_from_user(&args, argp, sizeof(args))) ++ return -EFAULT; ++ ++ if (!args.owner != !args.count) ++ return -EINVAL; ++ ++ mutex = kzalloc(sizeof(*mutex), GFP_KERNEL); ++ if (!mutex) ++ return -ENOMEM; ++ ++ init_obj(mutex); ++ mutex->type = WINESYNC_TYPE_MUTEX; ++ mutex->u.mutex.count = args.count; ++ mutex->u.mutex.owner = args.owner; ++ ++ ret = xa_alloc(&dev->objects, &id, mutex, xa_limit_32b, GFP_KERNEL); ++ if (ret < 0) { ++ kfree(mutex); ++ return ret; ++ } ++ ++ return put_user(id, &user_args->mutex); ++} ++ + static int winesync_delete(struct winesync_device *dev, void __user *argp) + { + struct winesync_obj *obj; +@@ -495,6 +562,9 @@ static void try_wake_any_obj(struct winesync_obj *obj) + case WINESYNC_TYPE_SEM: + try_wake_any_sem(obj); + break; ++ case WINESYNC_TYPE_MUTEX: ++ try_wake_any_mutex(obj); ++ break; + } + } + +@@ -660,6 +730,8 @@ static long winesync_char_ioctl(struct file *file, unsigned int cmd, + void __user *argp = (void __user *)parm; + + switch (cmd) { ++ case WINESYNC_IOC_CREATE_MUTEX: ++ return winesync_create_mutex(dev, argp); + case WINESYNC_IOC_CREATE_SEM: + return winesync_create_sem(dev, argp); + case WINESYNC_IOC_DELETE: +diff --git a/include/uapi/linux/winesync.h b/include/uapi/linux/winesync.h +index 44025a510cb9..23606a3b1546 100644 +--- a/include/uapi/linux/winesync.h ++++ b/include/uapi/linux/winesync.h +@@ -16,6 +16,12 @@ struct winesync_sem_args { + __u32 max; + }; + ++struct winesync_mutex_args { ++ __u32 mutex; ++ __u32 owner; ++ __u32 count; ++}; ++ + struct winesync_wait_args { + __u64 timeout; + __u64 objs; +@@ -36,5 +42,7 @@ struct winesync_wait_args { + struct winesync_wait_args) + #define WINESYNC_IOC_WAIT_ALL _IOWR(WINESYNC_IOC_BASE, 4, \ + struct winesync_wait_args) ++#define WINESYNC_IOC_CREATE_MUTEX _IOWR(WINESYNC_IOC_BASE, 5, \ ++ struct winesync_mutex_args) + + #endif +-- +2.36.0 + +From 25b9628ad91377840cdc2b08dd53e1539ad05bdd Mon Sep 17 00:00:00 2001 +From: Zebediah Figura +Date: Fri, 5 Mar 2021 11:44:41 -0600 +Subject: [PATCH 08/34] winesync: Introduce WINESYNC_IOC_PUT_MUTEX. + +--- + drivers/misc/winesync.c | 67 +++++++++++++++++++++++++++++++++++ + include/uapi/linux/winesync.h | 2 ++ + 2 files changed, 69 insertions(+) + +diff --git a/drivers/misc/winesync.c b/drivers/misc/winesync.c +index 18eb05975907..d18d08a68546 100644 +--- a/drivers/misc/winesync.c ++++ b/drivers/misc/winesync.c +@@ -450,6 +450,71 @@ static int winesync_put_sem(struct winesync_device *dev, void __user *argp) + return ret; + } + ++/* ++ * Actually change the mutex state, returning -EPERM if not the owner. ++ */ ++static int put_mutex_state(struct winesync_obj *mutex, ++ const struct winesync_mutex_args *args) ++{ ++ lockdep_assert_held(&mutex->lock); ++ ++ if (mutex->u.mutex.owner != args->owner) ++ return -EPERM; ++ ++ if (!--mutex->u.mutex.count) ++ mutex->u.mutex.owner = 0; ++ return 0; ++} ++ ++static int winesync_put_mutex(struct winesync_device *dev, void __user *argp) ++{ ++ struct winesync_mutex_args __user *user_args = argp; ++ struct winesync_mutex_args args; ++ struct winesync_obj *mutex; ++ __u32 prev_count; ++ int ret; ++ ++ if (copy_from_user(&args, argp, sizeof(args))) ++ return -EFAULT; ++ if (!args.owner) ++ return -EINVAL; ++ ++ mutex = get_obj_typed(dev, args.mutex, WINESYNC_TYPE_MUTEX); ++ if (!mutex) ++ return -EINVAL; ++ ++ if (atomic_read(&mutex->all_hint) > 0) { ++ spin_lock(&dev->wait_all_lock); ++ spin_lock(&mutex->lock); ++ ++ prev_count = mutex->u.mutex.count; ++ ret = put_mutex_state(mutex, &args); ++ if (!ret) { ++ try_wake_all_obj(dev, mutex); ++ try_wake_any_mutex(mutex); ++ } ++ ++ spin_unlock(&mutex->lock); ++ spin_unlock(&dev->wait_all_lock); ++ } else { ++ spin_lock(&mutex->lock); ++ ++ prev_count = mutex->u.mutex.count; ++ ret = put_mutex_state(mutex, &args); ++ if (!ret) ++ try_wake_any_mutex(mutex); ++ ++ spin_unlock(&mutex->lock); ++ } ++ ++ put_obj(mutex); ++ ++ if (!ret && put_user(prev_count, &user_args->count)) ++ ret = -EFAULT; ++ ++ return ret; ++} ++ + static int winesync_schedule(const struct winesync_q *q, ktime_t *timeout) + { + int ret = 0; +@@ -736,6 +801,8 @@ static long winesync_char_ioctl(struct file *file, unsigned int cmd, + return winesync_create_sem(dev, argp); + case WINESYNC_IOC_DELETE: + return winesync_delete(dev, argp); ++ case WINESYNC_IOC_PUT_MUTEX: ++ return winesync_put_mutex(dev, argp); + case WINESYNC_IOC_PUT_SEM: + return winesync_put_sem(dev, argp); + case WINESYNC_IOC_WAIT_ALL: +diff --git a/include/uapi/linux/winesync.h b/include/uapi/linux/winesync.h +index 23606a3b1546..fde08cb8ab95 100644 +--- a/include/uapi/linux/winesync.h ++++ b/include/uapi/linux/winesync.h +@@ -44,5 +44,7 @@ struct winesync_wait_args { + struct winesync_wait_args) + #define WINESYNC_IOC_CREATE_MUTEX _IOWR(WINESYNC_IOC_BASE, 5, \ + struct winesync_mutex_args) ++#define WINESYNC_IOC_PUT_MUTEX _IOWR(WINESYNC_IOC_BASE, 6, \ ++ struct winesync_mutex_args) + + #endif +-- +2.36.0 + +From 97d6dc0155da6609849e6a03bcc9e7d7e0cb58f5 Mon Sep 17 00:00:00 2001 +From: Zebediah Figura +Date: Fri, 5 Mar 2021 11:46:46 -0600 +Subject: [PATCH 09/34] winesync: Introduce WINESYNC_IOC_KILL_OWNER. + +--- + drivers/misc/winesync.c | 80 ++++++++++++++++++++++++++++++++++- + include/uapi/linux/winesync.h | 1 + + 2 files changed, 79 insertions(+), 2 deletions(-) + +diff --git a/drivers/misc/winesync.c b/drivers/misc/winesync.c +index d18d08a68546..891537063bb6 100644 +--- a/drivers/misc/winesync.c ++++ b/drivers/misc/winesync.c +@@ -64,6 +64,7 @@ struct winesync_obj { + struct { + __u32 count; + __u32 owner; ++ bool ownerdead; + } mutex; + } u; + }; +@@ -87,6 +88,7 @@ struct winesync_q { + atomic_t signaled; + + bool all; ++ bool ownerdead; + __u32 count; + struct winesync_q_entry entries[]; + }; +@@ -240,6 +242,9 @@ static void try_wake_all(struct winesync_device *dev, struct winesync_q *q, + obj->u.sem.count--; + break; + case WINESYNC_TYPE_MUTEX: ++ if (obj->u.mutex.ownerdead) ++ q->ownerdead = true; ++ obj->u.mutex.ownerdead = false; + obj->u.mutex.count++; + obj->u.mutex.owner = q->owner; + break; +@@ -300,6 +305,9 @@ static void try_wake_any_mutex(struct winesync_obj *mutex) + continue; + + if (atomic_cmpxchg(&q->signaled, -1, entry->index) == -1) { ++ if (mutex->u.mutex.ownerdead) ++ q->ownerdead = true; ++ mutex->u.mutex.ownerdead = false; + mutex->u.mutex.count++; + mutex->u.mutex.owner = q->owner; + wake_up_process(q->task); +@@ -515,6 +523,71 @@ static int winesync_put_mutex(struct winesync_device *dev, void __user *argp) + return ret; + } + ++/* ++ * Actually change the mutex state to mark its owner as dead. ++ */ ++static void put_mutex_ownerdead_state(struct winesync_obj *mutex) ++{ ++ lockdep_assert_held(&mutex->lock); ++ ++ mutex->u.mutex.ownerdead = true; ++ mutex->u.mutex.owner = 0; ++ mutex->u.mutex.count = 0; ++} ++ ++static int winesync_kill_owner(struct winesync_device *dev, void __user *argp) ++{ ++ struct winesync_obj *obj; ++ unsigned long id; ++ __u32 owner; ++ ++ if (get_user(owner, (__u32 __user *)argp)) ++ return -EFAULT; ++ if (!owner) ++ return -EINVAL; ++ ++ rcu_read_lock(); ++ ++ xa_for_each(&dev->objects, id, obj) { ++ if (!kref_get_unless_zero(&obj->refcount)) ++ continue; ++ ++ if (obj->type != WINESYNC_TYPE_MUTEX) { ++ put_obj(obj); ++ continue; ++ } ++ ++ if (atomic_read(&obj->all_hint) > 0) { ++ spin_lock(&dev->wait_all_lock); ++ spin_lock(&obj->lock); ++ ++ if (obj->u.mutex.owner == owner) { ++ put_mutex_ownerdead_state(obj); ++ try_wake_all_obj(dev, obj); ++ try_wake_any_mutex(obj); ++ } ++ ++ spin_unlock(&obj->lock); ++ spin_unlock(&dev->wait_all_lock); ++ } else { ++ spin_lock(&obj->lock); ++ ++ if (obj->u.mutex.owner == owner) { ++ put_mutex_ownerdead_state(obj); ++ try_wake_any_mutex(obj); ++ } ++ ++ spin_unlock(&obj->lock); ++ } ++ ++ put_obj(obj); ++ } ++ ++ rcu_read_unlock(); ++ ++ return 0; ++} ++ + static int winesync_schedule(const struct winesync_q *q, ktime_t *timeout) + { + int ret = 0; +@@ -583,6 +656,7 @@ static int setup_wait(struct winesync_device *dev, + q->owner = args->owner; + atomic_set(&q->signaled, -1); + q->all = all; ++ q->ownerdead = false; + q->count = count; + + for (i = 0; i < count; i++) { +@@ -695,7 +769,7 @@ static int winesync_wait_any(struct winesync_device *dev, void __user *argp) + struct winesync_wait_args __user *user_args = argp; + + /* even if we caught a signal, we need to communicate success */ +- ret = 0; ++ ret = q->ownerdead ? -EOWNERDEAD : 0; + + if (put_user(signaled, &user_args->index)) + ret = -EFAULT; +@@ -776,7 +850,7 @@ static int winesync_wait_all(struct winesync_device *dev, void __user *argp) + struct winesync_wait_args __user *user_args = argp; + + /* even if we caught a signal, we need to communicate success */ +- ret = 0; ++ ret = q->ownerdead ? -EOWNERDEAD : 0; + + if (put_user(signaled, &user_args->index)) + ret = -EFAULT; +@@ -801,6 +875,8 @@ static long winesync_char_ioctl(struct file *file, unsigned int cmd, + return winesync_create_sem(dev, argp); + case WINESYNC_IOC_DELETE: + return winesync_delete(dev, argp); ++ case WINESYNC_IOC_KILL_OWNER: ++ return winesync_kill_owner(dev, argp); + case WINESYNC_IOC_PUT_MUTEX: + return winesync_put_mutex(dev, argp); + case WINESYNC_IOC_PUT_SEM: +diff --git a/include/uapi/linux/winesync.h b/include/uapi/linux/winesync.h +index fde08cb8ab95..f57aa76d57f5 100644 +--- a/include/uapi/linux/winesync.h ++++ b/include/uapi/linux/winesync.h +@@ -46,5 +46,6 @@ struct winesync_wait_args { + struct winesync_mutex_args) + #define WINESYNC_IOC_PUT_MUTEX _IOWR(WINESYNC_IOC_BASE, 6, \ + struct winesync_mutex_args) ++#define WINESYNC_IOC_KILL_OWNER _IOW (WINESYNC_IOC_BASE, 7, __u32) + + #endif +-- +2.36.0 + +From 888bb6fa10b7eb593db18a38fe696fc396ee30de Mon Sep 17 00:00:00 2001 +From: Zebediah Figura +Date: Fri, 5 Mar 2021 11:47:55 -0600 +Subject: [PATCH 10/34] winesync: Introduce WINESYNC_IOC_READ_SEM. + +--- + drivers/misc/winesync.c | 29 +++++++++++++++++++++++++++++ + include/uapi/linux/winesync.h | 2 ++ + 2 files changed, 31 insertions(+) + +diff --git a/drivers/misc/winesync.c b/drivers/misc/winesync.c +index 891537063bb6..98bedda2f8eb 100644 +--- a/drivers/misc/winesync.c ++++ b/drivers/misc/winesync.c +@@ -523,6 +523,33 @@ static int winesync_put_mutex(struct winesync_device *dev, void __user *argp) + return ret; + } + ++static int winesync_read_sem(struct winesync_device *dev, void __user *argp) ++{ ++ struct winesync_sem_args __user *user_args = argp; ++ struct winesync_sem_args args; ++ struct winesync_obj *sem; ++ __u32 id; ++ ++ if (get_user(id, &user_args->sem)) ++ return -EFAULT; ++ ++ sem = get_obj_typed(dev, id, WINESYNC_TYPE_SEM); ++ if (!sem) ++ return -EINVAL; ++ ++ args.sem = id; ++ spin_lock(&sem->lock); ++ args.count = sem->u.sem.count; ++ args.max = sem->u.sem.max; ++ spin_unlock(&sem->lock); ++ ++ put_obj(sem); ++ ++ if (copy_to_user(user_args, &args, sizeof(args))) ++ return -EFAULT; ++ return 0; ++} ++ + /* + * Actually change the mutex state to mark its owner as dead. + */ +@@ -881,6 +908,8 @@ static long winesync_char_ioctl(struct file *file, unsigned int cmd, + return winesync_put_mutex(dev, argp); + case WINESYNC_IOC_PUT_SEM: + return winesync_put_sem(dev, argp); ++ case WINESYNC_IOC_READ_SEM: ++ return winesync_read_sem(dev, argp); + case WINESYNC_IOC_WAIT_ALL: + return winesync_wait_all(dev, argp); + case WINESYNC_IOC_WAIT_ANY: +diff --git a/include/uapi/linux/winesync.h b/include/uapi/linux/winesync.h +index f57aa76d57f5..311eb810647d 100644 +--- a/include/uapi/linux/winesync.h ++++ b/include/uapi/linux/winesync.h +@@ -47,5 +47,7 @@ struct winesync_wait_args { + #define WINESYNC_IOC_PUT_MUTEX _IOWR(WINESYNC_IOC_BASE, 6, \ + struct winesync_mutex_args) + #define WINESYNC_IOC_KILL_OWNER _IOW (WINESYNC_IOC_BASE, 7, __u32) ++#define WINESYNC_IOC_READ_SEM _IOWR(WINESYNC_IOC_BASE, 8, \ ++ struct winesync_sem_args) + + #endif +-- +2.36.0 + +From 4f17c2ab7b9aca22fb00f7f16e0bd3cf70c44fe1 Mon Sep 17 00:00:00 2001 +From: Zebediah Figura +Date: Fri, 5 Mar 2021 11:48:10 -0600 +Subject: [PATCH 11/34] winesync: Introduce WINESYNC_IOC_READ_MUTEX. + +--- + drivers/misc/winesync.c | 31 +++++++++++++++++++++++++++++++ + include/uapi/linux/winesync.h | 2 ++ + 2 files changed, 33 insertions(+) + +diff --git a/drivers/misc/winesync.c b/drivers/misc/winesync.c +index 98bedda2f8eb..eae272663abe 100644 +--- a/drivers/misc/winesync.c ++++ b/drivers/misc/winesync.c +@@ -550,6 +550,35 @@ static int winesync_read_sem(struct winesync_device *dev, void __user *argp) + return 0; + } + ++static int winesync_read_mutex(struct winesync_device *dev, void __user *argp) ++{ ++ struct winesync_mutex_args __user *user_args = argp; ++ struct winesync_mutex_args args; ++ struct winesync_obj *mutex; ++ __u32 id; ++ int ret; ++ ++ if (get_user(id, &user_args->mutex)) ++ return -EFAULT; ++ ++ mutex = get_obj_typed(dev, id, WINESYNC_TYPE_MUTEX); ++ if (!mutex) ++ return -EINVAL; ++ ++ args.mutex = id; ++ spin_lock(&mutex->lock); ++ args.count = mutex->u.mutex.count; ++ args.owner = mutex->u.mutex.owner; ++ ret = mutex->u.mutex.ownerdead ? -EOWNERDEAD : 0; ++ spin_unlock(&mutex->lock); ++ ++ put_obj(mutex); ++ ++ if (copy_to_user(user_args, &args, sizeof(args))) ++ return -EFAULT; ++ return ret; ++} ++ + /* + * Actually change the mutex state to mark its owner as dead. + */ +@@ -908,6 +937,8 @@ static long winesync_char_ioctl(struct file *file, unsigned int cmd, + return winesync_put_mutex(dev, argp); + case WINESYNC_IOC_PUT_SEM: + return winesync_put_sem(dev, argp); ++ case WINESYNC_IOC_READ_MUTEX: ++ return winesync_read_mutex(dev, argp); + case WINESYNC_IOC_READ_SEM: + return winesync_read_sem(dev, argp); + case WINESYNC_IOC_WAIT_ALL: +diff --git a/include/uapi/linux/winesync.h b/include/uapi/linux/winesync.h +index 311eb810647d..3371a303a927 100644 +--- a/include/uapi/linux/winesync.h ++++ b/include/uapi/linux/winesync.h +@@ -49,5 +49,7 @@ struct winesync_wait_args { + #define WINESYNC_IOC_KILL_OWNER _IOW (WINESYNC_IOC_BASE, 7, __u32) + #define WINESYNC_IOC_READ_SEM _IOWR(WINESYNC_IOC_BASE, 8, \ + struct winesync_sem_args) ++#define WINESYNC_IOC_READ_MUTEX _IOWR(WINESYNC_IOC_BASE, 9, \ ++ struct winesync_mutex_args) + + #endif +-- +2.36.0 + +From e897f7ec5164d6d5d3d9881756be9a538c533487 Mon Sep 17 00:00:00 2001 +From: Zebediah Figura +Date: Fri, 5 Mar 2021 11:50:49 -0600 +Subject: [PATCH 12/34] docs: winesync: Add documentation for the winesync + uAPI. + +--- + Documentation/userspace-api/index.rst | 1 + + Documentation/userspace-api/winesync.rst | 324 +++++++++++++++++++++++ + 2 files changed, 325 insertions(+) + create mode 100644 Documentation/userspace-api/winesync.rst + +diff --git a/Documentation/userspace-api/index.rst b/Documentation/userspace-api/index.rst +index a61eac0c73f8..0bf697ddcb09 100644 +--- a/Documentation/userspace-api/index.rst ++++ b/Documentation/userspace-api/index.rst +@@ -29,6 +29,7 @@ place where this information is gathered. + sysfs-platform_profile + vduse + futex2 ++ winesync + + .. only:: subproject and html + +diff --git a/Documentation/userspace-api/winesync.rst b/Documentation/userspace-api/winesync.rst +new file mode 100644 +index 000000000000..34e54be229cf +--- /dev/null ++++ b/Documentation/userspace-api/winesync.rst +@@ -0,0 +1,324 @@ ++===================================== ++Wine synchronization primitive driver ++===================================== ++ ++This page documents the user-space API for the winesync driver. ++ ++winesync is a support driver for emulation of NT synchronization ++primitives by the Wine project or other NT emulators. It exists ++because implementation in user-space, using existing tools, cannot ++simultaneously satisfy performance, correctness, and security ++constraints. It is implemented entirely in software, and does not ++drive any hardware device. ++ ++This interface is meant as a compatibility tool only, and should not ++be used for general synchronization. Instead use generic, versatile ++interfaces such as futex(2) and poll(2). ++ ++Synchronization primitives ++========================== ++ ++The winesync driver exposes two types of synchronization primitives, ++semaphores and mutexes. ++ ++A semaphore holds a single volatile 32-bit counter, and a static ++32-bit integer denoting the maximum value. It is considered signaled ++when the counter is nonzero. The counter is decremented by one when a ++wait is satisfied. Both the initial and maximum count are established ++when the semaphore is created. ++ ++A mutex holds a volatile 32-bit recursion count, and a volatile 32-bit ++identifier denoting its owner. A mutex is considered signaled when its ++owner is zero (indicating that it is not owned). The recursion count ++is incremented when a wait is satisfied, and ownership is set to the ++given identifier. ++ ++A mutex also holds an internal flag denoting whether its previous ++owner has died; such a mutex is said to be inconsistent. Owner death ++is not tracked automatically based on thread death, but rather must be ++communicated using ``WINESYNC_IOC_KILL_OWNER``. An inconsistent mutex ++is inherently considered unowned. ++ ++Except for the "unowned" semantics of zero, the actual value of the ++owner identifier is not interpreted by the winesync driver at all. The ++intended use is to store a thread identifier; however, the winesync ++driver does not actually validate that a calling thread provides ++consistent or unique identifiers. ++ ++Unless specified otherwise, all operations on an object are atomic and ++totally ordered with respect to other operations on the same object. ++ ++Objects are represented by unsigned 32-bit integers. ++ ++Char device ++=========== ++ ++The winesync driver creates a single char device /dev/winesync. Each ++file description opened on the device represents a unique namespace. ++That is, objects created on one open file description are shared ++across all its individual descriptors, but are not shared with other ++open() calls on the same device. The same file description may be ++shared across multiple processes. ++ ++ioctl reference ++=============== ++ ++All operations on the device are done through ioctls. There are three ++structures used in ioctl calls:: ++ ++ struct winesync_sem_args { ++ __u32 sem; ++ __u32 count; ++ __u32 max; ++ }; ++ ++ struct winesync_mutex_args { ++ __u32 mutex; ++ __u32 owner; ++ __u32 count; ++ }; ++ ++ struct winesync_wait_args { ++ __u64 timeout; ++ __u64 objs; ++ __u32 count; ++ __u32 owner; ++ __u32 index; ++ __u32 pad; ++ }; ++ ++Depending on the ioctl, members of the structure may be used as input, ++output, or not at all. All ioctls return 0 on success. ++ ++The ioctls are as follows: ++ ++.. c:macro:: WINESYNC_IOC_CREATE_SEM ++ ++ Create a semaphore object. Takes a pointer to struct ++ :c:type:`winesync_sem_args`, which is used as follows: ++ ++ .. list-table:: ++ ++ * - ``sem`` ++ - On output, contains the identifier of the created semaphore. ++ * - ``count`` ++ - Initial count of the semaphore. ++ * - ``max`` ++ - Maximum count of the semaphore. ++ ++ Fails with ``EINVAL`` if ``count`` is greater than ``max``. ++ ++.. c:macro:: WINESYNC_IOC_CREATE_MUTEX ++ ++ Create a mutex object. Takes a pointer to struct ++ :c:type:`winesync_mutex_args`, which is used as follows: ++ ++ .. list-table:: ++ ++ * - ``mutex`` ++ - On output, contains the identifier of the created mutex. ++ * - ``count`` ++ - Initial recursion count of the mutex. ++ * - ``owner`` ++ - Initial owner of the mutex. ++ ++ If ``owner`` is nonzero and ``count`` is zero, or if ``owner`` is ++ zero and ``count`` is nonzero, the function fails with ``EINVAL``. ++ ++.. c:macro:: WINESYNC_IOC_DELETE ++ ++ Delete an object of any type. Takes an input-only pointer to a ++ 32-bit integer denoting the object to delete. ++ ++ Wait ioctls currently in progress are not interrupted, and behave as ++ if the object remains valid. ++ ++.. c:macro:: WINESYNC_IOC_PUT_SEM ++ ++ Post to a semaphore object. Takes a pointer to struct ++ :c:type:`winesync_sem_args`, which is used as follows: ++ ++ .. list-table:: ++ ++ * - ``sem`` ++ - Semaphore object to post to. ++ * - ``count`` ++ - Count to add to the semaphore. On output, contains the ++ previous count of the semaphore. ++ * - ``max`` ++ - Not used. ++ ++ If adding ``count`` to the semaphore's current count would raise the ++ latter past the semaphore's maximum count, the ioctl fails with ++ ``EOVERFLOW`` and the semaphore is not affected. If raising the ++ semaphore's count causes it to become signaled, eligible threads ++ waiting on this semaphore will be woken and the semaphore's count ++ decremented appropriately. ++ ++.. c:macro:: WINESYNC_IOC_PUT_MUTEX ++ ++ Release a mutex object. Takes a pointer to struct ++ :c:type:`winesync_mutex_args`, which is used as follows: ++ ++ .. list-table:: ++ ++ * - ``mutex`` ++ - Mutex object to release. ++ * - ``owner`` ++ - Mutex owner identifier. ++ * - ``count`` ++ - On output, contains the previous recursion count. ++ ++ If ``owner`` is zero, the ioctl fails with ``EINVAL``. If ``owner`` ++ is not the current owner of the mutex, the ioctl fails with ++ ``EPERM``. ++ ++ The mutex's count will be decremented by one. If decrementing the ++ mutex's count causes it to become zero, the mutex is marked as ++ unowned and signaled, and eligible threads waiting on it will be ++ woken as appropriate. ++ ++.. c:macro:: WINESYNC_IOC_READ_SEM ++ ++ Read the current state of a semaphore object. Takes a pointer to ++ struct :c:type:`winesync_sem_args`, which is used as follows: ++ ++ .. list-table:: ++ ++ * - ``sem`` ++ - Semaphore object to read. ++ * - ``count`` ++ - On output, contains the current count of the semaphore. ++ * - ``max`` ++ - On output, contains the maximum count of the semaphore. ++ ++.. c:macro:: WINESYNC_IOC_READ_MUTEX ++ ++ Read the current state of a mutex object. Takes a pointer to struct ++ :c:type:`winesync_mutex_args`, which is used as follows: ++ ++ .. list-table:: ++ ++ * - ``mutex`` ++ - Mutex object to read. ++ * - ``owner`` ++ - On output, contains the current owner of the mutex, or zero ++ if the mutex is not currently owned. ++ * - ``count`` ++ - On output, contains the current recursion count of the mutex. ++ ++ If the mutex is marked as inconsistent, the function fails with ++ ``EOWNERDEAD``. In this case, ``count`` and ``owner`` are set to ++ zero. ++ ++.. c:macro:: WINESYNC_IOC_KILL_OWNER ++ ++ Mark any mutexes owned by the given owner as unowned and ++ inconsistent. Takes an input-only pointer to a 32-bit integer ++ denoting the owner. If the owner is zero, the ioctl fails with ++ ``EINVAL``. ++ ++ For each mutex currently owned by the given owner, eligible threads ++ waiting on said mutex will be woken as appropriate (and such waits ++ will fail with ``EOWNERDEAD``, as described below). ++ ++ The operation as a whole is not atomic; however, the modification of ++ each mutex is atomic and totally ordered with respect to other ++ operations on the same mutex. ++ ++.. c:macro:: WINESYNC_IOC_WAIT_ANY ++ ++ Poll on any of a list of objects, atomically acquiring at most one. ++ Takes a pointer to struct :c:type:`winesync_wait_args`, which is ++ used as follows: ++ ++ .. list-table:: ++ ++ * - ``timeout`` ++ - Optional pointer to a 64-bit struct :c:type:`timespec` ++ (specified as an integer so that the structure has the same ++ size regardless of architecture). The timeout is specified in ++ absolute format, as measured against the MONOTONIC clock. If ++ the timeout is equal to or earlier than the current time, the ++ function returns immediately without sleeping. If ``timeout`` ++ is zero, i.e. NULL, the function will sleep until an object ++ is signaled, and will not fail with ``ETIMEDOUT``. ++ * - ``objs`` ++ - Pointer to an array of ``count`` 32-bit object identifiers ++ (specified as an integer so that the structure has the same ++ size regardless of architecture). If any identifier is ++ invalid, the function fails with ``EINVAL``. ++ * - ``count`` ++ - Number of object identifiers specified in the ``objs`` array. ++ * - ``owner`` ++ - Mutex owner identifier. If any object in ``objs`` is a mutex, ++ the ioctl will attempt to acquire that mutex on behalf of ++ ``owner``. If ``owner`` is zero, the ioctl fails with ++ ``EINVAL``. ++ * - ``index`` ++ - On success, contains the index (into ``objs``) of the object ++ which was signaled. ++ * - ``pad`` ++ - This field is not used and must be set to zero. ++ ++ This function attempts to acquire one of the given objects. If ++ unable to do so, it sleeps until an object becomes signaled, ++ subsequently acquiring it, or the timeout expires. In the latter ++ case the ioctl fails with ``ETIMEDOUT``. The function only acquires ++ one object, even if multiple objects are signaled. ++ ++ A semaphore is considered to be signaled if its count is nonzero, ++ and is acquired by decrementing its count by one. A mutex is ++ considered to be signaled if it is unowned or if its owner matches ++ the ``owner`` argument, and is acquired by incrementing its ++ recursion count by one and setting its owner to the ``owner`` ++ argument. ++ ++ Acquisition is atomic and totally ordered with respect to other ++ operations on the same object. If two wait operations (with ++ different ``owner`` identifiers) are queued on the same mutex, only ++ one is signaled. If two wait operations are queued on the same ++ semaphore, and a value of one is posted to it, only one is signaled. ++ The order in which threads are signaled is not specified. ++ ++ If an inconsistent mutex is acquired, the ioctl fails with ++ ``EOWNERDEAD``. Although this is a failure return, the function may ++ otherwise be considered successful. The mutex is marked as owned by ++ the given owner (with a recursion count of 1) and as no longer ++ inconsistent, and ``index`` is still set to the index of the mutex. ++ ++ It is valid to pass the same object more than once. If a wakeup ++ occurs due to that object being signaled, ``index`` is set to the ++ lowest index corresponding to that object. ++ ++ The function may fail with ``EINTR`` if a signal is received. ++ ++.. c:macro:: WINESYNC_IOC_WAIT_ALL ++ ++ Poll on a list of objects, atomically acquiring all of them. Takes a ++ pointer to struct :c:type:`winesync_wait_args`, which is used ++ identically to ``WINESYNC_IOC_WAIT_ANY``, except that ``index`` is ++ always filled with zero on success. ++ ++ This function attempts to simultaneously acquire all of the given ++ objects. If unable to do so, it sleeps until all objects become ++ simultaneously signaled, subsequently acquiring them, or the timeout ++ expires. In the latter case the ioctl fails with ``ETIMEDOUT`` and ++ no objects are modified. ++ ++ Objects may become signaled and subsequently designaled (through ++ acquisition by other threads) while this thread is sleeping. Only ++ once all objects are simultaneously signaled does the ioctl acquire ++ them and return. The entire acquisition is atomic and totally ++ ordered with respect to other operations on any of the given ++ objects. ++ ++ If an inconsistent mutex is acquired, the ioctl fails with ++ ``EOWNERDEAD``. Similarly to ``WINESYNC_IOC_WAIT_ANY``, all objects ++ are nevertheless marked as acquired. Note that if multiple mutex ++ objects are specified, there is no way to know which were marked as ++ inconsistent. ++ ++ Unlike ``WINESYNC_IOC_WAIT_ANY``, it is not valid to pass the same ++ object more than once. If this is attempted, the function fails with ++ ``EINVAL``. +-- +2.36.0 + +From 622699b7dd8d5390dccdd9be1159e93dee6815ac Mon Sep 17 00:00:00 2001 +From: Zebediah Figura +Date: Fri, 5 Mar 2021 12:06:23 -0600 +Subject: [PATCH 13/34] selftests: winesync: Add some tests for semaphore + state. + +--- + tools/testing/selftests/Makefile | 1 + + .../selftests/drivers/winesync/Makefile | 8 + + .../testing/selftests/drivers/winesync/config | 1 + + .../selftests/drivers/winesync/winesync.c | 153 ++++++++++++++++++ + 4 files changed, 163 insertions(+) + create mode 100644 tools/testing/selftests/drivers/winesync/Makefile + create mode 100644 tools/testing/selftests/drivers/winesync/config + create mode 100644 tools/testing/selftests/drivers/winesync/winesync.c + +diff --git a/tools/testing/selftests/Makefile b/tools/testing/selftests/Makefile +index c852eb40c4f7..a366016d6254 100644 +--- a/tools/testing/selftests/Makefile ++++ b/tools/testing/selftests/Makefile +@@ -18,6 +18,7 @@ + TARGETS += drivers/s390x/uvdevice + TARGETS += drivers/net/bonding + TARGETS += drivers/net/team ++TARGETS += drivers/winesync + TARGETS += efivarfs + TARGETS += exec + TARGETS += fchmodat2 +new file mode 100644 +index 000000000000..43b39fdeea10 +--- /dev/null ++++ b/tools/testing/selftests/drivers/winesync/Makefile +@@ -0,0 +1,8 @@ ++# SPDX-LICENSE-IDENTIFIER: GPL-2.0-only ++TEST_GEN_PROGS := winesync ++ ++top_srcdir =../../../../.. ++CFLAGS += -I$(top_srcdir)/usr/include ++LDLIBS += -lpthread ++ ++include ../../lib.mk +diff --git a/tools/testing/selftests/drivers/winesync/config b/tools/testing/selftests/drivers/winesync/config +new file mode 100644 +index 000000000000..60539c826d06 +--- /dev/null ++++ b/tools/testing/selftests/drivers/winesync/config +@@ -0,0 +1 @@ ++CONFIG_WINESYNC=y +diff --git a/tools/testing/selftests/drivers/winesync/winesync.c b/tools/testing/selftests/drivers/winesync/winesync.c +new file mode 100644 +index 000000000000..58ade297fef9 +--- /dev/null ++++ b/tools/testing/selftests/drivers/winesync/winesync.c +@@ -0,0 +1,153 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++/* ++ * Various unit tests for the "winesync" synchronization primitive driver. ++ * ++ * Copyright (C) 2021 Zebediah Figura ++ */ ++ ++#define _GNU_SOURCE ++#include ++#include ++#include ++#include ++#include ++#include ++#include "../../kselftest_harness.h" ++ ++static int read_sem_state(int fd, __u32 sem, __u32 *count, __u32 *max) ++{ ++ struct winesync_sem_args args; ++ int ret; ++ ++ args.sem = sem; ++ args.count = 0xdeadbeef; ++ args.max = 0xdeadbeef; ++ ret = ioctl(fd, WINESYNC_IOC_READ_SEM, &args); ++ *count = args.count; ++ *max = args.max; ++ return ret; ++} ++ ++#define check_sem_state(fd, sem, count, max) \ ++ ({ \ ++ __u32 __count, __max; \ ++ int ret = read_sem_state((fd), (sem), &__count, &__max); \ ++ EXPECT_EQ(0, ret); \ ++ EXPECT_EQ((count), __count); \ ++ EXPECT_EQ((max), __max); \ ++ }) ++ ++static int put_sem(int fd, __u32 sem, __u32 *count) ++{ ++ struct winesync_sem_args args; ++ int ret; ++ ++ args.sem = sem; ++ args.count = *count; ++ ret = ioctl(fd, WINESYNC_IOC_PUT_SEM, &args); ++ *count = args.count; ++ return ret; ++} ++ ++static int wait_any(int fd, __u32 count, const __u32 *objs, __u32 owner, ++ __u32 *index) ++{ ++ struct winesync_wait_args args = {0}; ++ struct timespec timeout; ++ int ret; ++ ++ clock_gettime(CLOCK_MONOTONIC, &timeout); ++ ++ args.timeout = (uintptr_t)&timeout; ++ args.count = count; ++ args.objs = (uintptr_t)objs; ++ args.owner = owner; ++ args.index = 0xdeadbeef; ++ ret = ioctl(fd, WINESYNC_IOC_WAIT_ANY, &args); ++ *index = args.index; ++ return ret; ++} ++ ++TEST(semaphore_state) ++{ ++ struct winesync_sem_args sem_args; ++ struct timespec timeout; ++ __u32 sem, count, index; ++ int fd, ret; ++ ++ clock_gettime(CLOCK_MONOTONIC, &timeout); ++ ++ fd = open("/dev/winesync", O_CLOEXEC | O_RDONLY); ++ ASSERT_LE(0, fd); ++ ++ sem_args.count = 3; ++ sem_args.max = 2; ++ sem_args.sem = 0xdeadbeef; ++ ret = ioctl(fd, WINESYNC_IOC_CREATE_SEM, &sem_args); ++ EXPECT_EQ(-1, ret); ++ EXPECT_EQ(EINVAL, errno); ++ ++ sem_args.count = 2; ++ sem_args.max = 2; ++ sem_args.sem = 0xdeadbeef; ++ ret = ioctl(fd, WINESYNC_IOC_CREATE_SEM, &sem_args); ++ EXPECT_EQ(0, ret); ++ EXPECT_NE(0xdeadbeef, sem_args.sem); ++ check_sem_state(fd, sem, 2, 2); ++ ++ count = 0; ++ ret = put_sem(fd, sem, &count); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(2, count); ++ check_sem_state(fd, sem, 2, 2); ++ ++ count = 1; ++ ret = put_sem(fd, sem, &count); ++ EXPECT_EQ(-1, ret); ++ EXPECT_EQ(EOVERFLOW, errno); ++ check_sem_state(fd, sem, 2, 2); ++ ++ ret = wait_any(fd, 1, &sem, 123, &index); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, index); ++ check_sem_state(fd, sem, 1, 2); ++ ++ ret = wait_any(fd, 1, &sem, 123, &index); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, index); ++ check_sem_state(fd, sem, 0, 2); ++ ++ ret = wait_any(fd, 1, &sem, 123, &index); ++ EXPECT_EQ(-1, ret); ++ EXPECT_EQ(ETIMEDOUT, errno); ++ ++ count = 3; ++ ret = put_sem(fd, sem, &count); ++ EXPECT_EQ(-1, ret); ++ EXPECT_EQ(EOVERFLOW, errno); ++ check_sem_state(fd, sem, 0, 2); ++ ++ count = 2; ++ ret = put_sem(fd, sem, &count); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, count); ++ check_sem_state(fd, sem, 2, 2); ++ ++ ret = wait_any(fd, 1, &sem, 123, &index); ++ EXPECT_EQ(0, ret); ++ ret = wait_any(fd, 1, &sem, 123, &index); ++ EXPECT_EQ(0, ret); ++ ++ count = 1; ++ ret = put_sem(fd, sem, &count); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, count); ++ check_sem_state(fd, sem, 1, 2); ++ ++ ret = ioctl(fd, WINESYNC_IOC_DELETE, &sem); ++ EXPECT_EQ(0, ret); ++ ++ close(fd); ++} ++ ++TEST_HARNESS_MAIN +-- +2.36.0 + +From c62acefda29b36849abde8134bf2a3fe8d893520 Mon Sep 17 00:00:00 2001 +From: Zebediah Figura +Date: Fri, 5 Mar 2021 12:07:04 -0600 +Subject: [PATCH 14/34] selftests: winesync: Add some tests for mutex state. + +--- + .../selftests/drivers/winesync/winesync.c | 188 ++++++++++++++++++ + 1 file changed, 188 insertions(+) + +diff --git a/tools/testing/selftests/drivers/winesync/winesync.c b/tools/testing/selftests/drivers/winesync/winesync.c +index 58ade297fef9..801b776da5aa 100644 +--- a/tools/testing/selftests/drivers/winesync/winesync.c ++++ b/tools/testing/selftests/drivers/winesync/winesync.c +@@ -49,6 +49,42 @@ static int put_sem(int fd, __u32 sem, __u32 *count) + return ret; + } + ++static int read_mutex_state(int fd, __u32 mutex, __u32 *count, __u32 *owner) ++{ ++ struct winesync_mutex_args args; ++ int ret; ++ ++ args.mutex = mutex; ++ args.count = 0xdeadbeef; ++ args.owner = 0xdeadbeef; ++ ret = ioctl(fd, WINESYNC_IOC_READ_MUTEX, &args); ++ *count = args.count; ++ *owner = args.owner; ++ return ret; ++} ++ ++#define check_mutex_state(fd, mutex, count, owner) \ ++ ({ \ ++ __u32 __count, __owner; \ ++ int ret = read_mutex_state((fd), (mutex), &__count, &__owner); \ ++ EXPECT_EQ(0, ret); \ ++ EXPECT_EQ((count), __count); \ ++ EXPECT_EQ((owner), __owner); \ ++ }) ++ ++static int put_mutex(int fd, __u32 mutex, __u32 owner, __u32 *count) ++{ ++ struct winesync_mutex_args args; ++ int ret; ++ ++ args.mutex = mutex; ++ args.owner = owner; ++ args.count = 0xdeadbeef; ++ ret = ioctl(fd, WINESYNC_IOC_PUT_MUTEX, &args); ++ *count = args.count; ++ return ret; ++} ++ + static int wait_any(int fd, __u32 count, const __u32 *objs, __u32 owner, + __u32 *index) + { +@@ -150,4 +186,156 @@ TEST(semaphore_state) + close(fd); + } + ++TEST(mutex_state) ++{ ++ struct winesync_mutex_args mutex_args; ++ __u32 mutex, owner, count, index; ++ struct timespec timeout; ++ int fd, ret; ++ ++ clock_gettime(CLOCK_MONOTONIC, &timeout); ++ ++ fd = open("/dev/winesync", O_CLOEXEC | O_RDONLY); ++ ASSERT_LE(0, fd); ++ ++ mutex_args.owner = 123; ++ mutex_args.count = 0; ++ ret = ioctl(fd, WINESYNC_IOC_CREATE_MUTEX, &mutex_args); ++ EXPECT_EQ(-1, ret); ++ EXPECT_EQ(EINVAL, errno); ++ ++ mutex_args.owner = 0; ++ mutex_args.count = 2; ++ ret = ioctl(fd, WINESYNC_IOC_CREATE_MUTEX, &mutex_args); ++ EXPECT_EQ(-1, ret); ++ EXPECT_EQ(EINVAL, errno); ++ ++ mutex_args.owner = 123; ++ mutex_args.count = 2; ++ mutex_args.mutex = 0xdeadbeef; ++ ret = ioctl(fd, WINESYNC_IOC_CREATE_MUTEX, &mutex_args); ++ EXPECT_EQ(0, ret); ++ EXPECT_NE(0xdeadbeef, mutex_args.mutex); ++ mutex = mutex_args.mutex; ++ check_mutex_state(fd, mutex, 2, 123); ++ ++ ret = put_mutex(fd, mutex, 0, &count); ++ EXPECT_EQ(-1, ret); ++ EXPECT_EQ(EINVAL, errno); ++ ++ ret = put_mutex(fd, mutex, 456, &count); ++ EXPECT_EQ(-1, ret); ++ EXPECT_EQ(EPERM, errno); ++ check_mutex_state(fd, mutex, 2, 123); ++ ++ ret = put_mutex(fd, mutex, 123, &count); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(2, count); ++ check_mutex_state(fd, mutex, 1, 123); ++ ++ ret = put_mutex(fd, mutex, 123, &count); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(1, count); ++ check_mutex_state(fd, mutex, 0, 0); ++ ++ ret = put_mutex(fd, mutex, 123, &count); ++ EXPECT_EQ(-1, ret); ++ EXPECT_EQ(EPERM, errno); ++ ++ ret = wait_any(fd, 1, &mutex, 456, &index); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, index); ++ check_mutex_state(fd, mutex, 1, 456); ++ ++ ret = wait_any(fd, 1, &mutex, 456, &index); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, index); ++ check_mutex_state(fd, mutex, 2, 456); ++ ++ ret = put_mutex(fd, mutex, 456, &count); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(2, count); ++ check_mutex_state(fd, mutex, 1, 456); ++ ++ ret = wait_any(fd, 1, &mutex, 123, &index); ++ EXPECT_EQ(-1, ret); ++ EXPECT_EQ(ETIMEDOUT, errno); ++ ++ owner = 0; ++ ret = ioctl(fd, WINESYNC_IOC_KILL_OWNER, &owner); ++ EXPECT_EQ(-1, ret); ++ EXPECT_EQ(EINVAL, errno); ++ ++ owner = 123; ++ ret = ioctl(fd, WINESYNC_IOC_KILL_OWNER, &owner); ++ EXPECT_EQ(0, ret); ++ check_mutex_state(fd, mutex, 1, 456); ++ ++ owner = 456; ++ ret = ioctl(fd, WINESYNC_IOC_KILL_OWNER, &owner); ++ EXPECT_EQ(0, ret); ++ ++ mutex_args.count = 0xdeadbeef; ++ mutex_args.owner = 0xdeadbeef; ++ ret = ioctl(fd, WINESYNC_IOC_READ_MUTEX, &mutex_args); ++ EXPECT_EQ(-1, ret); ++ EXPECT_EQ(EOWNERDEAD, errno); ++ EXPECT_EQ(0, mutex_args.count); ++ EXPECT_EQ(0, mutex_args.owner); ++ ++ mutex_args.count = 0xdeadbeef; ++ mutex_args.owner = 0xdeadbeef; ++ ret = ioctl(fd, WINESYNC_IOC_READ_MUTEX, &mutex_args); ++ EXPECT_EQ(-1, ret); ++ EXPECT_EQ(EOWNERDEAD, errno); ++ EXPECT_EQ(0, mutex_args.count); ++ EXPECT_EQ(0, mutex_args.owner); ++ ++ ret = wait_any(fd, 1, &mutex, 123, &index); ++ EXPECT_EQ(-1, ret); ++ EXPECT_EQ(EOWNERDEAD, errno); ++ EXPECT_EQ(0, index); ++ check_mutex_state(fd, mutex, 1, 123); ++ ++ owner = 123; ++ ret = ioctl(fd, WINESYNC_IOC_KILL_OWNER, &owner); ++ EXPECT_EQ(0, ret); ++ ++ mutex_args.count = 0xdeadbeef; ++ mutex_args.owner = 0xdeadbeef; ++ ret = ioctl(fd, WINESYNC_IOC_READ_MUTEX, &mutex_args); ++ EXPECT_EQ(-1, ret); ++ EXPECT_EQ(EOWNERDEAD, errno); ++ EXPECT_EQ(0, mutex_args.count); ++ EXPECT_EQ(0, mutex_args.owner); ++ ++ ret = wait_any(fd, 1, &mutex, 123, &index); ++ EXPECT_EQ(-1, ret); ++ EXPECT_EQ(EOWNERDEAD, errno); ++ EXPECT_EQ(0, index); ++ check_mutex_state(fd, mutex, 1, 123); ++ ++ ret = ioctl(fd, WINESYNC_IOC_DELETE, &mutex); ++ EXPECT_EQ(0, ret); ++ ++ mutex_args.owner = 0; ++ mutex_args.count = 0; ++ mutex_args.mutex = 0xdeadbeef; ++ ret = ioctl(fd, WINESYNC_IOC_CREATE_MUTEX, &mutex_args); ++ EXPECT_EQ(0, ret); ++ EXPECT_NE(0xdeadbeef, mutex_args.mutex); ++ mutex = mutex_args.mutex; ++ check_mutex_state(fd, mutex, 0, 0); ++ ++ ret = wait_any(fd, 1, &mutex, 123, &index); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, index); ++ check_mutex_state(fd, mutex, 1, 123); ++ ++ ret = ioctl(fd, WINESYNC_IOC_DELETE, &mutex_args.mutex); ++ EXPECT_EQ(0, ret); ++ ++ close(fd); ++} ++ + TEST_HARNESS_MAIN +-- +2.36.0 + +From 540cefcfe255d0b4c7208ae57a43fe0f16ce2531 Mon Sep 17 00:00:00 2001 +From: Zebediah Figura +Date: Fri, 5 Mar 2021 12:07:45 -0600 +Subject: [PATCH 15/34] selftests: winesync: Add some tests for + WINESYNC_IOC_WAIT_ANY. + +--- + .../selftests/drivers/winesync/winesync.c | 107 ++++++++++++++++++ + 1 file changed, 107 insertions(+) + +diff --git a/tools/testing/selftests/drivers/winesync/winesync.c b/tools/testing/selftests/drivers/winesync/winesync.c +index 801b776da5aa..5903061d38b6 100644 +--- a/tools/testing/selftests/drivers/winesync/winesync.c ++++ b/tools/testing/selftests/drivers/winesync/winesync.c +@@ -338,4 +338,111 @@ TEST(mutex_state) + close(fd); + } + ++TEST(test_wait_any) ++{ ++ struct winesync_mutex_args mutex_args = {0}; ++ struct winesync_wait_args wait_args = {0}; ++ struct winesync_sem_args sem_args = {0}; ++ __u32 objs[2], owner, index; ++ struct timespec timeout; ++ int fd, ret; ++ ++ clock_gettime(CLOCK_MONOTONIC, &timeout); ++ ++ fd = open("/dev/winesync", O_CLOEXEC | O_RDONLY); ++ ASSERT_LE(0, fd); ++ ++ sem_args.count = 2; ++ sem_args.max = 3; ++ sem_args.sem = 0xdeadbeef; ++ ret = ioctl(fd, WINESYNC_IOC_CREATE_SEM, &sem_args); ++ EXPECT_EQ(0, ret); ++ EXPECT_NE(0xdeadbeef, sem_args.sem); ++ ++ mutex_args.owner = 0; ++ mutex_args.count = 0; ++ mutex_args.mutex = 0xdeadbeef; ++ ret = ioctl(fd, WINESYNC_IOC_CREATE_MUTEX, &mutex_args); ++ EXPECT_EQ(0, ret); ++ EXPECT_NE(0xdeadbeef, mutex_args.mutex); ++ ++ objs[0] = sem_args.sem; ++ objs[1] = mutex_args.mutex; ++ ++ ret = wait_any(fd, 2, objs, 123, &index); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, index); ++ check_sem_state(fd, sem_args.sem, 1, 3); ++ check_mutex_state(fd, mutex_args.mutex, 0, 0); ++ ++ ret = wait_any(fd, 2, objs, 123, &index); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, index); ++ check_sem_state(fd, sem_args.sem, 0, 3); ++ check_mutex_state(fd, mutex_args.mutex, 0, 0); ++ ++ ret = wait_any(fd, 2, objs, 123, &index); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(1, index); ++ check_sem_state(fd, sem_args.sem, 0, 3); ++ check_mutex_state(fd, mutex_args.mutex, 1, 123); ++ ++ sem_args.count = 1; ++ ret = ioctl(fd, WINESYNC_IOC_PUT_SEM, &sem_args); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, sem_args.count); ++ ++ ret = wait_any(fd, 2, objs, 123, &index); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, index); ++ check_sem_state(fd, sem_args.sem, 0, 3); ++ check_mutex_state(fd, mutex_args.mutex, 1, 123); ++ ++ ret = wait_any(fd, 2, objs, 123, &index); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(1, index); ++ check_sem_state(fd, sem_args.sem, 0, 3); ++ check_mutex_state(fd, mutex_args.mutex, 2, 123); ++ ++ ret = wait_any(fd, 2, objs, 456, &index); ++ EXPECT_EQ(-1, ret); ++ EXPECT_EQ(ETIMEDOUT, errno); ++ ++ owner = 123; ++ ret = ioctl(fd, WINESYNC_IOC_KILL_OWNER, &owner); ++ EXPECT_EQ(0, ret); ++ ++ ret = wait_any(fd, 2, objs, 456, &index); ++ EXPECT_EQ(-1, ret); ++ EXPECT_EQ(EOWNERDEAD, errno); ++ EXPECT_EQ(1, index); ++ ++ ret = wait_any(fd, 2, objs, 456, &index); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(1, index); ++ ++ /* test waiting on the same object twice */ ++ sem_args.count = 2; ++ ret = ioctl(fd, WINESYNC_IOC_PUT_SEM, &sem_args); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, sem_args.count); ++ ++ objs[0] = objs[1] = sem_args.sem; ++ ret = wait_any(fd, 2, objs, 456, &index); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, wait_args.index); ++ check_sem_state(fd, sem_args.sem, 1, 3); ++ ++ ret = wait_any(fd, 0, NULL, 456, &index); ++ EXPECT_EQ(-1, ret); ++ EXPECT_EQ(ETIMEDOUT, errno); ++ ++ ret = ioctl(fd, WINESYNC_IOC_DELETE, &sem_args.sem); ++ EXPECT_EQ(0, ret); ++ ret = ioctl(fd, WINESYNC_IOC_DELETE, &mutex_args.mutex); ++ EXPECT_EQ(0, ret); ++ ++ close(fd); ++} ++ + TEST_HARNESS_MAIN +-- +2.36.0 + +From 17f55215ea56e925369e2eec7eaead604a273e34 Mon Sep 17 00:00:00 2001 +From: Zebediah Figura +Date: Fri, 5 Mar 2021 12:08:25 -0600 +Subject: [PATCH 16/34] selftests: winesync: Add some tests for + WINESYNC_IOC_WAIT_ALL. + +--- + .../selftests/drivers/winesync/winesync.c | 104 +++++++++++++++++- + 1 file changed, 101 insertions(+), 3 deletions(-) + +diff --git a/tools/testing/selftests/drivers/winesync/winesync.c b/tools/testing/selftests/drivers/winesync/winesync.c +index 5903061d38b6..0718219f54bf 100644 +--- a/tools/testing/selftests/drivers/winesync/winesync.c ++++ b/tools/testing/selftests/drivers/winesync/winesync.c +@@ -85,8 +85,8 @@ static int put_mutex(int fd, __u32 mutex, __u32 owner, __u32 *count) + return ret; + } + +-static int wait_any(int fd, __u32 count, const __u32 *objs, __u32 owner, +- __u32 *index) ++static int wait_objs(int fd, unsigned long request, __u32 count, ++ const __u32 *objs, __u32 owner, __u32 *index) + { + struct winesync_wait_args args = {0}; + struct timespec timeout; +@@ -99,11 +99,23 @@ static int wait_any(int fd, __u32 count, const __u32 *objs, __u32 owner, + args.objs = (uintptr_t)objs; + args.owner = owner; + args.index = 0xdeadbeef; +- ret = ioctl(fd, WINESYNC_IOC_WAIT_ANY, &args); ++ ret = ioctl(fd, request, &args); + *index = args.index; + return ret; + } + ++static int wait_any(int fd, __u32 count, const __u32 *objs, ++ __u32 owner, __u32 *index) ++{ ++ return wait_objs(fd, WINESYNC_IOC_WAIT_ANY, count, objs, owner, index); ++} ++ ++static int wait_all(int fd, __u32 count, const __u32 *objs, ++ __u32 owner, __u32 *index) ++{ ++ return wait_objs(fd, WINESYNC_IOC_WAIT_ALL, count, objs, owner, index); ++} ++ + TEST(semaphore_state) + { + struct winesync_sem_args sem_args; +@@ -445,4 +457,90 @@ TEST(test_wait_any) + close(fd); + } + ++TEST(test_wait_all) ++{ ++ struct winesync_mutex_args mutex_args = {0}; ++ struct winesync_sem_args sem_args = {0}; ++ __u32 objs[2], owner, index; ++ int fd, ret; ++ ++ fd = open("/dev/winesync", O_CLOEXEC | O_RDONLY); ++ ASSERT_LE(0, fd); ++ ++ sem_args.count = 2; ++ sem_args.max = 3; ++ sem_args.sem = 0xdeadbeef; ++ ret = ioctl(fd, WINESYNC_IOC_CREATE_SEM, &sem_args); ++ EXPECT_EQ(0, ret); ++ EXPECT_NE(0xdeadbeef, sem_args.sem); ++ ++ mutex_args.owner = 0; ++ mutex_args.count = 0; ++ mutex_args.mutex = 0xdeadbeef; ++ ret = ioctl(fd, WINESYNC_IOC_CREATE_MUTEX, &mutex_args); ++ EXPECT_EQ(0, ret); ++ EXPECT_NE(0xdeadbeef, mutex_args.mutex); ++ ++ objs[0] = sem_args.sem; ++ objs[1] = mutex_args.mutex; ++ ++ ret = wait_all(fd, 2, objs, 123, &index); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, index); ++ check_sem_state(fd, sem_args.sem, 1, 3); ++ check_mutex_state(fd, mutex_args.mutex, 1, 123); ++ ++ ret = wait_all(fd, 2, objs, 456, &index); ++ EXPECT_EQ(-1, ret); ++ EXPECT_EQ(ETIMEDOUT, errno); ++ check_sem_state(fd, sem_args.sem, 1, 3); ++ check_mutex_state(fd, mutex_args.mutex, 1, 123); ++ ++ ret = wait_all(fd, 2, objs, 123, &index); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, index); ++ check_sem_state(fd, sem_args.sem, 0, 3); ++ check_mutex_state(fd, mutex_args.mutex, 2, 123); ++ ++ ret = wait_all(fd, 2, objs, 123, &index); ++ EXPECT_EQ(-1, ret); ++ EXPECT_EQ(ETIMEDOUT, errno); ++ check_sem_state(fd, sem_args.sem, 0, 3); ++ check_mutex_state(fd, mutex_args.mutex, 2, 123); ++ ++ sem_args.count = 3; ++ ret = ioctl(fd, WINESYNC_IOC_PUT_SEM, &sem_args); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, sem_args.count); ++ ++ ret = wait_all(fd, 2, objs, 123, &index); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, index); ++ check_sem_state(fd, sem_args.sem, 2, 3); ++ check_mutex_state(fd, mutex_args.mutex, 3, 123); ++ ++ owner = 123; ++ ret = ioctl(fd, WINESYNC_IOC_KILL_OWNER, &owner); ++ EXPECT_EQ(0, ret); ++ ++ ret = wait_all(fd, 2, objs, 123, &index); ++ EXPECT_EQ(-1, ret); ++ EXPECT_EQ(EOWNERDEAD, errno); ++ check_sem_state(fd, sem_args.sem, 1, 3); ++ check_mutex_state(fd, mutex_args.mutex, 1, 123); ++ ++ /* test waiting on the same object twice */ ++ objs[0] = objs[1] = sem_args.sem; ++ ret = wait_all(fd, 2, objs, 123, &index); ++ EXPECT_EQ(-1, ret); ++ EXPECT_EQ(EINVAL, errno); ++ ++ ret = ioctl(fd, WINESYNC_IOC_DELETE, &sem_args.sem); ++ EXPECT_EQ(0, ret); ++ ret = ioctl(fd, WINESYNC_IOC_DELETE, &mutex_args.mutex); ++ EXPECT_EQ(0, ret); ++ ++ close(fd); ++} ++ + TEST_HARNESS_MAIN +-- +2.36.0 + +From 6d07a2265d06d3f0af6fe2d9874762fb2e922488 Mon Sep 17 00:00:00 2001 +From: Zebediah Figura +Date: Fri, 5 Mar 2021 12:08:54 -0600 +Subject: [PATCH 17/34] selftests: winesync: Add some tests for invalid object + handling. + +--- + .../selftests/drivers/winesync/winesync.c | 93 +++++++++++++++++++ + 1 file changed, 93 insertions(+) + +diff --git a/tools/testing/selftests/drivers/winesync/winesync.c b/tools/testing/selftests/drivers/winesync/winesync.c +index 0718219f54bf..8a9fb496f5e0 100644 +--- a/tools/testing/selftests/drivers/winesync/winesync.c ++++ b/tools/testing/selftests/drivers/winesync/winesync.c +@@ -543,4 +543,97 @@ TEST(test_wait_all) + close(fd); + } + ++TEST(invalid_objects) ++{ ++ struct winesync_mutex_args mutex_args = {0}; ++ struct winesync_wait_args wait_args = {0}; ++ struct winesync_sem_args sem_args = {0}; ++ __u32 objs[2] = {0}; ++ int fd, ret; ++ ++ fd = open("/dev/winesync", O_CLOEXEC | O_RDONLY); ++ ASSERT_LE(0, fd); ++ ++ ret = ioctl(fd, WINESYNC_IOC_PUT_SEM, &sem_args); ++ EXPECT_EQ(-1, ret); ++ EXPECT_EQ(EINVAL, errno); ++ ++ ret = ioctl(fd, WINESYNC_IOC_READ_SEM, &sem_args); ++ EXPECT_EQ(-1, ret); ++ EXPECT_EQ(EINVAL, errno); ++ ++ ret = ioctl(fd, WINESYNC_IOC_PUT_MUTEX, &mutex_args); ++ EXPECT_EQ(-1, ret); ++ EXPECT_EQ(EINVAL, errno); ++ ++ ret = ioctl(fd, WINESYNC_IOC_READ_MUTEX, &mutex_args); ++ EXPECT_EQ(-1, ret); ++ EXPECT_EQ(EINVAL, errno); ++ ++ wait_args.objs = (uintptr_t)objs; ++ wait_args.count = 1; ++ ret = ioctl(fd, WINESYNC_IOC_WAIT_ANY, &wait_args); ++ EXPECT_EQ(-1, ret); ++ EXPECT_EQ(EINVAL, errno); ++ ret = ioctl(fd, WINESYNC_IOC_WAIT_ALL, &wait_args); ++ EXPECT_EQ(-1, ret); ++ EXPECT_EQ(EINVAL, errno); ++ ++ ret = ioctl(fd, WINESYNC_IOC_DELETE, &objs[0]); ++ EXPECT_EQ(-1, ret); ++ EXPECT_EQ(EINVAL, errno); ++ ++ sem_args.max = 1; ++ ret = ioctl(fd, WINESYNC_IOC_CREATE_SEM, &sem_args); ++ EXPECT_EQ(0, ret); ++ ++ mutex_args.mutex = sem_args.sem; ++ ret = ioctl(fd, WINESYNC_IOC_PUT_MUTEX, &mutex_args); ++ EXPECT_EQ(-1, ret); ++ EXPECT_EQ(EINVAL, errno); ++ ++ ret = ioctl(fd, WINESYNC_IOC_READ_MUTEX, &mutex_args); ++ EXPECT_EQ(-1, ret); ++ EXPECT_EQ(EINVAL, errno); ++ ++ objs[0] = sem_args.sem; ++ objs[1] = sem_args.sem + 1; ++ wait_args.count = 2; ++ ret = ioctl(fd, WINESYNC_IOC_WAIT_ANY, &wait_args); ++ EXPECT_EQ(-1, ret); ++ EXPECT_EQ(EINVAL, errno); ++ ret = ioctl(fd, WINESYNC_IOC_WAIT_ALL, &wait_args); ++ EXPECT_EQ(-1, ret); ++ EXPECT_EQ(EINVAL, errno); ++ ++ objs[0] = sem_args.sem + 1; ++ objs[1] = sem_args.sem; ++ ret = ioctl(fd, WINESYNC_IOC_WAIT_ANY, &wait_args); ++ EXPECT_EQ(-1, ret); ++ EXPECT_EQ(EINVAL, errno); ++ ret = ioctl(fd, WINESYNC_IOC_WAIT_ALL, &wait_args); ++ EXPECT_EQ(-1, ret); ++ EXPECT_EQ(EINVAL, errno); ++ ++ ret = ioctl(fd, WINESYNC_IOC_DELETE, &sem_args.sem); ++ EXPECT_EQ(0, ret); ++ ++ ret = ioctl(fd, WINESYNC_IOC_CREATE_MUTEX, &mutex_args); ++ EXPECT_EQ(0, ret); ++ ++ sem_args.sem = mutex_args.mutex; ++ ret = ioctl(fd, WINESYNC_IOC_PUT_SEM, &sem_args); ++ EXPECT_EQ(-1, ret); ++ EXPECT_EQ(EINVAL, errno); ++ ++ ret = ioctl(fd, WINESYNC_IOC_READ_SEM, &sem_args); ++ EXPECT_EQ(-1, ret); ++ EXPECT_EQ(EINVAL, errno); ++ ++ ret = ioctl(fd, WINESYNC_IOC_DELETE, &mutex_args.mutex); ++ EXPECT_EQ(0, ret); ++ ++ close(fd); ++} ++ + TEST_HARNESS_MAIN +-- +2.36.0 + +From fafaf63d58b1f8ae3644ec5850c170bce6f6b5d2 Mon Sep 17 00:00:00 2001 +From: Zebediah Figura +Date: Fri, 5 Mar 2021 12:09:32 -0600 +Subject: [PATCH 18/34] selftests: winesync: Add some tests for wakeup + signaling with WINESYNC_IOC_WAIT_ANY. + +--- + .../selftests/drivers/winesync/winesync.c | 154 ++++++++++++++++++ + 1 file changed, 154 insertions(+) + +diff --git a/tools/testing/selftests/drivers/winesync/winesync.c b/tools/testing/selftests/drivers/winesync/winesync.c +index 8a9fb496f5e0..04855df00894 100644 +--- a/tools/testing/selftests/drivers/winesync/winesync.c ++++ b/tools/testing/selftests/drivers/winesync/winesync.c +@@ -636,4 +636,158 @@ TEST(invalid_objects) + close(fd); + } + ++struct wake_args ++{ ++ int fd; ++ __u32 obj; ++}; ++ ++struct wait_args ++{ ++ int fd; ++ unsigned long request; ++ struct winesync_wait_args *args; ++ int ret; ++ int err; ++}; ++ ++static void *wait_thread(void *arg) ++{ ++ struct wait_args *args = arg; ++ ++ args->ret = ioctl(args->fd, args->request, args->args); ++ args->err = errno; ++ return NULL; ++} ++ ++static void get_abs_timeout(struct timespec *timeout, clockid_t clock, ++ unsigned int ms) ++{ ++ clock_gettime(clock, timeout); ++ timeout->tv_nsec += ms * 1000000; ++ timeout->tv_sec += (timeout->tv_nsec / 1000000000); ++ timeout->tv_nsec %= 1000000000; ++} ++ ++static int wait_for_thread(pthread_t thread, unsigned int ms) ++{ ++ struct timespec timeout; ++ get_abs_timeout(&timeout, CLOCK_REALTIME, ms); ++ return pthread_timedjoin_np(thread, NULL, &timeout); ++} ++ ++TEST(wake_any) ++{ ++ struct winesync_mutex_args mutex_args = {0}; ++ struct winesync_wait_args wait_args = {0}; ++ struct winesync_sem_args sem_args = {0}; ++ struct wait_args thread_args; ++ __u32 objs[2], count, index; ++ struct timespec timeout; ++ pthread_t thread; ++ int fd, ret; ++ ++ fd = open("/dev/winesync", O_CLOEXEC | O_RDONLY); ++ ASSERT_LE(0, fd); ++ ++ sem_args.count = 0; ++ sem_args.max = 3; ++ sem_args.sem = 0xdeadbeef; ++ ret = ioctl(fd, WINESYNC_IOC_CREATE_SEM, &sem_args); ++ EXPECT_EQ(0, ret); ++ EXPECT_NE(0xdeadbeef, sem_args.sem); ++ ++ mutex_args.owner = 123; ++ mutex_args.count = 1; ++ mutex_args.mutex = 0xdeadbeef; ++ ret = ioctl(fd, WINESYNC_IOC_CREATE_MUTEX, &mutex_args); ++ EXPECT_EQ(0, ret); ++ EXPECT_NE(0xdeadbeef, mutex_args.mutex); ++ ++ objs[0] = sem_args.sem; ++ objs[1] = mutex_args.mutex; ++ ++ /* test waking the semaphore */ ++ ++ get_abs_timeout(&timeout, CLOCK_MONOTONIC, 1000); ++ wait_args.timeout = (uintptr_t)&timeout; ++ wait_args.objs = (uintptr_t)objs; ++ wait_args.count = 2; ++ wait_args.owner = 456; ++ wait_args.index = 0xdeadbeef; ++ thread_args.fd = fd; ++ thread_args.args = &wait_args; ++ thread_args.request = WINESYNC_IOC_WAIT_ANY; ++ ret = pthread_create(&thread, NULL, wait_thread, &thread_args); ++ EXPECT_EQ(0, ret); ++ ++ ret = wait_for_thread(thread, 100); ++ EXPECT_EQ(ETIMEDOUT, ret); ++ ++ sem_args.count = 1; ++ ret = ioctl(fd, WINESYNC_IOC_PUT_SEM, &sem_args); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, sem_args.count); ++ check_sem_state(fd, sem_args.sem, 0, 3); ++ ++ ret = wait_for_thread(thread, 100); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, thread_args.ret); ++ EXPECT_EQ(0, wait_args.index); ++ ++ /* test waking the mutex */ ++ ++ /* first grab it again for owner 123 */ ++ ret = wait_any(fd, 1, &mutex_args.mutex, 123, &index); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, index); ++ ++ get_abs_timeout(&timeout, CLOCK_MONOTONIC, 1000); ++ wait_args.owner = 456; ++ ret = pthread_create(&thread, NULL, wait_thread, &thread_args); ++ EXPECT_EQ(0, ret); ++ ++ ret = wait_for_thread(thread, 100); ++ EXPECT_EQ(ETIMEDOUT, ret); ++ ++ ret = put_mutex(fd, mutex_args.mutex, 123, &count); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(2, count); ++ ++ ret = pthread_tryjoin_np(thread, NULL); ++ EXPECT_EQ(EBUSY, ret); ++ ++ ret = put_mutex(fd, mutex_args.mutex, 123, &count); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(1, mutex_args.count); ++ check_mutex_state(fd, mutex_args.mutex, 1, 456); ++ ++ ret = wait_for_thread(thread, 100); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, thread_args.ret); ++ EXPECT_EQ(1, wait_args.index); ++ ++ /* delete an object while it's being waited on */ ++ ++ get_abs_timeout(&timeout, CLOCK_MONOTONIC, 200); ++ wait_args.owner = 123; ++ ret = pthread_create(&thread, NULL, wait_thread, &thread_args); ++ EXPECT_EQ(0, ret); ++ ++ ret = wait_for_thread(thread, 100); ++ EXPECT_EQ(ETIMEDOUT, ret); ++ ++ ret = ioctl(fd, WINESYNC_IOC_DELETE, &sem_args.sem); ++ EXPECT_EQ(0, ret); ++ ret = ioctl(fd, WINESYNC_IOC_DELETE, &mutex_args.mutex); ++ EXPECT_EQ(0, ret); ++ ++ ret = wait_for_thread(thread, 200); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(-1, thread_args.ret); ++ EXPECT_EQ(ETIMEDOUT, thread_args.err); ++ ++ close(fd); ++} ++ + TEST_HARNESS_MAIN +-- +2.36.0 + +From c1916abd720dc30c3dc1972fd9a4d69844e8ffbd Mon Sep 17 00:00:00 2001 +From: Zebediah Figura +Date: Fri, 5 Mar 2021 12:09:36 -0600 +Subject: [PATCH 19/34] selftests: winesync: Add some tests for wakeup + signaling with WINESYNC_IOC_WAIT_ALL. + +--- + .../selftests/drivers/winesync/winesync.c | 102 ++++++++++++++++++ + 1 file changed, 102 insertions(+) + +diff --git a/tools/testing/selftests/drivers/winesync/winesync.c b/tools/testing/selftests/drivers/winesync/winesync.c +index 04855df00894..ad6d0f9a2a35 100644 +--- a/tools/testing/selftests/drivers/winesync/winesync.c ++++ b/tools/testing/selftests/drivers/winesync/winesync.c +@@ -790,4 +790,106 @@ TEST(wake_any) + close(fd); + } + ++TEST(wake_all) ++{ ++ struct winesync_mutex_args mutex_args = {0}; ++ struct winesync_wait_args wait_args = {0}; ++ struct winesync_sem_args sem_args = {0}; ++ struct wait_args thread_args; ++ __u32 objs[2], count, index; ++ struct timespec timeout; ++ pthread_t thread; ++ int fd, ret; ++ ++ fd = open("/dev/winesync", O_CLOEXEC | O_RDONLY); ++ ASSERT_LE(0, fd); ++ ++ sem_args.count = 0; ++ sem_args.max = 3; ++ sem_args.sem = 0xdeadbeef; ++ ret = ioctl(fd, WINESYNC_IOC_CREATE_SEM, &sem_args); ++ EXPECT_EQ(0, ret); ++ EXPECT_NE(0xdeadbeef, sem_args.sem); ++ ++ mutex_args.owner = 123; ++ mutex_args.count = 1; ++ mutex_args.mutex = 0xdeadbeef; ++ ret = ioctl(fd, WINESYNC_IOC_CREATE_MUTEX, &mutex_args); ++ EXPECT_EQ(0, ret); ++ EXPECT_NE(0xdeadbeef, mutex_args.mutex); ++ ++ objs[0] = sem_args.sem; ++ objs[1] = mutex_args.mutex; ++ ++ get_abs_timeout(&timeout, CLOCK_MONOTONIC, 1000); ++ wait_args.timeout = (uintptr_t)&timeout; ++ wait_args.objs = (uintptr_t)objs; ++ wait_args.count = 2; ++ wait_args.owner = 456; ++ thread_args.fd = fd; ++ thread_args.args = &wait_args; ++ thread_args.request = WINESYNC_IOC_WAIT_ALL; ++ ret = pthread_create(&thread, NULL, wait_thread, &thread_args); ++ EXPECT_EQ(0, ret); ++ ++ ret = wait_for_thread(thread, 100); ++ EXPECT_EQ(ETIMEDOUT, ret); ++ ++ sem_args.count = 1; ++ ret = ioctl(fd, WINESYNC_IOC_PUT_SEM, &sem_args); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, sem_args.count); ++ ++ ret = pthread_tryjoin_np(thread, NULL); ++ EXPECT_EQ(EBUSY, ret); ++ ++ check_sem_state(fd, sem_args.sem, 1, 3); ++ ++ ret = wait_any(fd, 1, &sem_args.sem, 123, &index); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, index); ++ ++ ret = put_mutex(fd, mutex_args.mutex, 123, &count); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(1, count); ++ ++ ret = pthread_tryjoin_np(thread, NULL); ++ EXPECT_EQ(EBUSY, ret); ++ ++ check_mutex_state(fd, mutex_args.mutex, 0, 0); ++ ++ sem_args.count = 2; ++ ret = ioctl(fd, WINESYNC_IOC_PUT_SEM, &sem_args); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, sem_args.count); ++ check_sem_state(fd, sem_args.sem, 1, 3); ++ check_mutex_state(fd, mutex_args.mutex, 1, 456); ++ ++ ret = wait_for_thread(thread, 100); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, thread_args.ret); ++ ++ /* delete an object while it's being waited on */ ++ ++ get_abs_timeout(&timeout, CLOCK_MONOTONIC, 200); ++ wait_args.owner = 123; ++ ret = pthread_create(&thread, NULL, wait_thread, &thread_args); ++ EXPECT_EQ(0, ret); ++ ++ ret = wait_for_thread(thread, 100); ++ EXPECT_EQ(ETIMEDOUT, ret); ++ ++ ret = ioctl(fd, WINESYNC_IOC_DELETE, &sem_args.sem); ++ EXPECT_EQ(0, ret); ++ ret = ioctl(fd, WINESYNC_IOC_DELETE, &mutex_args.mutex); ++ EXPECT_EQ(0, ret); ++ ++ ret = wait_for_thread(thread, 200); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(-1, thread_args.ret); ++ EXPECT_EQ(ETIMEDOUT, thread_args.err); ++ ++ close(fd); ++} ++ + TEST_HARNESS_MAIN +-- +2.36.0 + +From 4e6e34339182f13972e7b906c0bd0dde74eda3d7 Mon Sep 17 00:00:00 2001 +From: Zebediah Figura +Date: Wed, 19 Jan 2022 18:21:03 -0600 +Subject: [PATCH 21/34] winesync: Introduce WINESYNC_IOC_CREATE_EVENT. + +--- + drivers/misc/winesync.c | 65 +++++++++++++++++++++++++++++++++++ + include/uapi/linux/winesync.h | 8 +++++ + 2 files changed, 73 insertions(+) + +diff --git a/drivers/misc/winesync.c b/drivers/misc/winesync.c +index eae272663abe..eaba41510784 100644 +--- a/drivers/misc/winesync.c ++++ b/drivers/misc/winesync.c +@@ -17,6 +17,7 @@ + enum winesync_type { + WINESYNC_TYPE_SEM, + WINESYNC_TYPE_MUTEX, ++ WINESYNC_TYPE_EVENT, + }; + + struct winesync_obj { +@@ -66,6 +67,10 @@ struct winesync_obj { + __u32 owner; + bool ownerdead; + } mutex; ++ struct { ++ bool manual; ++ bool signaled; ++ } event; + } u; + }; + +@@ -199,6 +204,8 @@ static bool is_signaled(struct winesync_obj *obj, __u32 owner) + if (obj->u.mutex.owner && obj->u.mutex.owner != owner) + return false; + return obj->u.mutex.count < UINT_MAX; ++ case WINESYNC_TYPE_EVENT: ++ return obj->u.event.signaled; + } + + WARN(1, "bad object type %#x\n", obj->type); +@@ -248,6 +255,10 @@ static void try_wake_all(struct winesync_device *dev, struct winesync_q *q, + obj->u.mutex.count++; + obj->u.mutex.owner = q->owner; + break; ++ case WINESYNC_TYPE_EVENT: ++ if (!obj->u.event.manual) ++ obj->u.event.signaled = false; ++ break; + } + } + wake_up_process(q->task); +@@ -315,6 +326,26 @@ static void try_wake_any_mutex(struct winesync_obj *mutex) + } + } + ++static void try_wake_any_event(struct winesync_obj *event) ++{ ++ struct winesync_q_entry *entry; ++ ++ lockdep_assert_held(&event->lock); ++ ++ list_for_each_entry(entry, &event->any_waiters, node) { ++ struct winesync_q *q = entry->q; ++ ++ if (!event->u.event.signaled) ++ break; ++ ++ if (atomic_cmpxchg(&q->signaled, -1, entry->index) == -1) { ++ if (!event->u.event.manual) ++ event->u.event.signaled = false; ++ wake_up_process(q->task); ++ } ++ } ++} ++ + static int winesync_create_sem(struct winesync_device *dev, void __user *argp) + { + struct winesync_sem_args __user *user_args = argp; +@@ -379,6 +410,35 @@ static int winesync_create_mutex(struct winesync_device *dev, void __user *argp) + return put_user(id, &user_args->mutex); + } + ++static int winesync_create_event(struct winesync_device *dev, void __user *argp) ++{ ++ struct winesync_event_args __user *user_args = argp; ++ struct winesync_event_args args; ++ struct winesync_obj *event; ++ __u32 id; ++ int ret; ++ ++ if (copy_from_user(&args, argp, sizeof(args))) ++ return -EFAULT; ++ ++ event = kzalloc(sizeof(*event), GFP_KERNEL); ++ if (!event) ++ return -ENOMEM; ++ ++ init_obj(event); ++ event->type = WINESYNC_TYPE_EVENT; ++ event->u.event.manual = args.manual; ++ event->u.event.signaled = args.signaled; ++ ++ ret = xa_alloc(&dev->objects, &id, event, xa_limit_32b, GFP_KERNEL); ++ if (ret < 0) { ++ kfree(event); ++ return ret; ++ } ++ ++ return put_user(id, &user_args->event); ++} ++ + static int winesync_delete(struct winesync_device *dev, void __user *argp) + { + struct winesync_obj *obj; +@@ -760,6 +820,9 @@ static void try_wake_any_obj(struct winesync_obj *obj) + case WINESYNC_TYPE_MUTEX: + try_wake_any_mutex(obj); + break; ++ case WINESYNC_TYPE_EVENT: ++ try_wake_any_event(obj); ++ break; + } + } + +@@ -925,6 +988,8 @@ static long winesync_char_ioctl(struct file *file, unsigned int cmd, + void __user *argp = (void __user *)parm; + + switch (cmd) { ++ case WINESYNC_IOC_CREATE_EVENT: ++ return winesync_create_event(dev, argp); + case WINESYNC_IOC_CREATE_MUTEX: + return winesync_create_mutex(dev, argp); + case WINESYNC_IOC_CREATE_SEM: +diff --git a/include/uapi/linux/winesync.h b/include/uapi/linux/winesync.h +index 3371a303a927..3999407534e0 100644 +--- a/include/uapi/linux/winesync.h ++++ b/include/uapi/linux/winesync.h +@@ -22,6 +22,12 @@ struct winesync_mutex_args { + __u32 count; + }; + ++struct winesync_event_args { ++ __u32 event; ++ __u32 manual; ++ __u32 signaled; ++}; ++ + struct winesync_wait_args { + __u64 timeout; + __u64 objs; +@@ -51,5 +57,7 @@ struct winesync_wait_args { + struct winesync_sem_args) + #define WINESYNC_IOC_READ_MUTEX _IOWR(WINESYNC_IOC_BASE, 9, \ + struct winesync_mutex_args) ++#define WINESYNC_IOC_CREATE_EVENT _IOWR(WINESYNC_IOC_BASE, 10, \ ++ struct winesync_event_args) + + #endif +-- +2.36.0 + +From 92a843a6d77099e638d5513fb4093e42ba84a3a3 Mon Sep 17 00:00:00 2001 +From: Zebediah Figura +Date: Wed, 19 Jan 2022 18:43:30 -0600 +Subject: [PATCH 22/34] winesync: Introduce WINESYNC_IOC_SET_EVENT. + +--- + drivers/misc/winesync.c | 45 +++++++++++++++++++++++++++++++++++ + include/uapi/linux/winesync.h | 2 ++ + 2 files changed, 47 insertions(+) + +diff --git a/drivers/misc/winesync.c b/drivers/misc/winesync.c +index eaba41510784..658ad7b80c29 100644 +--- a/drivers/misc/winesync.c ++++ b/drivers/misc/winesync.c +@@ -704,6 +704,49 @@ static int winesync_kill_owner(struct winesync_device *dev, void __user *argp) + return 0; + } + ++static int winesync_set_event(struct winesync_device *dev, void __user *argp) ++{ ++ struct winesync_event_args __user *user_args = argp; ++ struct winesync_event_args args; ++ struct winesync_obj *event; ++ bool prev_state; ++ ++ if (copy_from_user(&args, argp, sizeof(args))) ++ return -EFAULT; ++ ++ event = get_obj_typed(dev, args.event, WINESYNC_TYPE_EVENT); ++ if (!event) ++ return -EINVAL; ++ ++ if (atomic_read(&event->all_hint) > 0) { ++ spin_lock(&dev->wait_all_lock); ++ spin_lock(&event->lock); ++ ++ prev_state = event->u.event.signaled; ++ event->u.event.signaled = true; ++ try_wake_all_obj(dev, event); ++ try_wake_any_event(event); ++ ++ spin_unlock(&event->lock); ++ spin_unlock(&dev->wait_all_lock); ++ } else { ++ spin_lock(&event->lock); ++ ++ prev_state = event->u.event.signaled; ++ event->u.event.signaled = true; ++ try_wake_any_event(event); ++ ++ spin_unlock(&event->lock); ++ } ++ ++ put_obj(event); ++ ++ if (put_user(prev_state, &user_args->signaled)) ++ return -EFAULT; ++ ++ return 0; ++} ++ + static int winesync_schedule(const struct winesync_q *q, ktime_t *timeout) + { + int ret = 0; +@@ -1006,6 +1049,8 @@ static long winesync_char_ioctl(struct file *file, unsigned int cmd, + return winesync_read_mutex(dev, argp); + case WINESYNC_IOC_READ_SEM: + return winesync_read_sem(dev, argp); ++ case WINESYNC_IOC_SET_EVENT: ++ return winesync_set_event(dev, argp); + case WINESYNC_IOC_WAIT_ALL: + return winesync_wait_all(dev, argp); + case WINESYNC_IOC_WAIT_ANY: +diff --git a/include/uapi/linux/winesync.h b/include/uapi/linux/winesync.h +index 3999407534e0..34cd65d879a8 100644 +--- a/include/uapi/linux/winesync.h ++++ b/include/uapi/linux/winesync.h +@@ -59,5 +59,7 @@ struct winesync_wait_args { + struct winesync_mutex_args) + #define WINESYNC_IOC_CREATE_EVENT _IOWR(WINESYNC_IOC_BASE, 10, \ + struct winesync_event_args) ++#define WINESYNC_IOC_SET_EVENT _IOWR(WINESYNC_IOC_BASE, 11, \ ++ struct winesync_event_args) + + #endif +-- +2.36.0 + +From 7abe646cd9c913b78156186e3a2d98715a0f3513 Mon Sep 17 00:00:00 2001 +From: Zebediah Figura +Date: Wed, 19 Jan 2022 19:00:25 -0600 +Subject: [PATCH 23/34] winesync: Introduce WINESYNC_IOC_RESET_EVENT. + +--- + drivers/misc/winesync.c | 31 +++++++++++++++++++++++++++++++ + include/uapi/linux/winesync.h | 2 ++ + 2 files changed, 33 insertions(+) + +diff --git a/drivers/misc/winesync.c b/drivers/misc/winesync.c +index 658ad7b80c29..a93f173127f4 100644 +--- a/drivers/misc/winesync.c ++++ b/drivers/misc/winesync.c +@@ -747,6 +747,35 @@ static int winesync_set_event(struct winesync_device *dev, void __user *argp) + return 0; + } + ++static int winesync_reset_event(struct winesync_device *dev, void __user *argp) ++{ ++ struct winesync_event_args __user *user_args = argp; ++ struct winesync_event_args args; ++ struct winesync_obj *event; ++ bool prev_state; ++ ++ if (copy_from_user(&args, argp, sizeof(args))) ++ return -EFAULT; ++ ++ event = get_obj_typed(dev, args.event, WINESYNC_TYPE_EVENT); ++ if (!event) ++ return -EINVAL; ++ ++ spin_lock(&event->lock); ++ ++ prev_state = event->u.event.signaled; ++ event->u.event.signaled = false; ++ ++ spin_unlock(&event->lock); ++ ++ put_obj(event); ++ ++ if (put_user(prev_state, &user_args->signaled)) ++ return -EFAULT; ++ ++ return 0; ++} ++ + static int winesync_schedule(const struct winesync_q *q, ktime_t *timeout) + { + int ret = 0; +@@ -1049,6 +1078,8 @@ static long winesync_char_ioctl(struct file *file, unsigned int cmd, + return winesync_read_mutex(dev, argp); + case WINESYNC_IOC_READ_SEM: + return winesync_read_sem(dev, argp); ++ case WINESYNC_IOC_RESET_EVENT: ++ return winesync_reset_event(dev, argp); + case WINESYNC_IOC_SET_EVENT: + return winesync_set_event(dev, argp); + case WINESYNC_IOC_WAIT_ALL: +diff --git a/include/uapi/linux/winesync.h b/include/uapi/linux/winesync.h +index 34cd65d879a8..e71271fc44ba 100644 +--- a/include/uapi/linux/winesync.h ++++ b/include/uapi/linux/winesync.h +@@ -61,5 +61,7 @@ struct winesync_wait_args { + struct winesync_event_args) + #define WINESYNC_IOC_SET_EVENT _IOWR(WINESYNC_IOC_BASE, 11, \ + struct winesync_event_args) ++#define WINESYNC_IOC_RESET_EVENT _IOWR(WINESYNC_IOC_BASE, 12, \ ++ struct winesync_event_args) + + #endif +-- +2.36.0 + +From 3ea6a631230c7b17d345e2249f5f72ad24c46a79 Mon Sep 17 00:00:00 2001 +From: Zebediah Figura +Date: Wed, 19 Jan 2022 19:10:12 -0600 +Subject: [PATCH 24/34] winesync: Introduce WINESYNC_IOC_PULSE_EVENT. + +--- + drivers/misc/winesync.c | 11 +++++++++-- + include/uapi/linux/winesync.h | 2 ++ + 2 files changed, 11 insertions(+), 2 deletions(-) + +diff --git a/drivers/misc/winesync.c b/drivers/misc/winesync.c +index a93f173127f4..27d5baa457df 100644 +--- a/drivers/misc/winesync.c ++++ b/drivers/misc/winesync.c +@@ -704,7 +704,8 @@ static int winesync_kill_owner(struct winesync_device *dev, void __user *argp) + return 0; + } + +-static int winesync_set_event(struct winesync_device *dev, void __user *argp) ++static int winesync_set_event(struct winesync_device *dev, void __user *argp, ++ bool pulse) + { + struct winesync_event_args __user *user_args = argp; + struct winesync_event_args args; +@@ -726,6 +727,8 @@ static int winesync_set_event(struct winesync_device *dev, void __user *argp) + event->u.event.signaled = true; + try_wake_all_obj(dev, event); + try_wake_any_event(event); ++ if (pulse) ++ event->u.event.signaled = false; + + spin_unlock(&event->lock); + spin_unlock(&dev->wait_all_lock); +@@ -735,6 +738,8 @@ static int winesync_set_event(struct winesync_device *dev, void __user *argp) + prev_state = event->u.event.signaled; + event->u.event.signaled = true; + try_wake_any_event(event); ++ if (pulse) ++ event->u.event.signaled = false; + + spin_unlock(&event->lock); + } +@@ -1070,6 +1075,8 @@ static long winesync_char_ioctl(struct file *file, unsigned int cmd, + return winesync_delete(dev, argp); + case WINESYNC_IOC_KILL_OWNER: + return winesync_kill_owner(dev, argp); ++ case WINESYNC_IOC_PULSE_EVENT: ++ return winesync_set_event(dev, argp, true); + case WINESYNC_IOC_PUT_MUTEX: + return winesync_put_mutex(dev, argp); + case WINESYNC_IOC_PUT_SEM: +@@ -1081,7 +1088,7 @@ static long winesync_char_ioctl(struct file *file, unsigned int cmd, + case WINESYNC_IOC_RESET_EVENT: + return winesync_reset_event(dev, argp); + case WINESYNC_IOC_SET_EVENT: +- return winesync_set_event(dev, argp); ++ return winesync_set_event(dev, argp, false); + case WINESYNC_IOC_WAIT_ALL: + return winesync_wait_all(dev, argp); + case WINESYNC_IOC_WAIT_ANY: +diff --git a/include/uapi/linux/winesync.h b/include/uapi/linux/winesync.h +index e71271fc44ba..7c09d0e9733c 100644 +--- a/include/uapi/linux/winesync.h ++++ b/include/uapi/linux/winesync.h +@@ -63,5 +63,7 @@ struct winesync_wait_args { + struct winesync_event_args) + #define WINESYNC_IOC_RESET_EVENT _IOWR(WINESYNC_IOC_BASE, 12, \ + struct winesync_event_args) ++#define WINESYNC_IOC_PULSE_EVENT _IOWR(WINESYNC_IOC_BASE, 13, \ ++ struct winesync_event_args) + + #endif +-- +2.36.0 + +From 0fb972bb73385f9140f81a5f976b95ba750b73dd Mon Sep 17 00:00:00 2001 +From: Zebediah Figura +Date: Wed, 19 Jan 2022 19:14:00 -0600 +Subject: [PATCH 25/34] winesync: Introduce WINESYNC_IOC_READ_EVENT. + +--- + drivers/misc/winesync.c | 30 ++++++++++++++++++++++++++++++ + include/uapi/linux/winesync.h | 2 ++ + 2 files changed, 32 insertions(+) + +diff --git a/drivers/misc/winesync.c b/drivers/misc/winesync.c +index 27d5baa457df..0f8a8a94eef8 100644 +--- a/drivers/misc/winesync.c ++++ b/drivers/misc/winesync.c +@@ -639,6 +639,33 @@ static int winesync_read_mutex(struct winesync_device *dev, void __user *argp) + return ret; + } + ++static int winesync_read_event(struct winesync_device *dev, void __user *argp) ++{ ++ struct winesync_event_args __user *user_args = argp; ++ struct winesync_event_args args; ++ struct winesync_obj *event; ++ __u32 id; ++ ++ if (get_user(id, &user_args->event)) ++ return -EFAULT; ++ ++ event = get_obj_typed(dev, id, WINESYNC_TYPE_EVENT); ++ if (!event) ++ return -EINVAL; ++ ++ args.event = id; ++ spin_lock(&event->lock); ++ args.manual = event->u.event.manual; ++ args.signaled = event->u.event.signaled; ++ spin_unlock(&event->lock); ++ ++ put_obj(event); ++ ++ if (copy_to_user(user_args, &args, sizeof(args))) ++ return -EFAULT; ++ return 0; ++} ++ + /* + * Actually change the mutex state to mark its owner as dead. + */ +@@ -1081,6 +1109,8 @@ static long winesync_char_ioctl(struct file *file, unsigned int cmd, + return winesync_put_mutex(dev, argp); + case WINESYNC_IOC_PUT_SEM: + return winesync_put_sem(dev, argp); ++ case WINESYNC_IOC_READ_EVENT: ++ return winesync_read_event(dev, argp); + case WINESYNC_IOC_READ_MUTEX: + return winesync_read_mutex(dev, argp); + case WINESYNC_IOC_READ_SEM: +diff --git a/include/uapi/linux/winesync.h b/include/uapi/linux/winesync.h +index 7c09d0e9733c..fb3788339ffe 100644 +--- a/include/uapi/linux/winesync.h ++++ b/include/uapi/linux/winesync.h +@@ -65,5 +65,7 @@ struct winesync_wait_args { + struct winesync_event_args) + #define WINESYNC_IOC_PULSE_EVENT _IOWR(WINESYNC_IOC_BASE, 13, \ + struct winesync_event_args) ++#define WINESYNC_IOC_READ_EVENT _IOWR(WINESYNC_IOC_BASE, 14, \ ++ struct winesync_event_args) + + #endif +-- +2.36.0 + +From ae7648556c522595d288bc169bde503140a59db0 Mon Sep 17 00:00:00 2001 +From: Zebediah Figura +Date: Wed, 19 Jan 2022 19:34:47 -0600 +Subject: [PATCH 26/34] selftests: winesync: Add some tests for manual-reset + event state. + +--- + .../selftests/drivers/winesync/winesync.c | 92 +++++++++++++++++++ + 1 file changed, 92 insertions(+) + +diff --git a/tools/testing/selftests/drivers/winesync/winesync.c b/tools/testing/selftests/drivers/winesync/winesync.c +index ad6d0f9a2a35..7e99f09b113b 100644 +--- a/tools/testing/selftests/drivers/winesync/winesync.c ++++ b/tools/testing/selftests/drivers/winesync/winesync.c +@@ -85,6 +85,30 @@ static int put_mutex(int fd, __u32 mutex, __u32 owner, __u32 *count) + return ret; + } + ++static int read_event_state(int fd, __u32 event, __u32 *signaled, __u32 *manual) ++{ ++ struct winesync_event_args args; ++ int ret; ++ ++ args.event = event; ++ args.signaled = 0xdeadbeef; ++ args.manual = 0xdeadbeef; ++ ret = ioctl(fd, WINESYNC_IOC_READ_EVENT, &args); ++ *signaled = args.signaled; ++ *manual = args.manual; ++ return ret; ++} ++ ++#define check_event_state(fd, event, signaled, manual) \ ++ ({ \ ++ __u32 __signaled, __manual; \ ++ int ret = read_event_state((fd), (event), \ ++ &__signaled, &__manual); \ ++ EXPECT_EQ(0, ret); \ ++ EXPECT_EQ((signaled), __signaled); \ ++ EXPECT_EQ((manual), __manual); \ ++ }) ++ + static int wait_objs(int fd, unsigned long request, __u32 count, + const __u32 *objs, __u32 owner, __u32 *index) + { +@@ -350,6 +374,74 @@ TEST(mutex_state) + close(fd); + } + ++TEST(manual_event_state) ++{ ++ struct winesync_event_args event_args; ++ __u32 index; ++ int fd, ret; ++ ++ fd = open("/dev/winesync", O_CLOEXEC | O_RDONLY); ++ ASSERT_LE(0, fd); ++ ++ event_args.manual = 1; ++ event_args.signaled = 0; ++ event_args.event = 0xdeadbeef; ++ ret = ioctl(fd, WINESYNC_IOC_CREATE_EVENT, &event_args); ++ EXPECT_EQ(0, ret); ++ EXPECT_NE(0xdeadbeef, event_args.event); ++ check_event_state(fd, event_args.event, 0, 1); ++ ++ event_args.signaled = 0xdeadbeef; ++ ret = ioctl(fd, WINESYNC_IOC_SET_EVENT, &event_args); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, event_args.signaled); ++ check_event_state(fd, event_args.event, 1, 1); ++ ++ ret = ioctl(fd, WINESYNC_IOC_SET_EVENT, &event_args); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(1, event_args.signaled); ++ check_event_state(fd, event_args.event, 1, 1); ++ ++ ret = wait_any(fd, 1, &event_args.event, 123, &index); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, index); ++ check_event_state(fd, event_args.event, 1, 1); ++ ++ event_args.signaled = 0xdeadbeef; ++ ret = ioctl(fd, WINESYNC_IOC_RESET_EVENT, &event_args); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(1, event_args.signaled); ++ check_event_state(fd, event_args.event, 0, 1); ++ ++ ret = ioctl(fd, WINESYNC_IOC_RESET_EVENT, &event_args); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, event_args.signaled); ++ check_event_state(fd, event_args.event, 0, 1); ++ ++ ret = wait_any(fd, 1, &event_args.event, 123, &index); ++ EXPECT_EQ(-1, ret); ++ EXPECT_EQ(ETIMEDOUT, errno); ++ ++ ret = ioctl(fd, WINESYNC_IOC_SET_EVENT, &event_args); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, event_args.signaled); ++ ++ ret = ioctl(fd, WINESYNC_IOC_PULSE_EVENT, &event_args); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(1, event_args.signaled); ++ check_event_state(fd, event_args.event, 0, 1); ++ ++ ret = ioctl(fd, WINESYNC_IOC_PULSE_EVENT, &event_args); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, event_args.signaled); ++ check_event_state(fd, event_args.event, 0, 1); ++ ++ ret = ioctl(fd, WINESYNC_IOC_DELETE, &event_args.event); ++ EXPECT_EQ(0, ret); ++ ++ close(fd); ++} ++ + TEST(test_wait_any) + { + struct winesync_mutex_args mutex_args = {0}; +-- +2.36.0 + +From 5eeeb415ccc7e046fc71f20345bf8be20edfc1c4 Mon Sep 17 00:00:00 2001 +From: Zebediah Figura +Date: Wed, 19 Jan 2022 19:45:39 -0600 +Subject: [PATCH 27/34] selftests: winesync: Add some tests for auto-reset + event state. + +--- + .../selftests/drivers/winesync/winesync.c | 59 +++++++++++++++++++ + 1 file changed, 59 insertions(+) + +diff --git a/tools/testing/selftests/drivers/winesync/winesync.c b/tools/testing/selftests/drivers/winesync/winesync.c +index 7e99f09b113b..3a9ac69308af 100644 +--- a/tools/testing/selftests/drivers/winesync/winesync.c ++++ b/tools/testing/selftests/drivers/winesync/winesync.c +@@ -442,6 +442,65 @@ TEST(manual_event_state) + close(fd); + } + ++TEST(auto_event_state) ++{ ++ struct winesync_event_args event_args; ++ __u32 index; ++ int fd, ret; ++ ++ fd = open("/dev/winesync", O_CLOEXEC | O_RDONLY); ++ ASSERT_LE(0, fd); ++ ++ event_args.manual = 0; ++ event_args.signaled = 1; ++ event_args.event = 0xdeadbeef; ++ ret = ioctl(fd, WINESYNC_IOC_CREATE_EVENT, &event_args); ++ EXPECT_EQ(0, ret); ++ EXPECT_NE(0xdeadbeef, event_args.event); ++ ++ check_event_state(fd, event_args.event, 1, 0); ++ ++ event_args.signaled = 0xdeadbeef; ++ ret = ioctl(fd, WINESYNC_IOC_SET_EVENT, &event_args); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(1, event_args.signaled); ++ check_event_state(fd, event_args.event, 1, 0); ++ ++ ret = wait_any(fd, 1, &event_args.event, 123, &index); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, index); ++ check_event_state(fd, event_args.event, 0, 0); ++ ++ event_args.signaled = 0xdeadbeef; ++ ret = ioctl(fd, WINESYNC_IOC_RESET_EVENT, &event_args); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, event_args.signaled); ++ check_event_state(fd, event_args.event, 0, 0); ++ ++ ret = wait_any(fd, 1, &event_args.event, 123, &index); ++ EXPECT_EQ(-1, ret); ++ EXPECT_EQ(ETIMEDOUT, errno); ++ ++ ret = ioctl(fd, WINESYNC_IOC_SET_EVENT, &event_args); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, event_args.signaled); ++ ++ ret = ioctl(fd, WINESYNC_IOC_PULSE_EVENT, &event_args); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(1, event_args.signaled); ++ check_event_state(fd, event_args.event, 0, 0); ++ ++ ret = ioctl(fd, WINESYNC_IOC_PULSE_EVENT, &event_args); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, event_args.signaled); ++ check_event_state(fd, event_args.event, 0, 0); ++ ++ ret = ioctl(fd, WINESYNC_IOC_DELETE, &event_args.event); ++ EXPECT_EQ(0, ret); ++ ++ close(fd); ++} ++ + TEST(test_wait_any) + { + struct winesync_mutex_args mutex_args = {0}; +-- +2.36.0 + +From 6857a39cd264169494908abf8564ac7161773203 Mon Sep 17 00:00:00 2001 +From: Zebediah Figura +Date: Wed, 19 Jan 2022 21:00:50 -0600 +Subject: [PATCH 28/34] selftests: winesync: Add some tests for wakeup + signaling with events. + +--- + .../selftests/drivers/winesync/winesync.c | 152 +++++++++++++++++- + 1 file changed, 150 insertions(+), 2 deletions(-) + +diff --git a/tools/testing/selftests/drivers/winesync/winesync.c b/tools/testing/selftests/drivers/winesync/winesync.c +index 3a9ac69308af..2ccc51510230 100644 +--- a/tools/testing/selftests/drivers/winesync/winesync.c ++++ b/tools/testing/selftests/drivers/winesync/winesync.c +@@ -610,6 +610,7 @@ TEST(test_wait_any) + + TEST(test_wait_all) + { ++ struct winesync_event_args event_args = {0}; + struct winesync_mutex_args mutex_args = {0}; + struct winesync_sem_args sem_args = {0}; + __u32 objs[2], owner, index; +@@ -632,6 +633,11 @@ TEST(test_wait_all) + EXPECT_EQ(0, ret); + EXPECT_NE(0xdeadbeef, mutex_args.mutex); + ++ event_args.manual = true; ++ event_args.signaled = true; ++ ret = ioctl(fd, WINESYNC_IOC_CREATE_EVENT, &event_args); ++ EXPECT_EQ(0, ret); ++ + objs[0] = sem_args.sem; + objs[1] = mutex_args.mutex; + +@@ -680,6 +686,14 @@ TEST(test_wait_all) + check_sem_state(fd, sem_args.sem, 1, 3); + check_mutex_state(fd, mutex_args.mutex, 1, 123); + ++ objs[0] = sem_args.sem; ++ objs[1] = event_args.event; ++ ret = wait_all(fd, 2, objs, 123, &index); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, index); ++ check_sem_state(fd, sem_args.sem, 0, 3); ++ check_event_state(fd, event_args.event, 1, 1); ++ + /* test waiting on the same object twice */ + objs[0] = objs[1] = sem_args.sem; + ret = wait_all(fd, 2, objs, 123, &index); +@@ -690,6 +704,8 @@ TEST(test_wait_all) + EXPECT_EQ(0, ret); + ret = ioctl(fd, WINESYNC_IOC_DELETE, &mutex_args.mutex); + EXPECT_EQ(0, ret); ++ ret = ioctl(fd, WINESYNC_IOC_DELETE, &event_args.event); ++ EXPECT_EQ(0, ret); + + close(fd); + } +@@ -829,6 +845,7 @@ static int wait_for_thread(pthread_t thread, unsigned int ms) + + TEST(wake_any) + { ++ struct winesync_event_args event_args = {0}; + struct winesync_mutex_args mutex_args = {0}; + struct winesync_wait_args wait_args = {0}; + struct winesync_sem_args sem_args = {0}; +@@ -918,10 +935,103 @@ TEST(wake_any) + EXPECT_EQ(0, thread_args.ret); + EXPECT_EQ(1, wait_args.index); + ++ /* test waking events */ ++ ++ event_args.manual = false; ++ event_args.signaled = false; ++ ret = ioctl(fd, WINESYNC_IOC_CREATE_EVENT, &event_args); ++ EXPECT_EQ(0, ret); ++ ++ objs[1] = event_args.event; ++ get_abs_timeout(&timeout, CLOCK_MONOTONIC, 1000); ++ ret = pthread_create(&thread, NULL, wait_thread, &thread_args); ++ EXPECT_EQ(0, ret); ++ ++ ret = wait_for_thread(thread, 100); ++ EXPECT_EQ(ETIMEDOUT, ret); ++ ++ ret = ioctl(fd, WINESYNC_IOC_SET_EVENT, &event_args); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, event_args.signaled); ++ check_event_state(fd, event_args.event, 0, 0); ++ ++ ret = wait_for_thread(thread, 100); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, thread_args.ret); ++ EXPECT_EQ(1, wait_args.index); ++ ++ get_abs_timeout(&timeout, CLOCK_MONOTONIC, 1000); ++ ret = pthread_create(&thread, NULL, wait_thread, &thread_args); ++ EXPECT_EQ(0, ret); ++ ++ ret = wait_for_thread(thread, 100); ++ EXPECT_EQ(ETIMEDOUT, ret); ++ ++ ret = ioctl(fd, WINESYNC_IOC_PULSE_EVENT, &event_args); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, event_args.signaled); ++ check_event_state(fd, event_args.event, 0, 0); ++ ++ ret = wait_for_thread(thread, 100); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, thread_args.ret); ++ EXPECT_EQ(1, wait_args.index); ++ ++ ret = ioctl(fd, WINESYNC_IOC_DELETE, &event_args.event); ++ EXPECT_EQ(0, ret); ++ ++ event_args.manual = true; ++ event_args.signaled = false; ++ ret = ioctl(fd, WINESYNC_IOC_CREATE_EVENT, &event_args); ++ EXPECT_EQ(0, ret); ++ ++ objs[1] = event_args.event; ++ get_abs_timeout(&timeout, CLOCK_MONOTONIC, 1000); ++ ret = pthread_create(&thread, NULL, wait_thread, &thread_args); ++ EXPECT_EQ(0, ret); ++ ++ ret = wait_for_thread(thread, 100); ++ EXPECT_EQ(ETIMEDOUT, ret); ++ ++ ret = ioctl(fd, WINESYNC_IOC_SET_EVENT, &event_args); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, event_args.signaled); ++ check_event_state(fd, event_args.event, 1, 1); ++ ++ ret = wait_for_thread(thread, 100); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, thread_args.ret); ++ EXPECT_EQ(1, wait_args.index); ++ ++ ret = ioctl(fd, WINESYNC_IOC_RESET_EVENT, &event_args); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(1, event_args.signaled); ++ ++ get_abs_timeout(&timeout, CLOCK_MONOTONIC, 1000); ++ ret = pthread_create(&thread, NULL, wait_thread, &thread_args); ++ EXPECT_EQ(0, ret); ++ ++ ret = wait_for_thread(thread, 100); ++ EXPECT_EQ(ETIMEDOUT, ret); ++ ++ ret = ioctl(fd, WINESYNC_IOC_PULSE_EVENT, &event_args); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, event_args.signaled); ++ check_event_state(fd, event_args.event, 0, 1); ++ ++ ret = wait_for_thread(thread, 100); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, thread_args.ret); ++ EXPECT_EQ(1, wait_args.index); ++ ++ ret = ioctl(fd, WINESYNC_IOC_DELETE, &event_args.event); ++ EXPECT_EQ(0, ret); ++ + /* delete an object while it's being waited on */ + + get_abs_timeout(&timeout, CLOCK_MONOTONIC, 200); + wait_args.owner = 123; ++ objs[1] = mutex_args.mutex; + ret = pthread_create(&thread, NULL, wait_thread, &thread_args); + EXPECT_EQ(0, ret); + +@@ -943,11 +1053,13 @@ TEST(wake_any) + + TEST(wake_all) + { ++ struct winesync_event_args manual_event_args = {0}; ++ struct winesync_event_args auto_event_args = {0}; + struct winesync_mutex_args mutex_args = {0}; + struct winesync_wait_args wait_args = {0}; + struct winesync_sem_args sem_args = {0}; + struct wait_args thread_args; +- __u32 objs[2], count, index; ++ __u32 objs[4], count, index; + struct timespec timeout; + pthread_t thread; + int fd, ret; +@@ -969,13 +1081,25 @@ TEST(wake_all) + EXPECT_EQ(0, ret); + EXPECT_NE(0xdeadbeef, mutex_args.mutex); + ++ manual_event_args.manual = true; ++ manual_event_args.signaled = true; ++ ret = ioctl(fd, WINESYNC_IOC_CREATE_EVENT, &manual_event_args); ++ EXPECT_EQ(0, ret); ++ ++ auto_event_args.manual = false; ++ auto_event_args.signaled = true; ++ ret = ioctl(fd, WINESYNC_IOC_CREATE_EVENT, &auto_event_args); ++ EXPECT_EQ(0, ret); ++ + objs[0] = sem_args.sem; + objs[1] = mutex_args.mutex; ++ objs[2] = manual_event_args.event; ++ objs[3] = auto_event_args.event; + + get_abs_timeout(&timeout, CLOCK_MONOTONIC, 1000); + wait_args.timeout = (uintptr_t)&timeout; + wait_args.objs = (uintptr_t)objs; +- wait_args.count = 2; ++ wait_args.count = 4; + wait_args.owner = 456; + thread_args.fd = fd; + thread_args.args = &wait_args; +@@ -1009,12 +1133,32 @@ TEST(wake_all) + + check_mutex_state(fd, mutex_args.mutex, 0, 0); + ++ ret = ioctl(fd, WINESYNC_IOC_RESET_EVENT, &manual_event_args); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(1, manual_event_args.signaled); ++ + sem_args.count = 2; + ret = ioctl(fd, WINESYNC_IOC_PUT_SEM, &sem_args); + EXPECT_EQ(0, ret); + EXPECT_EQ(0, sem_args.count); ++ check_sem_state(fd, sem_args.sem, 2, 3); ++ ++ ret = ioctl(fd, WINESYNC_IOC_RESET_EVENT, &auto_event_args); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(1, auto_event_args.signaled); ++ ++ ret = ioctl(fd, WINESYNC_IOC_SET_EVENT, &manual_event_args); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, manual_event_args.signaled); ++ ++ ret = ioctl(fd, WINESYNC_IOC_SET_EVENT, &auto_event_args); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, auto_event_args.signaled); ++ + check_sem_state(fd, sem_args.sem, 1, 3); + check_mutex_state(fd, mutex_args.mutex, 1, 456); ++ check_event_state(fd, manual_event_args.event, 1, 1); ++ check_event_state(fd, auto_event_args.event, 0, 0); + + ret = wait_for_thread(thread, 100); + EXPECT_EQ(0, ret); +@@ -1034,6 +1178,10 @@ TEST(wake_all) + EXPECT_EQ(0, ret); + ret = ioctl(fd, WINESYNC_IOC_DELETE, &mutex_args.mutex); + EXPECT_EQ(0, ret); ++ ret = ioctl(fd, WINESYNC_IOC_DELETE, &manual_event_args.event); ++ EXPECT_EQ(0, ret); ++ ret = ioctl(fd, WINESYNC_IOC_DELETE, &auto_event_args.event); ++ EXPECT_EQ(0, ret); + + ret = wait_for_thread(thread, 200); + EXPECT_EQ(0, ret); +-- +2.36.0 + +From 8d2d3a310b90252903cc10e84e2bb1a06d7e8fac Mon Sep 17 00:00:00 2001 +From: Zebediah Figura +Date: Wed, 19 Jan 2022 21:06:22 -0600 +Subject: [PATCH 29/34] selftests: winesync: Add some tests for invalid object + handling with events. + +--- + .../selftests/drivers/winesync/winesync.c | 34 +++++++++++++++++++ + 1 file changed, 34 insertions(+) + +diff --git a/tools/testing/selftests/drivers/winesync/winesync.c b/tools/testing/selftests/drivers/winesync/winesync.c +index 2ccc51510230..f2e18836c733 100644 +--- a/tools/testing/selftests/drivers/winesync/winesync.c ++++ b/tools/testing/selftests/drivers/winesync/winesync.c +@@ -712,6 +712,7 @@ TEST(test_wait_all) + + TEST(invalid_objects) + { ++ struct winesync_event_args event_args = {0}; + struct winesync_mutex_args mutex_args = {0}; + struct winesync_wait_args wait_args = {0}; + struct winesync_sem_args sem_args = {0}; +@@ -737,6 +738,22 @@ TEST(invalid_objects) + EXPECT_EQ(-1, ret); + EXPECT_EQ(EINVAL, errno); + ++ ret = ioctl(fd, WINESYNC_IOC_SET_EVENT, &event_args); ++ EXPECT_EQ(-1, ret); ++ EXPECT_EQ(EINVAL, errno); ++ ++ ret = ioctl(fd, WINESYNC_IOC_RESET_EVENT, &event_args); ++ EXPECT_EQ(-1, ret); ++ EXPECT_EQ(EINVAL, errno); ++ ++ ret = ioctl(fd, WINESYNC_IOC_PULSE_EVENT, &event_args); ++ EXPECT_EQ(-1, ret); ++ EXPECT_EQ(EINVAL, errno); ++ ++ ret = ioctl(fd, WINESYNC_IOC_READ_EVENT, &event_args); ++ EXPECT_EQ(-1, ret); ++ EXPECT_EQ(EINVAL, errno); ++ + wait_args.objs = (uintptr_t)objs; + wait_args.count = 1; + ret = ioctl(fd, WINESYNC_IOC_WAIT_ANY, &wait_args); +@@ -763,6 +780,23 @@ TEST(invalid_objects) + EXPECT_EQ(-1, ret); + EXPECT_EQ(EINVAL, errno); + ++ event_args.event = sem_args.sem; ++ ret = ioctl(fd, WINESYNC_IOC_SET_EVENT, &event_args); ++ EXPECT_EQ(-1, ret); ++ EXPECT_EQ(EINVAL, errno); ++ ++ ret = ioctl(fd, WINESYNC_IOC_RESET_EVENT, &event_args); ++ EXPECT_EQ(-1, ret); ++ EXPECT_EQ(EINVAL, errno); ++ ++ ret = ioctl(fd, WINESYNC_IOC_PULSE_EVENT, &event_args); ++ EXPECT_EQ(-1, ret); ++ EXPECT_EQ(EINVAL, errno); ++ ++ ret = ioctl(fd, WINESYNC_IOC_READ_EVENT, &event_args); ++ EXPECT_EQ(-1, ret); ++ EXPECT_EQ(EINVAL, errno); ++ + objs[0] = sem_args.sem; + objs[1] = sem_args.sem + 1; + wait_args.count = 2; +-- +2.36.0 + +From 25270ec5877bcf2aa81fc4dd8326a4ee5af6e541 Mon Sep 17 00:00:00 2001 +From: Zebediah Figura +Date: Wed, 19 Jan 2022 22:01:46 -0600 +Subject: [PATCH 30/34] docs: winesync: Document event APIs. + +--- + Documentation/userspace-api/winesync.rst | 104 ++++++++++++++++++++++- + 1 file changed, 101 insertions(+), 3 deletions(-) + +diff --git a/Documentation/userspace-api/winesync.rst b/Documentation/userspace-api/winesync.rst +index 34e54be229cf..ffa2f8fbc7e3 100644 +--- a/Documentation/userspace-api/winesync.rst ++++ b/Documentation/userspace-api/winesync.rst +@@ -18,8 +18,8 @@ interfaces such as futex(2) and poll(2). + Synchronization primitives + ========================== + +-The winesync driver exposes two types of synchronization primitives, +-semaphores and mutexes. ++The winesync driver exposes three types of synchronization primitives: ++semaphores, mutexes, and events. + + A semaphore holds a single volatile 32-bit counter, and a static + 32-bit integer denoting the maximum value. It is considered signaled +@@ -45,6 +45,12 @@ intended use is to store a thread identifier; however, the winesync + driver does not actually validate that a calling thread provides + consistent or unique identifiers. + ++An event holds a volatile boolean state denoting whether it is ++signaled or not. There are two types of events, auto-reset and ++manual-reset. An auto-reset event is designaled when a wait is ++satisfied; a manual-reset event is not. The event type is specified ++when the event is created. ++ + Unless specified otherwise, all operations on an object are atomic and + totally ordered with respect to other operations on the same object. + +@@ -78,6 +84,12 @@ structures used in ioctl calls:: + __u32 count; + }; + ++ struct winesync_event_args { ++ __u32 event; ++ __u32 signaled; ++ __u32 manual; ++ }; ++ + struct winesync_wait_args { + __u64 timeout; + __u64 objs; +@@ -125,6 +137,22 @@ The ioctls are as follows: + If ``owner`` is nonzero and ``count`` is zero, or if ``owner`` is + zero and ``count`` is nonzero, the function fails with ``EINVAL``. + ++.. c:macro:: WINESYNC_IOC_CREATE_EVENT ++ ++ Create an event object. Takes a pointer to struct ++ :c:type:`winesync_event_args`, which is used as follows: ++ ++ .. list-table:: ++ ++ * - ``event`` ++ - On output, contains the identifier of the created event. ++ * - ``signaled`` ++ - If nonzero, the event is initially signaled, otherwise ++ nonsignaled. ++ * - ``manual`` ++ - If nonzero, the event is a manual-reset event, otherwise ++ auto-reset. ++ + .. c:macro:: WINESYNC_IOC_DELETE + + Delete an object of any type. Takes an input-only pointer to a +@@ -178,6 +206,60 @@ The ioctls are as follows: + unowned and signaled, and eligible threads waiting on it will be + woken as appropriate. + ++.. c:macro:: WINESYNC_IOC_SET_EVENT ++ ++ Signal an event object. Takes a pointer to struct ++ :c:type:`winesync_event_args`, which is used as follows: ++ ++ .. list-table:: ++ ++ * - ``event`` ++ - Event object to set. ++ * - ``signaled`` ++ - On output, contains the previous state of the event. ++ * - ``manual`` ++ - Unused. ++ ++ Eligible threads will be woken, and auto-reset events will be ++ designaled appropriately. ++ ++.. c:macro:: WINESYNC_IOC_RESET_EVENT ++ ++ Designal an event object. Takes a pointer to struct ++ :c:type:`winesync_event_args`, which is used as follows: ++ ++ .. list-table:: ++ ++ * - ``event`` ++ - Event object to reset. ++ * - ``signaled`` ++ - On output, contains the previous state of the event. ++ * - ``manual`` ++ - Unused. ++ ++.. c:macro:: WINESYNC_IOC_PULSE_EVENT ++ ++ Wake threads waiting on an event object without leaving it in a ++ signaled state. Takes a pointer to struct ++ :c:type:`winesync_event_args`, which is used as follows: ++ ++ .. list-table:: ++ ++ * - ``event`` ++ - Event object to pulse. ++ * - ``signaled`` ++ - On output, contains the previous state of the event. ++ * - ``manual`` ++ - Unused. ++ ++ A pulse operation can be thought of as a set followed by a reset, ++ performed as a single atomic operation. If two threads are waiting ++ on an auto-reset event which is pulsed, only one will be woken. If ++ two threads are waiting a manual-reset event which is pulsed, both ++ will be woken. However, in both cases, the event will be unsignaled ++ afterwards, and a simultaneous read operation will always report the ++ event as unsignaled. ++ + .. c:macro:: WINESYNC_IOC_READ_SEM + + Read the current state of a semaphore object. Takes a pointer to +@@ -211,6 +293,21 @@ The ioctls are as follows: + ``EOWNERDEAD``. In this case, ``count`` and ``owner`` are set to + zero. + ++.. c:macro:: WINESYNC_IOC_READ_EVENT ++ ++ Read the current state of an event object. Takes a pointer to struct ++ :c:type:`winesync_event_args`, which is used as follows: ++ ++ .. list-table:: ++ ++ * - ``event`` ++ - Event object. ++ * - ``signaled`` ++ - On output, contains the current state of the event. ++ * - ``manual`` ++ - On output, contains 1 if the event is a manual-reset event, ++ and 0 otherwise. ++ + .. c:macro:: WINESYNC_IOC_KILL_OWNER + + Mark any mutexes owned by the given owner as unowned and +@@ -272,7 +369,8 @@ The ioctls are as follows: + considered to be signaled if it is unowned or if its owner matches + the ``owner`` argument, and is acquired by incrementing its + recursion count by one and setting its owner to the ``owner`` +- argument. ++ argument. An auto-reset event is acquired by designaling it; a ++ manual-reset event is not affected by acquisition. + + Acquisition is atomic and totally ordered with respect to other + operations on the same object. If two wait operations (with +-- +2.36.0 + +From 80f5b4dfd947592ff89cb54a07ce9d1087c608d0 Mon Sep 17 00:00:00 2001 +From: Zebediah Figura +Date: Wed, 13 Apr 2022 20:02:39 -0500 +Subject: [PATCH 31/34] winesync: Introduce alertable waits. + +--- + drivers/misc/winesync.c | 68 ++++++++++++++++++++++++++++++----- + include/uapi/linux/winesync.h | 2 +- + 2 files changed, 60 insertions(+), 10 deletions(-) + +diff --git a/drivers/misc/winesync.c b/drivers/misc/winesync.c +index 4fbf231a7909..7a28f58dbbf2 100644 +--- a/drivers/misc/winesync.c ++++ b/drivers/misc/winesync.c +@@ -841,10 +841,11 @@ static int setup_wait(struct winesync_device *dev, + const __u32 count = args->count; + struct winesync_q *q; + ktime_t timeout = 0; ++ __u32 total_count; + __u32 *ids; + __u32 i, j; + +- if (!args->owner || args->pad) ++ if (!args->owner) + return -EINVAL; + + if (args->timeout) { +@@ -858,7 +859,11 @@ static int setup_wait(struct winesync_device *dev, + timeout = timespec64_to_ns(&to); + } + +- ids = kmalloc_array(count, sizeof(*ids), GFP_KERNEL); ++ total_count = count; ++ if (args->alert) ++ total_count++; ++ ++ ids = kmalloc_array(total_count, sizeof(*ids), GFP_KERNEL); + if (!ids) + return -ENOMEM; + if (copy_from_user(ids, u64_to_user_ptr(args->objs), +@@ -866,8 +871,10 @@ static int setup_wait(struct winesync_device *dev, + kfree(ids); + return -EFAULT; + } ++ if (args->alert) ++ ids[count] = args->alert; + +- q = kmalloc(struct_size(q, entries, count), GFP_KERNEL); ++ q = kmalloc(struct_size(q, entries, total_count), GFP_KERNEL); + if (!q) { + kfree(ids); + return -ENOMEM; +@@ -879,7 +886,7 @@ static int setup_wait(struct winesync_device *dev, + q->ownerdead = false; + q->count = count; + +- for (i = 0; i < count; i++) { ++ for (i = 0; i < total_count; i++) { + struct winesync_q_entry *entry = &q->entries[i]; + struct winesync_obj *obj = get_obj(dev, ids[i]); + +@@ -934,9 +941,9 @@ static int winesync_wait_any(struct winesync_device *dev, void __user *argp) + { + struct winesync_wait_args args; + struct winesync_q *q; ++ __u32 i, total_count; + ktime_t timeout; + int signaled; +- __u32 i; + int ret; + + if (copy_from_user(&args, argp, sizeof(args))) +@@ -946,9 +953,13 @@ static int winesync_wait_any(struct winesync_device *dev, void __user *argp) + if (ret < 0) + return ret; + ++ total_count = args.count; ++ if (args.alert) ++ total_count++; ++ + /* queue ourselves */ + +- for (i = 0; i < args.count; i++) { ++ for (i = 0; i < total_count; i++) { + struct winesync_q_entry *entry = &q->entries[i]; + struct winesync_obj *obj = entry->obj; + +@@ -957,9 +968,15 @@ static int winesync_wait_any(struct winesync_device *dev, void __user *argp) + spin_unlock(&obj->lock); + } + +- /* check if we are already signaled */ ++ /* ++ * Check if we are already signaled. ++ * ++ * Note that the API requires that normal objects are checked before ++ * the alert event. Hence we queue the alert event last, and check ++ * objects in order. ++ */ + +- for (i = 0; i < args.count; i++) { ++ for (i = 0; i < total_count; i++) { + struct winesync_obj *obj = q->entries[i].obj; + + if (atomic_read(&q->signaled) != -1) +@@ -976,7 +993,7 @@ static int winesync_wait_any(struct winesync_device *dev, void __user *argp) + + /* and finally, unqueue */ + +- for (i = 0; i < args.count; i++) { ++ for (i = 0; i < total_count; i++) { + struct winesync_q_entry *entry = &q->entries[i]; + struct winesync_obj *obj = entry->obj; + +@@ -1036,6 +1053,14 @@ static int winesync_wait_all(struct winesync_device *dev, void __user *argp) + */ + list_add_tail(&entry->node, &obj->all_waiters); + } ++ if (args.alert) { ++ struct winesync_q_entry *entry = &q->entries[args.count]; ++ struct winesync_obj *obj = entry->obj; ++ ++ spin_lock(&obj->lock); ++ list_add_tail(&entry->node, &obj->any_waiters); ++ spin_unlock(&obj->lock); ++ } + + /* check if we are already signaled */ + +@@ -1043,6 +1068,21 @@ static int winesync_wait_all(struct winesync_device *dev, void __user *argp) + + spin_unlock(&dev->wait_all_lock); + ++ /* ++ * Check if the alert event is signaled, making sure to do so only ++ * after checking if the other objects are signaled. ++ */ ++ ++ if (args.alert) { ++ struct winesync_obj *obj = q->entries[args.count].obj; ++ ++ if (atomic_read(&q->signaled) == -1) { ++ spin_lock(&obj->lock); ++ try_wake_any_obj(obj); ++ spin_unlock(&obj->lock); ++ } ++ } ++ + /* sleep */ + + ret = winesync_schedule(q, args.timeout ? &timeout : NULL); +@@ -1065,6 +1105,16 @@ static int winesync_wait_all(struct winesync_device *dev, void __user *argp) + + put_obj(obj); + } ++ if (args.alert) { ++ struct winesync_q_entry *entry = &q->entries[args.count]; ++ struct winesync_obj *obj = entry->obj; ++ ++ spin_lock(&obj->lock); ++ list_del(&entry->node); ++ spin_unlock(&obj->lock); ++ ++ put_obj(obj); ++ } + + spin_unlock(&dev->wait_all_lock); + +diff --git a/include/uapi/linux/winesync.h b/include/uapi/linux/winesync.h +index fb3788339ffe..5b4e369f7469 100644 +--- a/include/uapi/linux/winesync.h ++++ b/include/uapi/linux/winesync.h +@@ -34,7 +34,7 @@ struct winesync_wait_args { + __u32 count; + __u32 owner; + __u32 index; +- __u32 pad; ++ __u32 alert; + }; + + #define WINESYNC_IOC_BASE 0xf7 +-- +2.37.3 + +From 127efad71a0702a68890097b114b3467c234259f Mon Sep 17 00:00:00 2001 +From: Zebediah Figura +Date: Wed, 20 Apr 2022 18:08:37 -0500 +Subject: [PATCH 32/34] selftests: winesync: Add tests for alertable waits. + +--- + .../selftests/drivers/winesync/winesync.c | 191 +++++++++++++++++- + 1 file changed, 188 insertions(+), 3 deletions(-) + +diff --git a/tools/testing/selftests/drivers/winesync/winesync.c b/tools/testing/selftests/drivers/winesync/winesync.c +index f2e18836c733..a87e3c48709b 100644 +--- a/tools/testing/selftests/drivers/winesync/winesync.c ++++ b/tools/testing/selftests/drivers/winesync/winesync.c +@@ -110,7 +110,7 @@ static int read_event_state(int fd, __u32 event, __u32 *signaled, __u32 *manual) + }) + + static int wait_objs(int fd, unsigned long request, __u32 count, +- const __u32 *objs, __u32 owner, __u32 *index) ++ const __u32 *objs, __u32 owner, __u32 alert, __u32 *index) + { + struct winesync_wait_args args = {0}; + struct timespec timeout; +@@ -123,6 +123,7 @@ static int wait_objs(int fd, unsigned long request, __u32 count, + args.objs = (uintptr_t)objs; + args.owner = owner; + args.index = 0xdeadbeef; ++ args.alert = alert; + ret = ioctl(fd, request, &args); + *index = args.index; + return ret; +@@ -131,13 +132,29 @@ static int wait_objs(int fd, unsigned long request, __u32 count, + static int wait_any(int fd, __u32 count, const __u32 *objs, + __u32 owner, __u32 *index) + { +- return wait_objs(fd, WINESYNC_IOC_WAIT_ANY, count, objs, owner, index); ++ return wait_objs(fd, WINESYNC_IOC_WAIT_ANY, ++ count, objs, owner, 0, index); + } + + static int wait_all(int fd, __u32 count, const __u32 *objs, + __u32 owner, __u32 *index) + { +- return wait_objs(fd, WINESYNC_IOC_WAIT_ALL, count, objs, owner, index); ++ return wait_objs(fd, WINESYNC_IOC_WAIT_ALL, ++ count, objs, owner, 0, index); ++} ++ ++static int wait_any_alert(int fd, __u32 count, const __u32 *objs, ++ __u32 owner, __u32 alert, __u32 *index) ++{ ++ return wait_objs(fd, WINESYNC_IOC_WAIT_ANY, ++ count, objs, owner, alert, index); ++} ++ ++static int wait_all_alert(int fd, __u32 count, const __u32 *objs, ++ __u32 owner, __u32 alert, __u32 *index) ++{ ++ return wait_objs(fd, WINESYNC_IOC_WAIT_ALL, ++ count, objs, owner, alert, index); + } + + TEST(semaphore_state) +@@ -1225,4 +1242,172 @@ TEST(wake_all) + close(fd); + } + ++TEST(alert_any) ++{ ++ struct winesync_event_args event_args = {0}; ++ struct winesync_sem_args sem_args = {0}; ++ __u32 objs[2], index; ++ int fd, ret; ++ ++ fd = open("/dev/winesync", O_CLOEXEC | O_RDONLY); ++ ASSERT_LE(0, fd); ++ ++ sem_args.count = 0; ++ sem_args.max = 2; ++ sem_args.sem = 0xdeadbeef; ++ ret = ioctl(fd, WINESYNC_IOC_CREATE_SEM, &sem_args); ++ EXPECT_EQ(0, ret); ++ EXPECT_NE(0xdeadbeef, sem_args.sem); ++ objs[0] = sem_args.sem; ++ ++ sem_args.count = 1; ++ sem_args.max = 2; ++ sem_args.sem = 0xdeadbeef; ++ ret = ioctl(fd, WINESYNC_IOC_CREATE_SEM, &sem_args); ++ EXPECT_EQ(0, ret); ++ EXPECT_NE(0xdeadbeef, sem_args.sem); ++ objs[1] = sem_args.sem; ++ ++ event_args.manual = true; ++ event_args.signaled = true; ++ ret = ioctl(fd, WINESYNC_IOC_CREATE_EVENT, &event_args); ++ EXPECT_EQ(0, ret); ++ ++ ret = wait_any_alert(fd, 0, NULL, 123, event_args.event, &index); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, index); ++ ++ ret = ioctl(fd, WINESYNC_IOC_RESET_EVENT, &event_args); ++ EXPECT_EQ(0, ret); ++ ++ ret = wait_any_alert(fd, 0, NULL, 123, event_args.event, &index); ++ EXPECT_EQ(-1, ret); ++ EXPECT_EQ(ETIMEDOUT, errno); ++ ++ ret = ioctl(fd, WINESYNC_IOC_SET_EVENT, &event_args); ++ EXPECT_EQ(0, ret); ++ ++ ret = wait_any_alert(fd, 2, objs, 123, event_args.event, &index); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(1, index); ++ ++ ret = wait_any_alert(fd, 2, objs, 123, event_args.event, &index); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(2, index); ++ ++ ret = ioctl(fd, WINESYNC_IOC_DELETE, &event_args.event); ++ EXPECT_EQ(0, ret); ++ ++ /* test with an auto-reset event */ ++ ++ event_args.manual = false; ++ event_args.signaled = true; ++ ret = ioctl(fd, WINESYNC_IOC_CREATE_EVENT, &event_args); ++ EXPECT_EQ(0, ret); ++ ++ sem_args.sem = objs[0]; ++ sem_args.count = 1; ++ ret = ioctl(fd, WINESYNC_IOC_PUT_SEM, &sem_args); ++ EXPECT_EQ(0, ret); ++ ++ ret = wait_any_alert(fd, 2, objs, 123, event_args.event, &index); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, index); ++ ++ ret = wait_any_alert(fd, 2, objs, 123, event_args.event, &index); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(2, index); ++ ++ ret = wait_any_alert(fd, 2, objs, 123, event_args.event, &index); ++ EXPECT_EQ(-1, ret); ++ EXPECT_EQ(ETIMEDOUT, errno); ++ ++ ret = ioctl(fd, WINESYNC_IOC_DELETE, &event_args.event); ++ EXPECT_EQ(0, ret); ++ ++ ret = ioctl(fd, WINESYNC_IOC_DELETE, &objs[0]); ++ EXPECT_EQ(0, ret); ++ ret = ioctl(fd, WINESYNC_IOC_DELETE, &objs[1]); ++ EXPECT_EQ(0, ret); ++ ++ close(fd); ++} ++ ++TEST(alert_all) ++{ ++ struct winesync_event_args event_args = {0}; ++ struct winesync_sem_args sem_args = {0}; ++ __u32 objs[2], index; ++ int fd, ret; ++ ++ fd = open("/dev/winesync", O_CLOEXEC | O_RDONLY); ++ ASSERT_LE(0, fd); ++ ++ sem_args.count = 2; ++ sem_args.max = 2; ++ sem_args.sem = 0xdeadbeef; ++ ret = ioctl(fd, WINESYNC_IOC_CREATE_SEM, &sem_args); ++ EXPECT_EQ(0, ret); ++ EXPECT_NE(0xdeadbeef, sem_args.sem); ++ objs[0] = sem_args.sem; ++ ++ sem_args.count = 1; ++ sem_args.max = 2; ++ sem_args.sem = 0xdeadbeef; ++ ret = ioctl(fd, WINESYNC_IOC_CREATE_SEM, &sem_args); ++ EXPECT_EQ(0, ret); ++ EXPECT_NE(0xdeadbeef, sem_args.sem); ++ objs[1] = sem_args.sem; ++ ++ event_args.manual = true; ++ event_args.signaled = true; ++ ret = ioctl(fd, WINESYNC_IOC_CREATE_EVENT, &event_args); ++ EXPECT_EQ(0, ret); ++ ++ ret = wait_all_alert(fd, 2, objs, 123, event_args.event, &index); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, index); ++ ++ ret = wait_all_alert(fd, 2, objs, 123, event_args.event, &index); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(2, index); ++ ++ ret = ioctl(fd, WINESYNC_IOC_DELETE, &event_args.event); ++ EXPECT_EQ(0, ret); ++ ++ /* test with an auto-reset event */ ++ ++ event_args.manual = false; ++ event_args.signaled = true; ++ ret = ioctl(fd, WINESYNC_IOC_CREATE_EVENT, &event_args); ++ EXPECT_EQ(0, ret); ++ ++ sem_args.sem = objs[1]; ++ sem_args.count = 2; ++ ret = ioctl(fd, WINESYNC_IOC_PUT_SEM, &sem_args); ++ EXPECT_EQ(0, ret); ++ ++ ret = wait_all_alert(fd, 2, objs, 123, event_args.event, &index); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, index); ++ ++ ret = wait_all_alert(fd, 2, objs, 123, event_args.event, &index); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(2, index); ++ ++ ret = wait_all_alert(fd, 2, objs, 123, event_args.event, &index); ++ EXPECT_EQ(-1, ret); ++ EXPECT_EQ(ETIMEDOUT, errno); ++ ++ ret = ioctl(fd, WINESYNC_IOC_DELETE, &event_args.event); ++ EXPECT_EQ(0, ret); ++ ++ ret = ioctl(fd, WINESYNC_IOC_DELETE, &objs[0]); ++ EXPECT_EQ(0, ret); ++ ret = ioctl(fd, WINESYNC_IOC_DELETE, &objs[1]); ++ EXPECT_EQ(0, ret); ++ ++ close(fd); ++} ++ + TEST_HARNESS_MAIN +-- +2.36.0 + +From e5ec8276fae40b6a2cdab3cb728160705c0f40ab Mon Sep 17 00:00:00 2001 +From: Zebediah Figura +Date: Wed, 20 Apr 2022 18:24:43 -0500 +Subject: [PATCH 33/34] serftests: winesync: Add some tests for wakeup + signaling via alerts. + +--- + .../selftests/drivers/winesync/winesync.c | 66 +++++++++++++++++++ + 1 file changed, 66 insertions(+) + +diff --git a/tools/testing/selftests/drivers/winesync/winesync.c b/tools/testing/selftests/drivers/winesync/winesync.c +index a87e3c48709b..169e922484b0 100644 +--- a/tools/testing/selftests/drivers/winesync/winesync.c ++++ b/tools/testing/selftests/drivers/winesync/winesync.c +@@ -1245,8 +1245,12 @@ TEST(wake_all) + TEST(alert_any) + { + struct winesync_event_args event_args = {0}; ++ struct winesync_wait_args wait_args = {0}; + struct winesync_sem_args sem_args = {0}; ++ struct wait_args thread_args; ++ struct timespec timeout; + __u32 objs[2], index; ++ pthread_t thread; + int fd, ret; + + fd = open("/dev/winesync", O_CLOEXEC | O_RDONLY); +@@ -1295,6 +1299,35 @@ TEST(alert_any) + EXPECT_EQ(0, ret); + EXPECT_EQ(2, index); + ++ /* test wakeup via alert */ ++ ++ ret = ioctl(fd, WINESYNC_IOC_RESET_EVENT, &event_args); ++ EXPECT_EQ(0, ret); ++ ++ get_abs_timeout(&timeout, CLOCK_MONOTONIC, 1000); ++ wait_args.timeout = (uintptr_t)&timeout; ++ wait_args.objs = (uintptr_t)objs; ++ wait_args.count = 2; ++ wait_args.owner = 123; ++ wait_args.index = 0xdeadbeef; ++ wait_args.alert = event_args.event; ++ thread_args.fd = fd; ++ thread_args.args = &wait_args; ++ thread_args.request = WINESYNC_IOC_WAIT_ANY; ++ ret = pthread_create(&thread, NULL, wait_thread, &thread_args); ++ EXPECT_EQ(0, ret); ++ ++ ret = wait_for_thread(thread, 100); ++ EXPECT_EQ(ETIMEDOUT, ret); ++ ++ ret = ioctl(fd, WINESYNC_IOC_SET_EVENT, &event_args); ++ EXPECT_EQ(0, ret); ++ ++ ret = wait_for_thread(thread, 100); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, thread_args.ret); ++ EXPECT_EQ(2, wait_args.index); ++ + ret = ioctl(fd, WINESYNC_IOC_DELETE, &event_args.event); + EXPECT_EQ(0, ret); + +@@ -1336,8 +1369,12 @@ TEST(alert_any) + TEST(alert_all) + { + struct winesync_event_args event_args = {0}; ++ struct winesync_wait_args wait_args = {0}; + struct winesync_sem_args sem_args = {0}; ++ struct wait_args thread_args; ++ struct timespec timeout; + __u32 objs[2], index; ++ pthread_t thread; + int fd, ret; + + fd = open("/dev/winesync", O_CLOEXEC | O_RDONLY); +@@ -1372,6 +1409,35 @@ TEST(alert_all) + EXPECT_EQ(0, ret); + EXPECT_EQ(2, index); + ++ /* test wakeup via alert */ ++ ++ ret = ioctl(fd, WINESYNC_IOC_RESET_EVENT, &event_args); ++ EXPECT_EQ(0, ret); ++ ++ get_abs_timeout(&timeout, CLOCK_MONOTONIC, 1000); ++ wait_args.timeout = (uintptr_t)&timeout; ++ wait_args.objs = (uintptr_t)objs; ++ wait_args.count = 2; ++ wait_args.owner = 123; ++ wait_args.index = 0xdeadbeef; ++ wait_args.alert = event_args.event; ++ thread_args.fd = fd; ++ thread_args.args = &wait_args; ++ thread_args.request = WINESYNC_IOC_WAIT_ALL; ++ ret = pthread_create(&thread, NULL, wait_thread, &thread_args); ++ EXPECT_EQ(0, ret); ++ ++ ret = wait_for_thread(thread, 100); ++ EXPECT_EQ(ETIMEDOUT, ret); ++ ++ ret = ioctl(fd, WINESYNC_IOC_SET_EVENT, &event_args); ++ EXPECT_EQ(0, ret); ++ ++ ret = wait_for_thread(thread, 100); ++ EXPECT_EQ(0, ret); ++ EXPECT_EQ(0, thread_args.ret); ++ EXPECT_EQ(2, wait_args.index); ++ + ret = ioctl(fd, WINESYNC_IOC_DELETE, &event_args.event); + EXPECT_EQ(0, ret); + +-- +2.36.0 + +From 50ed00eef095c7799949b2523a5c21210b374f86 Mon Sep 17 00:00:00 2001 +From: Zebediah Figura +Date: Wed, 20 Apr 2022 18:58:17 -0500 +Subject: [PATCH 34/34] docs: winesync: Document alertable waits. + +--- + Documentation/userspace-api/winesync.rst | 40 ++++++++++++++++++------ + 1 file changed, 31 insertions(+), 9 deletions(-) + +diff --git a/Documentation/userspace-api/winesync.rst b/Documentation/userspace-api/winesync.rst +index ffa2f8fbc7e3..f0110d2744c7 100644 +--- a/Documentation/userspace-api/winesync.rst ++++ b/Documentation/userspace-api/winesync.rst +@@ -354,9 +354,13 @@ The ioctls are as follows: + ``EINVAL``. + * - ``index`` + - On success, contains the index (into ``objs``) of the object +- which was signaled. +- * - ``pad`` +- - This field is not used and must be set to zero. ++ which was signaled. If ``alert`` was signaled instead, ++ this contains ``count``. ++ * - ``alert`` ++ - Optional event object identifier. If nonzero, this specifies ++ an "alert" event object which, if signaled, will terminate ++ the wait. If nonzero, the identifier must point to a valid ++ event. + + This function attempts to acquire one of the given objects. If + unable to do so, it sleeps until an object becomes signaled, +@@ -385,9 +389,19 @@ The ioctls are as follows: + the given owner (with a recursion count of 1) and as no longer + inconsistent, and ``index`` is still set to the index of the mutex. + +- It is valid to pass the same object more than once. If a wakeup +- occurs due to that object being signaled, ``index`` is set to the +- lowest index corresponding to that object. ++ The ``alert`` argument is an "extra" event which can terminate the ++ wait, independently of all other objects. If members of ``objs`` and ++ ``alert`` are both simultaneously signaled, a member of ``objs`` ++ will always be given priority and acquired first. Aside from this, ++ for "any" waits, there is no difference between passing an event as ++ this parameter, and passing it as an additional object at the end of ++ the ``objs`` array. For "all" waits, there is an additional ++ difference, as described below. ++ ++ It is valid to pass the same object more than once, including by ++ passing the same event in the ``objs`` array and in ``alert``. If a ++ wakeup occurs due to that object being signaled, ``index`` is set to ++ the lowest index corresponding to that object. + + The function may fail with ``EINTR`` if a signal is received. + +@@ -396,7 +410,7 @@ The ioctls are as follows: + Poll on a list of objects, atomically acquiring all of them. Takes a + pointer to struct :c:type:`winesync_wait_args`, which is used + identically to ``WINESYNC_IOC_WAIT_ANY``, except that ``index`` is +- always filled with zero on success. ++ always filled with zero on success if not woken via alert. + + This function attempts to simultaneously acquire all of the given + objects. If unable to do so, it sleeps until all objects become +@@ -417,6 +431,14 @@ The ioctls are as follows: + objects are specified, there is no way to know which were marked as + inconsistent. + ++ As with "any" waits, the ``alert`` argument is an "extra" event ++ which can terminate the wait. Critically, however, an "all" wait ++ will succeed if all members in ``objs`` are signaled, *or* if ++ ``alert`` is signaled. In the latter case ``index`` will be set to ++ ``count``. As with "any" waits, if both conditions are filled, the ++ former takes priority, and objects in ``objs`` will be acquired. ++ + Unlike ``WINESYNC_IOC_WAIT_ANY``, it is not valid to pass the same +- object more than once. If this is attempted, the function fails with +- ``EINVAL``. ++ object more than once, nor is it valid to pass the same object in ++ ``objs`` and in ``alert`` If this is attempted, the function fails ++ with ``EINVAL``. +-- +2.36.0 + diff --git a/patches/series b/patches/series index 0a6eaa8..53037fa 100644 --- a/patches/series +++ b/patches/series @@ -1,17 +1,26 @@ cachyos/0001-cachyos-base-all.patch cachyos/0001-bore-cachy.patch +asuslinux/ROG-ALLY-NCT6775-PLATFORM.patch +asuslinux/asus-linux.patch +asuslinux/rog-ally-audio-fix.patch +asuslinux/rog-ally-bmc150.patch +asuslinux/v2-0001-platform-x86-asus-wmi-disable-USB0-hub-on-ROG-All.patch nobara/0001-Allow-to-set-custom-USB-pollrate-for-specific-device.patch -nobara/set-ps4-bt-poll-rate-1000hz.patch -nobara-rebased/amdgpu-si-cik-default.patch nobara/0001-Revert-PCI-Add-a-REBAR-size-quirk-for-Sapphire-RX-56.patch -#nobara/0001-Revert-nvme-pci-drop-redundant-pci_enable_pcie_error.patch +nobara/0001-Revert-nvme-pci-drop-redundant-pci_enable_pcie_error.patch +nobara/0001-Set-amdgpu.ppfeaturemask-0xffffffff-as-default.patch nobara/0001-acpi-proc-idle-skip-dummy-wait.patch +nobara/0001-add-acpi_call.patch +nobara/0001-amd-hdr.patch nobara/0001-drm-i915-quirks-disable-async-flipping-on-specific-d.patch +nobara/0001-hid-asus-nero-patches-rogue.patch nobara/0002-drm-i915-add-kernel-parameter-to-disable-async-page-.patch -asuslinux/v6-0001-platform-x86-asus-wmi-add-support-for-ASUS-screen.patch -asuslinux-rebased/v2-0002-ALSA-hda-cs35l41-Support-ASUS-2023-laptops-with-m.patch -asuslinux/amd-tablet-sfh.patch -#nobara-rebased/linux-surface.patch -nobara/rog-ally-bmc150.patch -nobara/rog-ally-side-keys-fix.patch -nobara/rog-ally-alsa.patch +nobara/OpenRGB.patch +nobara/amdgpu-si-cik-default.patch +nobara/lenovo-legion-laptop.patch +nobara/linux-surface.patch +nobara/mt76:-mt7921:-Disable-powersave-features-by-default.patch +nobara/set-ps4-bt-poll-rate-1000hz.patch +nobara/steam-deck.patch +nobara/uinput.patch +nobara/winesync.patc